ESP32: esp-idf Development Framework and the FreeRTOS kernel

This post is part of a series of ESP32 blog posts. Look the index here

Motivation

In the last blog post of this series, we saw how to set up the basic development environment, then built and loaded a hello-world program included in the examples folder of the esp-idf (Espressif IoT Development Framework). Today I’ll try to give some details about what is actually happening in the process, about what is the operating system running within the ESP32, and how it works.

The application development

The application development may be summarized in four steps:

  1. Configuration of a Project and writing the code
  2. Compilation of the Project and linking it to build an Application
  3. Flashing (uploading) of the Application to ESP32
  4. Monitoring / debugging of the Application as needed

The following diagram shows the ESP32 application development cycle,

Depiction of the development of ESP32 applications

Configuration of the project

The esp-idf provides a project template which can be used to bootstrap a new project. The source tree has,

  • A Makefile which defines the name for the project (app-template in the template), that also imports a Makefile from the esp-idf location, which is expected to be set at IDF_PATH as we did in the last blog
  • A src path which contains,
    • main.c - The actual source code for our application
    • component.mk - Component specific makefile. We’ll leave this empty for now

The programming language of choice is C, though C++ and even assembly also can be used. As mentioned another post there are other bootloaders available that could allow us to program ESP’s with Python or Lua among others.

To write an application using the espressif template –which I do recommend– you’ll need to edit the file src/main.c and add more files as needed. Another approach is to make a copy of the hello-world located at $IDF_PATH/examples/get-started/hello_world and build upon it.

Once you have some code, yo can compile it and upload it to the ESP32. As we saw in the last blog post with the esp-idf we can use the Makefile, and perform the compilation with make. This won’t upload the code, but it will cross compile it –more about his below– to run on ESP32 CPUs. If the compilation succeeds we can upload the application to the ESP32 with make flash.

Cross compiling is the process of using a host computer architecture to compile binaries targeted to run in another architecture. For the ESP32, I use my amd64 64-bit architecture to compile programs for the Xtensa Tensilica LX6 Microprocessor, which is a 32-bit architecture. That’s why we need a toolchain since it provides an adequate set of cross compiling tools and library linkers to put together machine code that can run on the ESP’s 32-bit CPU.

The toolchain contains several commands with the form xtensa-esp32-elf-*, for instance, xtensa-esp32-elf-gcc is the C compiler to build Xtensa binaries that run on ESP32 CPUs.

In summary, the esp-idf with the toolchain allows to build an upload the application to the ESP32.

Compilation, flashing, and monitoring of the project

We have already seen how to compile the project, flash it to the ESP32, and how to look to the serial monitor, all with the esp-idf. If you wish to get a deeper understanding, I suggest reading more at the amazing Kolban’s book on ESP32 by Neil Kolban.

FreeRTOS

The purpose of an operating system kernel is to provide an interface between the software applications and the underlying hardware infrastructure. When we are running ESP32 programs, we are doing so within the FreeRTOS kernel.

The FreeRTOS is an open source operating system designed for embedded systems like the ESP32, it has three main core functions,

  • Memory management
  • Task management
  • API Synchronization

The RTOS stands for Real Time Operating System, a property that we will discuss in the next subsection.

The FreeRTOS kernel was originally developed by Richard Barry around 2003, after years of development and success, Amazon Web Services (AWS) took over the stewardship of the project in 2017. It is natural to ask, why did AWS asume this role?, well AWS offers services to millions of customers, and in all industry sectors. A growing number of AWS services are designed for Internet of Things (IoT) applications, that is, the connection and management of internet connected devices.

Device manufacturers connect their MCU based devices to the cloud, however, it takes time to build the security and connectivity components necessary for those devices run work properly at production scale. A significant proportion of connected MCU devices already run the FreeRTOS kernel, so Amazon chose to provide the FreeRTOS project with the resources necessary to extend their offering into fully integrated security and connectivity libraries, and ensure those libraries can be developed and supported long into the future.

Memory management

Since we are working with C, the use of malloc() and free() are valid ways to allocate/release memory resources, like in the example below,

#include<stdio.h>
#include<stdlib.h>

int main() {
    // Allocate memory for an Int
    int *counter = malloc(sizeof(int));
    
    // Store data in the variable
    *counter = 123;
    
    // Print what we have
    printf("Variable contents: %i\n", *counter);
    
    // Release the memory
    free(counter);
}

Task management

A part of the operating system called the scheduler is responsible for deciding which program to run when, and provides the illusion of simultaneous execution by rapidly switching between each program.

The scheduler in FreeRTOS is designed to provide a predictable –deterministic– execution pattern. This is critical to embedded systems since they often have real time requirements, tha is, the embedded system must respond to a certain event within a strictly defined time.

Traditional real time schedulers, such as the scheduler used in FreeRTOS, achieve determinism –hence predictability– by allowing the user to assign a priority to each thread of execution. The scheduler then uses the priority to know which thread of execution to run next. In FreeRTOS, a thread of execution is called a task.

A task can be intuitively understood as any piece of work that can be performed by the CPU. Examples of tasks could be checking whether a voltage level changes at certain GPIO port –say, by pressing a button–, or making a led blink by setting the voltage of the associated GPIO from low to high and back to low at certain frequency.

FreeRTOS allows several tasks to run concurrently by switching between task executions in a non locking fashion. In other words, the kernel can sequentially execute portions of different tasks at consecutive CPU cycles. The following image depicts this behaviour,

FreeRTOS concurrent task execution

Each horizontal line represents a task. In the top part of the image, the tasks seem to run at the same time, but as the portion at the bottom show, only a task at the time is executed by the CPU.

In FreeRTOS a task is implemented by a code block responsible of performing whatever the task we’d like to perform, but with a function signature as,

void theTaskName(void *theParameters)

But this is not enough, the task must be created with xTaskCreate() –we well discuss this API in detail in a future blog post–.

FreeRTOS expects a task to run forever, but, if we need it to end, we need to call vTaskDelete() at the end of the scope of the task code block, like this,

void theTaskName(void *theParameters) {

  // Perform activities

  vTaskDelete(NULL);
}

I’ll go into details about creating task in an incoming blog post.

API Synchronization

This is a more advanced topic of ESP32 programming with FreeRTOS, I’m still pending of diving into it and writing about it here.

Application entry point

If you look at the code of the hello-world example, you’ll notice that, unlike a regular C program, we have no main() function. The application entry for the esp-idf + FreeRTOS is the app_main() function. This doesn’t mean we can’t define more functions, as I wrote above, we can create FreeRTOS task and such. Programming the ESP32 with esp-idf is really regular C programming. Before running the entry point function, several steps happen, I recommend reading Espressif esp-idf getting started

Summary

In this blog I’ve discussed the logic behind using the esp-idf to program ESP32 CPUs, what is the operating system running within our ESP32 device (FreeRTOS), besides a very basic notion of what is a task, a concept we will be using a lot in the following blog posts of the series.

Resources

comments powered by Disqus