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:
- Configuration of a Project and writing the code
- Compilation of the Project and linking it to build an Application
- Flashing (uploading) of the Application to ESP32
- Monitoring / debugging of the Application as needed
The following diagram shows the ESP32 application development cycle,
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 atIDF_PATH
as we did in the last blog - A
src
path which contains,main.c
- The actual source code for our applicationcomponent.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,
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.