ESP32 FreeRTOS Event Groups with ESP-IDF

In this ESP32 user guide, we will show you how to use FreeRTOS event groups for task synchronization. Often times we come across the situation where multiple tasks are running and task synchronization is required. This may include conjunction or disjunction of events or synchronization checks between different tasks. The FreeRTOS library in ESP-IDF provides APIs to create and monitor event groups. We will show you through an example project that we will create and build in VS Code with ESP-IDF extension whereby we will use freertos/event_groups.h to synchronize two tasks.

Before we move ahead, make sure you have the latest version of VS Code installed on your system with the ESP-IDF extension configured.

ESP32 FreeRTOS Event Groups with ESP-IDF

In this section, let us demonstrate the working of event groups using FreeRTOS library.

Create Project

Open your VS Code and head over to View > Command Palette. Type ESP-IDF: New Project in the search bar and press enter.

Specify the project name and directory. We have named our project ‘ESP_IDF_EVENT_GROUPS.’ For the ESP-IDF board, we have chosen the custom board option. For ESP-IDF target, we have chosen ESP32 module. Click ‘Choose Template’ button to proceed forward.

ESP32 Event Groups using ESP-IDF

In the Extension, select ESP-IDF option:

ESP-IDF in VS Code New Project 2

We will click the ‘sample_project’ under the get-started tab. Now click ‘Create project using template sample_project.’

ESP-IDF in VS Code New Project 3

You will get a notification that the project has been created. To open the project in a new window, click ‘Yes.’

This opens our project that we created inside the EXPLORER tab. There are several folders inside our project folder. This is the same for every project which you will create through ESP-IDF Explorer.

ESP32 FreeRTOS Event Groups Code

The main folder contains the source code meaning the main.c file will be found here. Go to main > main.c and open it. Copy the code given below in your main.c file and save it.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/event_groups.h"

EventGroupHandle_t event_group;
const int got_temp = BIT0;
const int got_hum = BIT1;

void Get_Temperature(void *params)
{
  while (true)
  {
    
    xEventGroupSetBits(event_group, got_temp);
    printf("Temparture value received\n");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

void Get_Humidity(void *params)
{
  while (true)
  {
    xEventGroupSetBits(event_group, got_hum);
    printf("Humidity value received\n");
    vTaskDelay(5000 / portTICK_PERIOD_MS);
  }
}

void Sender(void *params)
{
  while (true)
  {
    xEventGroupWaitBits(event_group, got_temp | got_hum, true, true, portMAX_DELAY);
    printf("Sender: Received temperature and Humidity\n");
  }
}

void app_main(void)
{
  event_group = xEventGroupCreate();
  xTaskCreate(&Get_Temperature, "Get_Temperature", 2048, NULL, 1, NULL);
  xTaskCreate(&Get_Humidity, "Get_Humidity", 2048, NULL, 1, NULL);
  xTaskCreate(&Sender, "Sender", 2048, NULL, 1, NULL);
}

How the Code Works?

Firstly, we will start by including the necessary libraries that includes the FreeRTOS libraries to generate delays, create tasks and event groups.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "freertos/event_groups.h"

Next, we declare a variable ‘event_group’ to hold the created FreeRTOS event group. This FreeRTOS event group is used to signal when the event occurs.

EventGroupHandle_t event_group;

We have two tasks, Get_Temperature and Get_Humidity whose work is to print a message after every few seconds. The Get_Temperature prints “Temperature value received” after every second and the Get_Humidity prints “Humidity value received” after every 5 seconds when their event bits are set.

We define two event bits/flags for the events. These include the bit for got temperature and another bit for got humidity. They will help identify if a particular event occurred or not.

const int got_temp = BIT0;
const int got_hum = BIT1;

Inside the Get_Temperature() function, we call xEventGroupSetBits() to set bits within the event group. This function takes in two parameters. The first parameter is the event group handle whose bits are to be set, ‘event_group’ in our case. The second parameter is the bitwise value which is to be set, ‘got_temp’ in our case. We had previously defined it as ‘BIT0.’ After setting the bit, a message will be printed that will indicate that the temperature value is received. After, that there will be a delay of 1 second.

void Get_Temperature(void *params)
{
  while (true)
  {
    xEventGroupSetBits(event_group, got_temp);
    printf("Temperature value received\n");
    vTaskDelay(1000 / portTICK_PERIOD_MS);
  }
}

Similarly, inside the Get_Humidity() function, we call xEventGroupSetBits() to set bits within the event group. The first parameter is the event group handle whose bits are to be set, ‘event_group’ in our case. The second parameter is the bitwise value which is to be set, ‘got_hum’ in our case. We had previously defined it as ‘BIT1.’ After setting the bit, a message will be printed that will indicate that the humidity value is received. After, that there will be a delay of 5 seconds.

void Get_Humidity(void *params)
{
  while (true)
  {
    xEventGroupSetBits(event_group, got_hum);
    printf("Humidity value received\n");
    vTaskDelay(5000 / portTICK_PERIOD_MS);
  }
}

Inside the Sender() function, xEventGroupWaitBits() function is called. This function waits for one or more bits to be set within a previously created event group. It takes in four parameters.

  • The first parameter is the event group handle which is to be tested. It is set as ‘event_group’ in our case.
  • The second parameter is the bitwise value that indicates the bit/bits to test inside the event group. It is set as ‘got_temp | got_hum’ in our case.
  • The third parameter is ‘xClearOnExit’ which can take either of the two values: pdTRUE or pdFALSE. If it is set to pdTRUE then any bits set in the event group will be cleared before the function returns. If pdFALSE is set then the bits set in the event group are not changed. It is set as ‘true’ in our case.
  • The fourth parameter is ‘xWaitForAllBits’ which can also take either of the two values: pdTRUE and pdFALSE. If it is set to set to pdTRUE then the function returns when either all the bits are set or the maximum block time is over. If it is set to pdFALSE then the function returns when either any of the bit is set or the maximum block time is over. It is set as ‘true’ in our case.
  • The last parameter is ‘xTicksToWait’which is the maximum block time. It is set as ‘portMAX_DELAY’ in our case.

The Sender() function waits for the two events to occur and then prints the message “Sender: Received temperature and Humidity” in the terminal.

void Sender(void *params)
{
  while (true)
  {
    xEventGroupWaitBits(event_group, got_temp | got_hum, true, true, portMAX_DELAY);
    printf("Sender: Received temperature and Humidity\n");
  }
}

app_main()

Inside the app_main() function, we first create the event group using the function xEventGroupCreate(). This function returns the event group handle which is ‘event_group’ in our case.

 event_group = xEventGroupCreate();

After the event group is created, we create the tasks for Get_Temperature, Get_Humidity and Sender. To create a task we use the function xTaskCreate(). This function takes in several arguments.

  • The first argument is the name of the function.
  • The second argument is the name of the task for descriptive purposes.
  • The third argument specifies the stack size of the task. This indicates the amount of memory we want to reserve for the particular task.
  • The fourth argument is the parameter. It is a value that is passed as the parameter to the created task. If this property is not being used, then it is set as NULL.
  • The fifth argument is the priority of the task.
  • The last argument is the handle which is used to change the function of the task eg. suspend, delete, resume, get or set a config of task. It is optional so it can be set as NULL.
  xTaskCreate(&Get_Temperature, "Get_Temperature", 2048, NULL, 1, NULL);
  xTaskCreate(&Get_Humidity, "Get_Humidity", 2048, NULL, 1, NULL);
  xTaskCreate(&Sender, "Sender", 2048, NULL, 1, NULL);

Demonstration

To flash your chip, type the following command in the serial terminal. Remember to replace the COM port with the one through which your board is connected.

idf.py -p COMX flash monitor

After the code flashes successfully, this is the output that gets printed in the serial terminal. The Get_Temperature task prints “Temperature value received” after every second, the Get_Humidity task prints “Humidity value received” after every 5 seconds and the Sender task prints “Sender: Received temperature and Humidity” once both the events occur.

ESP32 Event Groups using ESP-IDF Terminal

You may also like to read:

Leave a Comment