In the previous posts I’ve described the new esp32 chip and explained how to setup the development environment, including the use of one – optional – graphical IDE, Eclipse.
Before starting to develop your programs, you need to understand three key components of the framework: flash, bootloader and FreeRTOS operating system. But don’t worry! At the end of this post you’ll write, compile and execute your first program (even if “minimal”) and starting from the next tutorial we’ll work together on more complex examples.
As I wrote in the first post of this tutorial, the esp32 chip requires an external flash memory to store programs, data, configuration parameters…
The external memory is connected to the chip via the SPI bus and the supported capacity is up to 16Mb. The official Espressif module (ESP-WROOM-32) includes a 4Mb flash memory made by GigaDevice (GD25Q32):
The flash memory can store different elements: programs, data… hence it’s divided into sections (partitions). The list partitions, their size and position within the flash memory is stored in the memory itself (at address 0x8000) and it’s called partition table.
Two partition types are defined by default:
- app (type 0) – partition that contains an application (program)
- data (type 1) – partition that contains data
When writing a new program, the developer can decide how to organize the flash memory based on the program’s specific needs. The esp-idf framework offers two pre-configured memory layouts, you can choose from the configuration menu:
You can also show the project’s partition table with the make partition_table command:
In the screenshot above, you can see the partition table layout that corresponds to the simplest configuration (Single factory app, no OTA):
- a data partition (named “nvs“) with a size of 24Kb starting from address 0x9000
- a data partition (named “phy_init“) with a size of 4Kb starting from address 0xf000
- an app partition (named “factory“) with a size of 1Mb starting from address 0x10000
To better understand why the flash memory is divided into partitions, I have to describe you how the esp32 bootloader works, that is how the application is started and how a new firmware is uploaded.
In the esp32 ROM memory there’s a small program, named first-stage bootloader. This program is executed at each reset of the chip: it configures the access to the external flash memory and, if required, stores on it new data coming from the serial/USB port (flash process). Once finished, it accesses the flash memory (at address 0x1000) and loads and executes the second-stage bootloader.
Even if the standard behavior of the esp-idf framework is to use this second-stage bootloader, you can also develop a stand-alone application that, stored at address 0x8000 of the flash memory, is directly executed by the first-stage bootloader.
The second-stage bootloader reads the partition table at address 0x8000 and searches for app partitions. It decides which application has to be executed based on the content of the otadata partition: if this partition is empty or doesn’t exist, the bootloaded executes the application stored in the factory partition. This allows to implement an over-the-air (OTA) application update process: you send the new version of your application to the esp32 chip; the version is stored in a new app partition. Once the upload is completed, the id of the partition is saved in otadata and the chip is rebooted; the bootloader will execute the new version:
The esp-idf framework is based on the FreeRTOS Real-Time Operating System. It may sound strange to talk about operating systems when working on a “small” chip like the esp32… but you don’t have to think that FreeRTOS is an operating system like Windows, Linux or MacOS. The main feature an embedded operating system offers – thanks to its internal scheduler, is multitasking, that is the ability to run different tasks in parallel. We know that a microprocessor core can execute an instruction at a time: it seems that different applications run simultaneously because of the scheduler is rapidly switching between their tasks.
different task statuses in FreeRTOS
A real-time operating system is design to guarantee that task scheduling is deterministic: you can predict the behavior of its scheduler. FreeRTOS allows the developer to define a priority for each task: the scheduler uses priority values to define the execution pattern of the different tasks.
Our first program
Let’s write our first program that used the esp-idf framework. This program, available in my Github repository, will be the skeleton for all the next tutorials.
First we must include some libraries:
The stdio.h library (Standard I/O) is used for input/output functions (printf…) and the two freertos header files are used to define and execute the different tasks.
Every program is executed starting from the app_main() function:
In the app_main() a new task is created using the xTaskCreate method. This method requires the following parameters: the pointer to the function (&loop_task) that contains task code, the name of the task (“loop_task”), the size, in words, of the stack memory to be assigned to the task (512), additional parameters for the task (NULL), the task’s priority (5) and the pointer – optional – to retrieve an handler of the task (NULL).
Once the task is created, the FreeRTOS scheduler executes it based on the different tasks and priorities.
The task has the following code:
it’s a continuous loop that every second prints the sentence Hello World!”. The vTaskDelay method pauses the task for the specified number of ticks. The portTICK_RATE_MS constant defines the duration, in milliseconds, of a tick; if you therefore divide 1000 for that constant you get the number of ticks in a second.
To be able to compile your project, you need to add a couple of additional files:
- a Makefile in the main folder of your project that contains the name of the project and an include for the main makefile of the framework:
- a component.mk file – it can be empty – in the folder where the source code is saved into to tell the compiler to process the files in that folder:
When the program is ready, you can compile and load it on the board as explained in a previous post:
If everything is ok, when you connect to the board with a serial emulator you should see: