Using the multitasking capabilities of the ESP32 / FreeRTOS
One thing that I wanted to learn more about is the multitasking capabilities of the ESP32. I digged into the ESP-IDF manuals and found helpful information about the FreeRTOS SMP and how it works.
What I found then is that if you program the ESP32 using the Arduino core for ESP32 as your framework, then it is quite simple. Then loop() is running as a task already in parallel to others that handle WiFi, BLE, …
To split your app tasks into several tasks, there are several functions to start a task available. The two most useful I found are
According to the documentation the difference is that xTaskCreate() adds a new task to the existing list of task, but leaves it to the task scheduler to decide on which CPU core the task will run. And (as the function call already suggests) xTaskCreatePinnedToCore() does the same, but tells the task scheduler which CPU core should run the task.
Why is it important on which CPU core the tasks will run on? According to FreeRTOS SMP :
However the ESP32 is dual core containing a Protocol CPU (known as CPU 0 or PRO_CPU) and an Application CPU (known as CPU 1 or APP_CPU).
CPU 0 might be very busy if you have a lot of WiFi communication or BLE activities, so it might be better to pin them to CPU 1 to make sure they get enough runtime to do what they should do.
Looking on my application I then tried to decide which “tasks”, that were handled in the main loop should be moved into separate tasks. The candidates I found where
- MQTT client
- Light measurement
- Temperature and humidity measurement
- Getting weather information from Weather Underground
Why move this 4 “tasks” in “real” FreeRTOS tasks and not handle them in the loop(), as we are all used to from old Arduino programming times?
The MQTT client I want to have in a separate task, because sending status messages to the MQTT server might be time consuming and depends on the quality and availability of the internet connection. I didn’t want the loop() to hang while a MQTT message is sent (and might fail to send because the server is down).
The light and temperature measurements are called in a fixed frequency. For this I could have just used a simple time-elapsed check in the loop(), but hey, we want to try out the features of ESP32 and FreeRTOS, so a time-elapsed–now-do-something approach seems to be inappropriate.
And getting the weather information from the internet can have the same problems as the MQTT client. The connection could be slow, the server could be down.
IMHO, when programming under FreeRTOS, the loop() should basically be very empty and the activities should be split into time or event triggered tasks.
A few things to keep in mind.
- Using the Arduino framework, at the time we can create tasks in setup() the task scheduler is already active. So the task that is created will start to run immediately. This might be not the best thing, as the initialization might not be finished and the task might fail to perform what it should do.
- If using OTA updates, it is crucial to stop all created tasks to make sure they do not interrupt or slow down the OTA data transfer.
For the delayed start of the tasks, I use a global boolean flag tasksEnabled. This flag is false at startup. When a task it started, before doing anything, it checks if this flag is true. If not, the tasks sends herself immediately into sleep.
To stop the tasks once an OTA process has started, I use another global boolean flag otaRunning. This flag is set true when the OTA process started. Each of the tasks checks this flag on each run. If the tasks find this flag true, the task suspends herself.
On the next page I show how I create and control the time triggered tasks.