In this ESP32 tutorial, we will check how to use the pthreads library on the Arduino core and create a simple testing program. The tests of this ESP32 tutorial were performed using a DFRobot’s ESP-WROOM-32 device integrated in a ESP32 development board.
Introduction
In this ESP32 tutorial, we will check how to use the pthreads library on the Arduino core and create a simple testing program. You can read more about pthreads here.
The header file for this library on the Arduino core can be found here. The implementation of the pthreads library can be seen here on the GitHub page of the IDF framework.
From the analysis of the .c implementation file, we can check that this pthreads library is implemented on top of FreeRTOS. Besides that, it is also mentioned that not all the original routines of the library are implemented and some of the functions available don’t implement all of the functionality.
For a list of tutorials about FreeRTOS, please check the related posts section at the end of this tutorial.
The tests of this ESP32 tutorial were performed using a DFRobot’s ESP-WROOM-32 device integrated in a ESP32 FireBeetle board.
The code
We will start our code by importing the pthread.h library, so we can then use the thread functions.
#include <pthread.h>
In the setup function, we will start by opening a serial connection, to print the results of running the program.
Serial.begin(115200);
As we will see later in the function call to create a thread, we will need a variable of type pthread_t, which works like a handle to the created thread [1]. Thus, it can be later used by functions that require a thread identifier [2].
For this tutorial we will be launching 4 threads, so we will create an array of pthread_t variables with size 4.
pthread_t threads[4];
We will also declare an int variable to store the result of the thread creation function and check if any error has occurred during its execution.
int returnValue;
In order to create a thread using the pthreads API, we simply need to call the pthread_create function.
As first input, this function receives a pointer to a pthread_t variable, in which the thread handle will be stored [1].
The second argument is a pointer to a pthread_attr_t structure whose contents are used at thread creation time to determine attributes for the new thread [1]. Nonetheless, in the ESP32 pthreads implementation, using this argument is not supported [3] and thus we should pass NULL here.
As third argument, we pass the thread function, which is the one that will be executed. This thread has a fixed prototype, more precisely it needs to return a pointer to void and receive as input also a pointer to void (void *).
We will call our thread function printThreadId and define it later. This function will simply print a numeric identifier of the thread, which it will receive as input parameter.
The final input parameter of the pthread_create function is an argument that can be passed to our thread function. Thus, it needs to always be of type pointer to void (void *), which is the same type defined in the thread function prototype.
This is a generic type that we need to cast back to our argument data type inside the thread function. Thus, we can use this fourth argument of the pthread_create function to pass a pointer to a more complex data structure that holds all the parameters needed for the thread function.
If our thread function doesn’t need any input argument, we can simply pass NULL here.
In our case, we will pass an integer with the thread number, starting in 0 and finishing in 3 (we will create our threads in a for loop and this value will be the current iteration index).
Note that although our variable is an integer, we will need to cast it to (void*) when passing it to the pthread_create function, which is the data type it receives, as we have mentioned before.
Although typically we use this argument to pass pointers to variables, we can actually pass an integer by value since an integer has the same size as a pointer to void (4 bytes). You can confirm this by calling the sizeof function in both data types.
Thus, as long as in the thread function we cast the variable back to int, we can correctly obtain its value. Note however that this cannot be done if the size of the variable we want to pass to the thread is greater than 4 bytes (for example, a struct) and in that case we need to pass a pointer to our data structure.
If the thread is successfully created, the pthread_create function will return zero. Thus, we will store its value in the previously declared variable and check for errors.
You can check the full setup function below, which already includes the loop where the threads are created and the error checking.
void setup() {
Serial.begin(115200);
pthread_t threads[4];
int returnValue;
for( int i = 0; i< 4; i++ ) {
returnValue = pthread_create(&threads[i], NULL, printThreadId, (void *)i);
if (returnValue) {
Serial.println("An error has occurred");
}
}
}
To finalize, we will create our printThreadId function. As mentioned, it needs to respect a defined prototype, which corresponds to returning a pointer to void and receiving as input also a pointer to void.
Since we passed as fourth input of the pthread_create function an integer with the thread index, we will cast it back to int and print it to the serial port.
void *printThreadId(void *threadid) {
Serial.println((int)threadid);
}
Note that the lower level UART functions (implementation file here) write the content inside a critical zone protected by a mutex, so they should be thread safe.
Since we are launching threads, we don’t need the Arduino main loop function. Thus, it can be left empty. The final source code can be seen below.
#include <pthread.h>
void *printThreadId(void *threadid) {
Serial.println((int)threadid);
}
void setup() {
Serial.begin(115200);
pthread_t threads[4];
int returnValue;
for( int i = 0; i< 4; i++ ) {
returnValue = pthread_create(&threads[i], NULL, printThreadId, (void *)i);
if (returnValue) {
Serial.println("An error has occurred");
}
}
}
void loop() {}
Testing the code
To test the code, simply compile it and upload it to your ESP32 device. After that, open the IDE serial monitor and check the results.
You should get an output similar to figure 1, which shows the indexes of the 4 threads getting printed to the serial monitor.
Figure 1 – Output of the program.