ESP32 Parallel Programming

As it is known, some of the microcontrollers, in order to increase performance provide more than one core. ESP32 is one of them providing two physical cores. In practice, it means that the program developed can run simultaneously on both cores. Thereby it is possible to optimize some of the tasks in a way that they are not waiting for each other but running in parallel instead. This is the main advantage of parallel programming comparing to a sequential one. However, it requires both dedicated program control structures and hardware support.

At the time while this chapter is being written, the simplest way of developing a parallel code on ESP32 is via using FreeRTOS™ [1], which is a widely used real-time library for different microcontrollers. The RTOS allows using most of the real-time and parallel programming features including semaphores, process assignments to cores and more. The following code chunks explain how to apply the most useful parallel programming features.

Let's start with an example of blinking LED and Text output (based on material found here [2]).

The first task is task1, that simply outputs a string “Hi there!” to default serial port with delay of 100 ms, i.e. 10 times per second:

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "driver/gpio.h"
 
#define BLINK_GPIO 13
 
void task1_SayHi(void * parameters)
{ 
   while(1)
   {
     printf("Hi there!\n");
     vTaskDelay(100 / portTICK_RATE_MS);
   }
}
  • #include “freertos/FreeRTOS.h” and #include “freertos/task.h” – adds needed libraries of FreeRTOS™.
  • #define BLINK_GPIO 13 – defines output pin that will be used to switch on or off the LED.
  • portTICK_RATE_MS refers to constant portTICK_PERIOD_MS that is used to calculate real-time from the tick rate – with the resolution of one tick period.

The second task is to bilk a LED with a period of 2 seconds (1 second on, 1 second off):

void task2_BlinkLED(void * parameters)
{
 
    gpio_pad_select_gpio(BLINK_GPIO);
    gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
    while(1) {
        /*Sets the LED low for one second*/
        gpio_set_level(BLINK_GPIO, 0);
        vTaskDelay(1000 / portTICK_RATE_MS);
 
        /*Sets the LED high for one second*/
        gpio_set_level(BLINK_GPIO, 1);
        vTaskDelay(1000 / portTICK_RATE_MS);
    }
}

Once both task functions are defined, they can be executed simultaneously:

void app_main()
{
    nvs_flash_init();
    xTaskCreate(&task1_SayHi, "task1_SayHi", 2000, NULL, 5, NULL);
    xTaskCreate(&task2_BlinkLED, "task2_BlinkLED", 2000,NULL,5,NULL );
}
  • nvs_flash_init() – initializes a non-volatile memory in flash memory, so it can be used by concurrent tasks
  • xTaskCreate – creates a task, without specifying a core, on which it is executed, with rather low priority (5). More on parameters can be found here [3].
In fact, the code does not run in parallel physically, it uses the full speed of the ESP32 that is far beyond human perception speed and shares the computation time between both tasks. Therefore for the human, it seems to be running in parallel. Each of the tasks uses Idle (defined by vTaskDelay()) time of the other task. Since both are simply the time slot is enough to complete.

To run the code physically in parallel it is necessary to assign task explicitly to the particular core, which requires a slight modification of the main() function:

void app_main()
{
    nvs_flash_init();
    xTaskCreatePinnedToCore(&task1_SayHi, "task1_SayHi", 2000, NULL, 5, NULL,0);
    xTaskCreatePinnedToCore(&task2_BlinkLED, "task2_BlinkLED", 2000,NULL,5,NULL,1);
}
  • xTaskCreatePinnedToCore – creates a task and assigns it to the particular core, on which it is executed. In this case, task1_SayHi() is assigned to core 0, while task2_BlinkLED() to core 1. For more information refer to [4].

While ESP32 provide two computing nodes, other devices like particular serial port or other peripherals are only single devices. In some cases, it might be needed to access those devices by multiple processes in a way that does not disturb the others. In a terminology of parallel programming, those “single” devices are called resources that need to be shared or simply shared resources. To share a resource it is necessary to have a signal that is available to all processes and that determines if the resource is available or not. Those signals are dedicated data structures and are called - semaphores. Depending on the particular platform they might represent a different data structure to address particular use case. RTOS support three main semaphore types – Binary (True/False), Counting (represents a queue) and Mutex (binary semaphore with priority). More details on each type and use examples might be found here [5]. To explain the concept of resource sharing here a simple binary-semaphore example is provided. Example uses two SayHi tasks to share the same output device:

Since we need to define a semaphore at the beginning a setup function is also needed:

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "driver/gpio.h"
 
SemaphoreHandle_t xSemaphore = NULL;
 
void setup()
{
    vSemaphoreCreateBinary( xSemaphore );
}

Now it is possible to define the task functions and modify them in a way they use the same resource

void task1_SayHi(void * parameters)
{ 
   while(1)
   {
       /*check and waits for semaphore to be released for 100 ticks. 
       If the semaphore is available it is taken / blocked */
       if( xSemaphoreTake( xSemaphore, ( TickType_t ) 100 ) == pdTRUE )
       {
          printf("TASK1: Hi there!\n");
          vTaskDelay(100 / portTICK_RATE_MS);
          xSemaphoreGive( xSemaphore );
       }
       else
       {
           //Does something else in case the semaphore is not available
       } 
   }
}
 
void task2_SayHi(void * parameters)
{ 
   while(1)
   {
       /*check and waits for semaphore to be released for 100 ticks. 
       If the semaphore is available it is taken / blocked */
       if( xSemaphoreTake( xSemaphore, ( TickType_t ) 100 ) == pdTRUE )
       {
          printf("TASK2: Hi there!\n");
          vTaskDelay(100 / portTICK_RATE_MS);
          xSemaphoreGive( xSemaphore );
       }
       else
       {
           //Does something else in case the semaphore is not available
       } 
   }
}

Now both of the tasks are ready to be executed on the same or different cores as explained previously.

Please note that semaphore mechanism is a powerful tool to synchronize tasks, prioritize tasks or simply make sure that a single resource is used properly in a multi-task application.
en/iot-open/getting_familiar_with_your_hardware_rtu_itmo_sut/esp/esp_parallel_programming.txt · Last modified: 2020/07/20 09:00 by 127.0.0.1
CC Attribution-Share Alike 4.0 International
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0