(Tutorial in the making!)

The Espressif Internet Development Framework (ESP-IDF) uses FreeRTOS to make better use of the two high speed processors and manage the numerous built-in peripherals. It is done by creating tasks. Let's look at the hello world, that looks a little different from the ones that you might have seen.

This hello world prints the string on UART (eventually on the computer terminal). We will first look at the ESP-IDF structure. Then look at the hello world example and modify it blink an LED while still continuously printing the 'hello world' string.

 
+--esp-idf
	|
	|
	+ - - components
	|
	+ - - docs
	|
	+ - - examples
	|
	+ - - make
	|
	+ - - tools

The components directory holds all the 'C' code for the ESP32. It contains all the 'components' that make up the ESP32. It includes Drivers for numerous peripherals, the bootloader, bt(bluetooth), freeRTOS etc. If we expand the components the tree looks like so:

+---components
|   +---app_update
|   |   |   component.mk
|   |   |   esp_ota_ops.c
|   |   |   
|   |   \---include
|   |           esp_ota_ops.h
|   |           
|   +---bootloader
|   |   |   component.mk
|   |   |   README.rst
|   |   |   
|   |   +---include
|   |   |       esp_image_format.h
|   |   |       esp_secure_boot.h
|   |   |       
|   |   +---include_priv
|   |   |       bootloader_flash.h
|   |   |       
|   |   \---src
|   |           bootloader_flash.c
|   |           esp_image_format.c
|   |           secure_boot.c
|   |           secure_boot_signatures.c
|   |           
|   +---bt
|   |   |   bt.c
.   . 
|   |           
|   +---driver
|   |   |   component.mk
|   |   |   gpio.c
.   .
|   |               
|   +---esp32
|   |   |   abi.cpp
.   .
|   |           
|   +---esptool_py
|   |   |   Kconfig.projbuild
.   .
|   |                       
|   +---ethernet
|   |   |   component.mk
.   .
|   |           
|   +---expat
.   . 
|   |               
|   +---freertos
|   |   |   component.mk
.   .
|   |           
|   +---idf_test
.   .
|   |
|   +---newlib
.   .
|   |
|   +---nghttp
.   .
|   |              
|   +---nvs_flash
|   |   |   .gitignore
|   |
.   .
|   |           
|   +---openssl
|   |
.   .  
|   |       
|   +---partition_table
.   .
|   | 
|   +---spi_flash
|   |
.   .
|   |           
|   +---tcpip_adapter
.   .
|   |           
|   +---ulp
|   |
.   .
|   |           
|   +---vfs
|   | 
.   .    
|   +---wpa_supplicant
|   |               
|   \---xtensa-debug-module
|   |   component.mk
.   .
|   
|   
+--- docs


As you notice each of the component folders one or more .c and .h files. To include a component we need to include the corresponding .h file in our code. Our hello world example needs 4 of these components.

  • freertos/FreeRTOS.h : Inclusion of this sets configuration required to run freeRTOS on ESP32.
  • freertos/task.h: The tasks as you can guess provide the multitasking functionality, which we will explore in the blinky with hello world example in some time.
  • esp_system.h: This inclusion configures the peripherals in the ESP system. Think of it as system initialization. It's like setting up all the components of your bike, before you could fire the engine!
  • nvs_flash.h: The code for ESP32 chip resides in an External Flash Memory. The access to that is configured here.
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"

For now, assume that the program execution starts with the app_main(), just like good old main(). This is the first function that gets called automatically.

void app_main()
{
    nvs_flash_init();
    xTaskCreate(&hello_task, "hello_task", 2048, NULL, 5, NULL);
}
  • Initialize Flash: nvs_flash_init(): As said earlier the ESP32 Modules run code from an external flash. Unless you're using directly the ESP32 chip, the module/board maker will taker of the flash initialization and access.
void hello_task(void *pvParameter)
{
    printf("Hello world!\n");
    for (int i = 10; i >= 0; i--) {
        printf("Restarting in %d seconds...\n", i);
        vTaskDelay(1000 / portTICK_RATE_MS);
    }
    printf("Restarting now.\n");
    fflush(stdout);
    esp_restart();
}


/* Hello World Example
 
   This example code is in the Public Domain (or CC0 licensed, at your option.)
 
   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"
 
void hello_task(void *pvParameter)
{
    printf("Hello world!\n");
    for (int i = 10; i >= 0; i--) {
        printf("Restarting in %d seconds...\n", i);
        vTaskDelay(1000 / portTICK_RATE_MS);
    }
    printf("Restarting now.\n");
    fflush(stdout);
    esp_restart();
}
 
void app_main()
{
    nvs_flash_init();
    xTaskCreate(&hello_task, "hello_task", 2048, NULL, 5, NULL);
}

Let's multitask: Blink with Hello

You should have seen the blinky code numerous time where-in; we turn ON an LED, wait for a second, turn it OFF and wait for another second. What if I wanted to continuously send a string, say every 100 milliseconds while the LED is still blinking?

It is easy to accomplish that using the RTOS. So the code below, create two tasks. For now, simply assume that these are two infinite (while) loops. Whenever one enters delay state (vTaskDelay function), the other runs and vice-versa. Since we are slow and ESP32 is fast, it switches between the tasks numerous times and we see both the tasks happening simultaneously.


#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "driver/gpio.h"
 
#define BLINK_GPIO 13
 
 
void hello_task(void *pvParameter)
{
 
	while(1)
	{
	    printf("Hello world!\n");
	    vTaskDelay(100 / portTICK_RATE_MS);
	}
}
 
void blinky(void *pvParameter)
{
 
    gpio_pad_select_gpio(BLINK_GPIO);
    /* Set the GPIO as a push/pull output */
    gpio_set_direction(BLINK_GPIO, GPIO_MODE_OUTPUT);
    while(1) {
        /* Blink off (output low) */
        gpio_set_level(BLINK_GPIO, 0);
        vTaskDelay(1000 / portTICK_RATE_MS);
        /* Blink on (output high) */
        gpio_set_level(BLINK_GPIO, 1);
        vTaskDelay(1000 / portTICK_RATE_MS);
    }
}
 
 
void app_main()
{
    nvs_flash_init();
    xTaskCreate(&hello_task, "hello_task", 2048, NULL, 5, NULL);
    xTaskCreate(&blinky, "blinky", 512,NULL,5,NULL );
}

A little food for thought, can you figure out many times the string is printed during a single LED blink? Can I create more tasks? Can I use this to multi-task when handling WiFi data on one hand and handling sensor data from other side.

Are you on a thinking spiral now? Well comment below, you may also sign up here to hear more about our ESP32 Exploration!