In this ESP32 ESP-IDF FreeRTOS tutorial, we will learn to use FreeRTOS with ESP32 using ESP-IDF framework. The good thing about ESP-IDF is that it already has a port of FreeRTOS for ESP32. Therefore, we do not need to install additional libraries and drivers. ESP-IDF FreeRTOS is based on Vanilla FreeRTOS v10.4.3
In this tutorial, we will learn to create FreeRTSO tasks such as task creation, deletion, priority setting, Task interrupts, etc. When using ESP-IDF, the majority of the codes use the functionalities of Vanilla FreeRTOS v10.4.3, therefore it is an important aspect while programming. Let’s start by showing you how to create and delete tasks and then move towards FreeRTOS Interrupt 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.
Components Required:
ESP32 Development board: You can buy from here.
FreeRTOS Tasks IntroductionIn a real-time application or RTOS, an application usually consists of a set of independent tasks or subroutine. On a single-core MCU, only one task can run at a time. On the other hand, in a dual-core processor such as ESP32 two tasks can run concurrently given these two tasks have no dependency on each other. FreeRTOS scheduler schedules these tasks based on their priority, time period, and execution time.
The FreeRTOS scheduler will frequently start and stop every task as the application keeps executing. A task does not have an understanding of the RTOS scheduler activity, therefore, it is the responsibility of the FreeRTOS scheduler to confirm that the processor context (register values, stack contents, etc) when a task is switched in is exactly that as when the same task was swapped out. For this purpose, each task is provided with its own stack. When the task is swapped out the execution context is saved to the stack of that task so it can also be exactly restored when the same task is later swapped back in. You can find more information about FreeRTOS tasks here.
FreeRTOS APIs provide features to schedule, create, delete, suspend, resume, and setting tasks priority. We will see examples of each of these throughout this tutorial.
ESP32 ESP-IDF FreeRTOS Create Task ExampleOpen your VS Code and create a new ESP-IDF project. Now head over to the main.c file. We will define the functions and the program code here.
To start off, include the FreeRTOS header files. The freertos/task.h library will enable us to use the FreeRTOS tasks.
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
xTaskCreate() for first task
Inside the main() function, we will create the tasks. To create a task, use the function xTaskCreate(). This function takes in several arguments.
xTaskCreate(Demo_Task, "Demo_Task", 4096, NULL, 10, &myTaskHandle);
Task Handle
The task handle will be defined as a global variable outside the main function. We have set it as NULL.
TaskHandle_t myTaskHandle = NULL;
Function of first task
Now we will create the function definition of Demo_Task. Here inside the infinite while loop we are printing a message on the serial terminal after every second. You have to use an infinite loop otherwise the microcontroller will keep on resetting.
void Demo_Task(void *arg)
{
while(1){
printf("Demo_Task printing..\n");
vTaskDelay(1000/ portTICK_RATE_MS);
}
}
xTaskCreatePinnedToCore() for second task
Similarly, let us create another task Demo_Task2 and define its function so that we can see both the tasks running simultaneously. To do that we will use the xTaskCreatePinnedToCore() function instead. It will enable the user to select which ESP32 core (core 0 or core 1) will run the particular task. Let us select core1 for the second task. The xTaskCreatePinnedToCore() takes in one extra argument as compared to xTaskCreate() function. It is the seventh argument that indicates the core that will be used for the created task. The user can specify ‘0’ or 1′ as an argument.
Let us select core1 for the second task which is Demo_Task2.
xTaskCreatePinnedToCore(Demo_Task2, "Demo_Task2", 4096, NULL,10, &myTaskHandle2, 1);
Similarly, we define the task handle for the second task as shown below:
TaskHandle_t myTaskHandle2 = NULL;
This is the function definition for this task.
void Demo_Task2(void *arg)
{
while(1){
printf("Demo_Task2 printing..\n");
vTaskDelay(1000/ portTICK_RATE_MS);
}
}
Complete Code & Demonstration
This is the complete code that we are using. Here we have created two tasks which will run simultaneously. Demo_Task1 prints “Demo_Task printing..” and Demo_Task2 prints “Demo_Task printing..” in the serial terminal.
#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
TaskHandle_t myTaskHandle = NULL;
TaskHandle_t myTaskHandle2 = NULL;
void Demo_Task(void *arg)
{
while(1){
printf("Demo_Task printing..\n");
vTaskDelay(1000/ portTICK_RATE_MS);
}
}
void Demo_Task2(void *arg)
{
while(1){
printf("Demo_Task2 printing..\n");
vTaskDelay(1000/ portTICK_RATE_MS);
}
}
void app_main(void)
{
xTaskCreate(Demo_Task, "Demo_Task", 4096, NULL, 10, &myTaskHandle);
xTaskCreatePinnedToCore(Demo_Task2, "Demo_Task2", 4096, NULL,10, &myTaskHandle2, 1);
}
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
The serial terminal will display the messages as our tasks keep on running infinitely.
ESP32 ESP-IDF FreeRTOS Delete Task ExampleNow let us modify the above code to show you the delete task functionality.
Look at the modified Demo_Task2() function below. Instead of using an infinite while loop, we have incorporated a for loop that will print the message after every second. This loop will finish after 5s thus, the ESP32 will reset at that point.
void Demo_Task2(void *arg)
{
for(int i=0;i<5;i++){
printf("Demo_Task2 printing..\n");
vTaskDelay(1000/ portTICK_RATE_MS);
}
}
Let us delete this task after 5s so that the ESP32 will not reset. To delete a task we use the vTaskDelete() function and specify the handle of the task to be deleted as a parameter inside it. If we pass NULL, then will cause the calling task to be deleted.
vTaskDelete(myTaskHandle2)
Inside the Demo_Task() function, first we will create an integer ‘count’ that will hold the number of seconds. When count reaches 3 then we will call the vTaskDelete() function and specify the handle of Demo_Task2 as a parameter inside it. This function will be called inside the Demo_Task() function hence, the first task will delete the second task after 5s. This way the ESP32 will not reset and the first task keeps on running.
void Demo_Task(void *arg)
{
int count = 0;
while(1){
count++;
printf("Demo_Task printing..\n");
vTaskDelay(1000/ portTICK_RATE_MS);
if (count == 3)
{
vTaskDelete(myTaskHandle2);
printf("Demo_Task2 is deleted!\n");
}
}
}
Complete Code & Demonstration
Below you can access the complete code in which we are demonstrating the vDeleteTask() function.
#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
TaskHandle_t myTaskHandle = NULL;
TaskHandle_t myTaskHandle2 = NULL;
void Demo_Task(void *arg)
{
int count = 0;
while(1){
count++;
printf("Demo_Task printing..\n");
vTaskDelay(1000/ portTICK_RATE_MS);
if (count == 5)
{
vTaskDelete(myTaskHandle2);
printf("Demo_Task2 is deleted!\n");
}
}
}
void Demo_Task2(void *arg)
{
for(int i=0;i<5;i++){
printf("Demo_Task2 printing..\n");
vTaskDelay(1000/ portTICK_RATE_MS);
}
}
void app_main(void)
{
xTaskCreate(Demo_Task, "Demo_Task", 4096, NULL, 10, &myTaskHandle);
xTaskCreatePinnedToCore(Demo_Task2, "Demo_Task2", 4096, NULL,10, &myTaskHandle2, 1);
}
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
The output of the serial terminal is shown below. Notice that Demo_Task2 ran for 5 seconds and then it was deleted. Demo_Task continues to run infinitely without the ESP32 resetting.
ESP32 ESP-IDF FreeRTOS Suspend & Resume Task ExampleTo suspend a task we use the vTaskSuspend() function and specify the handle of the task to be suspended as a parameter inside it. If we pass NULL, then will cause the calling task to be suspended
vTaskSuspend(myTaskHandle2);
To resume a task we use the vTaskResume() function and specify the handle of the task to be resumed as a parameter inside it. If we pass NULL, then will cause the calling task to be resumed.
vTaskResume(myTaskHandle2);
Code Modifications
First, we will modify the code above to increase the limit of Demo_Task2 to 10 seconds.
void Demo_Task2(void *arg)
{
for(int i=0;i<10;i++){
printf("Demo_Task2 printing..\n");
vTaskDelay(1000/ portTICK_RATE_MS);
}
}
Then, inside the Demo_Task() function, we will suspend Demo_Task2 after 5 seconds, then resume it after 3 seconds. After 10 seconds it will be deleted so that the ESP32 does not reset.
void Demo_Task(void *arg)
{
int count = 0;
while(1){
count++;
printf("Demo_Task printing..\n");
vTaskDelay(1000/ portTICK_RATE_MS);
if (count == 5)
{
vTaskSuspend(myTaskHandle2);
printf("Demo_Task2 is suspended!\n");
}
if (count == 8)
{
vTaskDelete(myTaskHandle2);
printf("Demo_Task2 is resumed!\n");
}
if (count == 10)
{
vTaskDelete(myTaskHandle2);
printf("Demo_Task2 is deleted!\n");
}
}
}
Complete Code & Demonstration
Below you can access the complete code in which we are demonstrating the vSuspendTask(), vResumeTask() and vDeleteTask() functions
#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
TaskHandle_t myTaskHandle = NULL;
TaskHandle_t myTaskHandle2 = NULL;
void Demo_Task(void *arg)
{
int count = 0;
while(1){
count++;
printf("Demo_Task printing..\n");
vTaskDelay(1000/ portTICK_RATE_MS);
if (count == 5)
{
vTaskSuspend(myTaskHandle2);
printf("Demo_Task2 is suspended!\n");
}
if (count == 8)
{
vTaskResume(myTaskHandle2);
printf("Demo_Task2 is resumed!\n");
}
if (count == 10)
{
vTaskDelete(myTaskHandle2);
printf("Demo_Task2 is deleted!\n");
}
}
}
void Demo_Task2(void *arg)
{
for(int i=0;i<10;i++){
printf("Demo_Task2 printing..\n");
vTaskDelay(1000/ portTICK_RATE_MS);
}
}
void app_main(void)
{
xTaskCreate(Demo_Task, "Demo_Task", 4096, NULL, 10, &myTaskHandle);
xTaskCreatePinnedToCore(Demo_Task2, "Demo_Task2", 4096, NULL,10, &myTaskHandle2, 1);
}
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
The output of the serial terminal is shown below. Notice that Demo_Task2 ran for 5 seconds and then it was suspended for three seconds. It got resumed after the three seconds were over. After 10 seconds, the Demo_Task2 get deleted but Demo_Task continues to run infinitely without the ESP32 resetting.
Get Tick CountIn order to calculate the time of the application we use the function, xTaskGetTickCount(). This function returns us the number of ticks since the application started.
ESP32 ESP-IDF FreeRTOSInterruptNow let us look at how to use FreeRTOS interrupts. For demonstration, we will incorporate the interrupt to toggle a LED connected with the ESP32 board with a pushbutton.
ESP-IDF provides a gpio.h library that provides helpful functions to control the digital input and output pins of ESP32 and to generate interrupts. For this project, we want to successfully setup an interrupt to toggle the LED state.
The first step is to include the header file:
#include "driver/gpio.h"
To set the GPIO interrupt trigger type, we use the function, gpio_set_intr_type() which takes in two arguments. The first argument is the GPIO number whose trigger type we want to set. The second argument is the interrupt type.
gpio_set_intr_type(gpio_num_t gpio_num, gpio_int_type_t intr_type)
Next, to install the driver’s GPIO ISR handler service, we use the function, gpio_install_isr_service(). This handler service permits GPIO interrupt handlers per pin. This function takes in a single argument which is the flag that is responsible for allocating the interrupt.
gpio_install_isr_service(int intr_alloc_flags)
To add the ISR handler for a particular GPIO pin, we use the function, gpio_isr_handler_add(). This takes in three arguments. The first argument is the GPIO number, the second argument is the ISR handler for that particular GPIO and the last argument is the parameter for the ISR handler.
gpio_isr_handler_add(gpio_num_t gpio_num, gpio_isr_t isr_handler, void *args)
Interrupt using a Push Button to toggle LED Schematic diagram
Let us now demonstrate how to generate interrupts in the ESP32 board using a push button to toggle an LED. The push button will be connected to an interrupt pin of ESP32 and configured as an input. Whereas the LED will be set up as a digital output. The LED will be toggled on each positive edge (0 to 1).
The following components are required:
Assemble your circuit as shown below:
When the pushbutton is not pressed, logic low will appear on GPIO33, or push button state will be low and when the push button is pressed, a logic high will be on GPIO33. That means a rising edge occurs when a push button is pressed. We can detect this rising edge with the help of interrupt pins of ESP32.
Note: GPIO6, GPIO7, GPIO8, GPIO9, GPIO10, and GPIO11 cannot be used as ESP32 interrupt pins.
Follow the diagram below to select the ESP32 GPIO pin that you can use to connect with the pushbutton.
ESP32 Toggle LED with Pushbutton using FreeRTOS InterruptsOpen your VS Code and create a new ESP-IDF project. Now head over to the main.c file. We will define the functions and the program code here.
ESP32 Interrupt Code#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "driver/gpio.h"
#define ESP_INR_FLAG_DEFAULT 0
#define LED_PIN 27
#define PUSH_BUTTON_PIN 33
TaskHandle_t ISR = NULL;
void IRAM_ATTR button_isr_handler(void *arg){
xTaskResumeFromISR(ISR);
}
void interrupt_task(void *arg){
bool led_status = false;
while(1){
vTaskSuspend(NULL);
led_status = !led_status;
gpio_set_level(LED_PIN, led_status);
printf("Button pressed!\n");
}
}
void app_main(void)
{
gpio_pad_select_gpio(PUSH_BUTTON_PIN);
gpio_pad_select_gpio(LED_PIN);
gpio_set_direction(PUSH_BUTTON_PIN, GPIO_MODE_INPUT);
gpio_set_direction(LED_PIN ,GPIO_MODE_OUTPUT);
gpio_set_intr_type(PUSH_BUTTON_PIN, GPIO_INTR_POSEDGE);
gpio_install_isr_service(ESP_INR_FLAG_DEFAULT);
gpio_isr_handler_add(PUSH_BUTTON_PIN, button_isr_handler, NULL);
xTaskCreate(interrupt_task, "interrupt_task", 4096, NULL, 10, &ISR);
}
How the Code Works?
Firstly, we will start by including the necessary libraries for this project. This includes the driver/gpio.h library as we have to work with GPIO pins and FreeRTOS libraries as we want to add delays and create tasks.
#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include "driver/gpio.h"
Next, we will define ESP_INR_FAG_DEFAULT and set it to zero . This will be used when we will install the interrupt.
#define ESP_INR_FLAG_DEFAULT 0
We have connected the LED at GPIO27. Therefore, we will define a variable called ‘LED_PIN’ that will hold the GPIO pin 27. This will be used later on in the code to control the digital output.
We have connected the push button at GPIO33. Therefore, we will define a variable called ‘PUSH_BUTTON_PIN’ that will hold the GPIO pin 33. This will be used later on in the code to set up the interrupt.
#define LED_PIN 27
#define PUSH_BUTTON_PIN 33
The task handle will be defined as a global variable outside the main function. We have set it as NULL.
TaskHandle_t ISR = NULL;
Inside the interrupt_task() function, we first create a bool variable called ‘led_status’ and set it to false. Then using a continuous while loop we first suspend the current task so that it runs once, toggle the led_status, set the LED_PIN to the state held in the variable ‘led_status’ and print ‘Button pressed!’ in the serial terminal.
void interrupt_task(void *arg){
bool led_status = false;
while(1){
vTaskSuspend(NULL);
led_status = !led_status;
gpio_set_level(LED_PIN, led_status);
printf("Button pressed!\n");
}
}
The button_isr_handler() will resume the interrupt_task from this point.
void IRAM_ATTR button_isr_handler(void *arg){
xTaskResumeFromISR(ISR);
}
app_main()
Inside the main function we will first configure the PUSH_BUTTON_PIN and the LED_PIN as GPIO pin by using the function, gpio_pad_select_gpio() and specify the pin as a parameter inside it.
gpio_pad_select_gpio(PUSH_BUTTON_PIN);
gpio_pad_select_gpio(LED_PIN);
Then, the next step is to configure the LED_PIN as an output and PUSH_BUTTON_PIN as an input. We will set the direction of the pin as an input or output using the gpio_set_direction() function. This function takes in two arguments. The first argument is the GPIO pin and the second argument is the mode (input or output) we want to set the pin in. In our case, we want to set the LED_PIN as an output pin and the PUSH_BUTTON_PIN as an output pin.
gpio_set_direction(PUSH_BUTTON_PIN, GPIO_MODE_INPUT);
gpio_set_direction(LED_PIN ,GPIO_MODE_OUTPUT);
The next step is to set the interrupt on the push button pin on the positive edge. As we are using a pull-down resistor with the button, hence when it will be pressed, it will be in high state. When it will be released it will be in a low state. When the state goes from low to high then the interrupt will occur. The following function sets interrupt for the PUSH_BUTTON_PIN on the positive (rising) edge:
gpio_set_intr_type(PUSH_BUTTON_PIN, GPIO_INTR_POSEDGE);
Then we will install the ISR service with the default configuration.
gpio_install_isr_service(ESP_INR_FLAG_DEFAULT);
Next we attach the interrupt service routine (ISR). Here we are adding the interrupt handler.
gpio_isr_handler_add(PUSH_BUTTON_PIN, button_isr_handler, NULL);
Finally, we create the interrupt_task, using the function xTaskCreate().
xTaskCreate(interrupt_task, "interrupt_task", 4096, NULL, 10, &ISR);
Compiling the Sketch
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, press the push button to turn the LED ON. Now press the push button again, to turn it OFF. Whenever the state of the push button goes from HIGH to LOW, the LED toggles.
Video Demo:
Next FeeRTOS tutorials to read:
You may also like to read:
We are a team of experienced Embedded Software Developers with skills in developing Embedded Linux, RTOS, and IoT products from scratch to deployment with a demonstrated history of working in the embedded software industry. Contact us for your projects: admin@esp32tutorials.com
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4