ESP32: FreeRTOS counting semaphores

The objective of this post is to provide an introduction to FreeRTOS  counting semaphores, using the ESP32 and the Arduino support.


Introduction

The objective of this post is to provide an introduction to FreeRTOS counting semaphores, using the ESP32 and the Arduino support. We assume a previous installation of the ESP32 support for the Arduino environment.

We will develop a simple application where we will use a counting semaphore as an execution barrier. Note that this is just a simple getting started example and we can implement this kind of barriers more efficiently using other mechanisms, such as task notifications [1].

Semaphores are typically used for both synchronization and mutual exclusion [2] in access to resources. I will leave some more content on semaphores on the related content section. In our example program we are going to use FreeRTOS counting semaphores.

So, in our program, we will have the setup function launching a configurable amount of tasks, and then it will wait on a semaphore for all the tasks to finish. This coordination will be achieved using the previously mentioned counting semaphores.

So, after launching the tasks, the setup function will try to hold a semaphore as many times as tasks launched, and only continue execution after that point. Note that, when a task tries to take a semaphore without units (semaphore counter equal to 0), it will block until the semaphore count is incremented by other task or a defined timeout occurs.


The code

We will start our code by declaring a global variable to hold the number of tasks to launch. This will give some more flexibility to our use case. We will perform our example with 4 tasks, but you can change it to another number.

Then, we will declare a counting semaphore as a global variable, so it can be accessed by both the setup function and the tasks. We create the semaphore with the xSemaphoreCreateCounting function.

This function receives as input arguments the maximum count the semaphore can achieve and the initial value  [3]. Since we are using this for synchronization purposes, we will specify a maximum count equal to the number of tasks to launch, and initialize it at 0.

int nTasks=4;
SemaphoreHandle_t barrierSemaphore = xSemaphoreCreateCounting( nTasks, 0 );

Next, on the setup function, we will start by opening the serial connection and printing a simple debug message, so we now the point of execution of the code.

Next, we will do a loop to launch our tasks. Note that we can use the same task function to launch multiple tasks, so we don’t need to implement code for every new task. We will check the code for the functions latter.

Note that we will create the tasks pinned to core 1 of the ESP32. This is because, as seen in this previous post, the setup function is pinned to core 1. It will be easier for us to understand the functioning of the semaphores without introducing the complexity of multi core execution. If you need more information about launching tasks pinned to a specific ESP32 core, check this previous tutorial. If you need help on the basics and arguments needed for launching a FreeRTOS task, check this tutorial.

Also, we will create the tasks with priority 0. We will do this because, as seen in this tutorial, the setup function runs with a priority of 1 (remember, higher numbers mean higher priorities). So, since the tasks will have lower priority, they will only run if the setup function blocks somewhere, and we expect it to block on our semaphore.

Just for the sake of differentiating the tasks, we will pass the number of the iteration of the loop as an input parameter, so they can print it in their code. Check this previous tutorial if you need more information about passing parameters to FreeRTOS tasks.

Update: In the original code, we were passing the variable by reference, which was causing concurrency problems (thanks to gecame for the warning). We will be passing the task argument by value.

Serial.begin(112500);
delay(1000);

Serial.println("Starting to launch tasks..");

int i;

for(i= 0; i< nTasks; i++){
    xTaskCreatePinnedToCore(
                    genericTask,    /* Function to implement the task */
                    "genericTask",  /* Name of the task */
                    10000,          /* Stack size in words */
                    (void *)i,      /* Task input parameter */
                    0,              /* Priority of the task */
                    NULL,           /* Task handle. */
                    1);             /* Core where the task should run */
}

After launching the tasks, we will do a for loop trying to get the semaphore as many times as the tasks launched. So, the setup function should only pass from that execution point when there are as many units in the semaphore as there are tasks. Since there will be the tasks incrementing  the semaphore, we should guarantee that the Arduino setup function will only finish after all the tasks finish.

To take a unit from a semaphore, we will call the xSemaphoreTake function. This function receives as first argument the handle of the semaphore (the global variable we declared and initialized in the beginning) and a maximum number of FreeRTOS ticks to wait [4]. Since we want it to block indefinitely until it can get the semaphore, we pass to this last argument the value portMAX_DELAY [4].

After that, we will execute a final message, indicating the setup function has passed that execution point.

for(i= 0; i< nTasks; i++){
    xSemaphoreTake(barrierSemaphore, portMAX_DELAY);
}

Serial.println("Tasks launched and semaphore passed...");

This finishes the setup function. Now we will implement the main loop where, just as an example, we will use a function called vTaskSuspend to suspend the main loop task. This will block its execution no matter what is its priority [5]. This function receives as input the handle of the task to be suspended [5]. In this case, since we want to suspend the calling task, we pass NULL [5].

void loop() {
  vTaskSuspend(NULL);
}

Finally, we will implement the code for our tasks. They will simply start, print their number (from the argument passed when they were launched), increment the semaphore and then finish.

So, we start by building and printing the string indicating the number of the task. Then, we will increment the semaphore using the xSemaphoreGive function. This function receives as input the handle for the semaphore [6]. Note that there is also a xSemaphoreGiveFromISR to be used in the context of an Interrupt Service Routine.

Check the code for the function bellow, which already includes the call to delete the task after the execution.

void genericTask( void * parameter ){

    String taskMessage = "Task number:";
    taskMessage = taskMessage + ((int)parameter);

    Serial.println(taskMessage);

    xSemaphoreGive(barrierSemaphore); 

    vTaskDelete(NULL);
}

Check the full source code for this tutorial bellow.

int nTasks=4;
SemaphoreHandle_t barrierSemaphore = xSemaphoreCreateCounting( nTasks, 0 );

void genericTask( void * parameter ){

    String taskMessage = "Task number:";
    taskMessage = taskMessage + ((int)parameter);

    Serial.println(taskMessage);

    xSemaphoreGive(barrierSemaphore); 

    vTaskDelete(NULL);
}

void setup() {

  Serial.begin(112500);
  delay(1000);

  Serial.println("Starting to launch tasks..");

  int i;

  for(i= 0; i< nTasks; i++){
    xTaskCreatePinnedToCore(
                    genericTask,    /* Function to implement the task */
                    "genericTask",  /* Name of the task */
                    10000,          /* Stack size in words */
                    (void *)i,      /* Task input parameter */
                    0,              /* Priority of the task */
                    NULL,           /* Task handle. */
                    1);             /* Core where the task should run */
  }    

  for(i= 0; i< nTasks; i++){
    xSemaphoreTake(barrierSemaphore, portMAX_DELAY);
  }

  Serial.println("Tasks launched and semaphore passed...");
}

void loop() {
  vTaskSuspend(NULL);
}


Testing the code

To test the code, just upload it to your ESP32 board using the Arduino IDE. Then open the serial monitor to check the results. You should get an output similar to figure 1.

ESP32 FreeRTOS counting semaphores

Figure 1 – Output of the semaphore synchronization program.

Note that although the setup function as a higher priority than any of the launched tasks, it only finished the execution (printing the “Tasks launched and semaphore passed…” message) after all the tasks executed their code (printing their number).

Just as an example, figure 2 shows the output if we comment the semaphore obtaining code in the Arduino setup function.

ESP32 FreeRTOS no synchronization.png

Figure 2 – Execution of the code without semaphore synchronization.

Note that now the setup function executes all of its code first, since it has higher priority and never blocks. The tasks will only be able to run after the setup finishes (remember that we suspended the execution of the main loop so, although it has higher priority, it gives away the CPU).

Besides no longer having the desired synchronization, we can see that the tasks are now outputting the wrong task number. This is because they are accessing the parameter after the setup function end. So, they are accessing a memory position no longer valid since the iteration variable was a local variable to the setup function.


Related content


Related posts

 

References

[1] http://www.freertos.org/a00124.html

[2] http://www.freertos.org/Embedded-RTOS-Binary-Semaphores.html

[3] http://www.freertos.org/CreateCounting.html

[4] http://www.freertos.org/a00122.html

[5] http://www.freertos.org/a00130.html

[6] http://www.freertos.org/a00123.html

Advertisements

5 Replies to “ESP32: FreeRTOS counting semaphores”

  1. Dear Nuno,
    many thanks for your very nice tutorials – they are extremely useful for getting acquainted with RTOS on the ESP32. A comment on your example above: there is a concurrency problem in the task creation loop. The variable “i” that will be used for the task ID is passed as a reference to each task. If task creation and execution is slower than the loop, the value of i may have incremented by the time the task reads it out. The more threads are created, the higher the chance you’ll hit this problem (try your example with say 20-40 threads, you will end up with many threads having the same task ID). You can avoid this problem by e.g. pre-creating an array with all task IDs and then pass each task ID as a reference to xTaskCreate.
    cheers

    Liked by 1 person

    1. Hi!

      Thank you very much for your feedback, I’m very happy to know that the tutorials are being useful to you 🙂

      You are right, there’s actually a concurrency problem I haven’t noticed, thanks for the warning.

      I will change it to pass the variable by value rather than by reference to the tasks, which should be the simpler solution.

      Actually, I don’t recall why I have passed the address of the iterator variable, which as not very smart since it was going to be changed by each iteration of the loop xD

      Note however that although this was not the original intent, the code should have worked the same way because of the following:
      – All tasks receive a reference to the same variable, i
      – The second loop of the setup function, where we make the semaphoreTake calls, resets i to 0.
      – As soon as the setup makes the semaphoreTake call, it is preempted and a thread starts executing with the current value of i
      – The thread prints the current value of i
      – As soon as that thread adds a unit to the semaphore, it is preempted, thus waking the setup.
      – The setup increments i and blocks again in a new semaphoreTake call
      – A new thread wakes with the new value of i and prints its value, then adds a unit to the semaphore, waking the setup again, and so on

      Nonetheless, as you mentioned, if we increase the number of threads to 20, it indeed shows repeated IDs.

      I think that this is happening because of the serial.println function being called before incrementing the semaphore.

      I haven’t analyzed the whole implementation of the Serial.print function, but I think it may have some call in it that may trigger a thread context switch, which I think is why threads start having repeated IDs.

      So, in the previous sequence, if the serial.print blocked the thread before incrementing the semaphore, then the setup wouldn’t wake and another thread would execute, still using the same value of i.

      I did not have the chance to analyze this much, but it seems to be the case because, in the original code, if I leave the string concatenation where it is and move the Serial.print function to after the sempahoreGive call, then it will print a distinct ID for each thread (unordered).

      But this is just a guess, unfortunately I did not have time to investigate the whole Serial print implementation. But does this make sense to you?

      Nonetheless, I will change the code to pass the IDs by value, since that will definitively avoid any problems 🙂

      Thanks again for the warning, it is very good to have people to discuss this with. Concurrency is always a pain and it is very easy to miss these situations 🙂

      Best regards,
      Nuno Santos

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s