The configuration of the firmware takes place in two phases. First of all, the Kconfig language (known from the Linux kernel) is used to define hundreds of different compile-time constants for the ESP-IDF framework. And then project-specific constants and globals are defined in a header file.
With the help of Kconfig, hundreds of different definitions are managed within the ESP-IDF framework. These definitions range from rather unimportant (e.g. type of newlib line ending) to absolutely essential (e.g. CPU frequency). As output, Kconfig creates a file called sdkconfig
, which contains all the definitions necessary for the build. However, this file is not tracked by Git, only a file called sdkconfig.defaults
ends up in the repository, which contains the deviations from the default settings.
The project-specific definitions are made in a header called config.hpp
, which gets included in all translation units by default. It contains, among other things, pin assignments, constants such as the ADC sampling rate, general global variables, FreeRTOS task handler and stack size, etc. etc.
An example of the FreeRTOS definitions would be the struct for the ADC task.
The firmware has a partition table that allows us to divide the entire available flash memory into partitions for different uses. Specifically, this division is done through a CSV file called partitions.csv
. The following table shows the distribution.
Name | Type | Subtype | Size | Description |
---|---|---|---|---|
otadata | data | ota | 8K | Bootloader uses this data to know which ota partition to execute |
ota_0 | app | ota_0 | 6M | First application partition (toggles to second on successful update) |
ota_1 | app | ota_1 | 6M | Second application partition (toggles to first on successful update) |
nvs | data | nvs | 6M | Stores settings, locomotives and accessories |
data | data | littlefs | 14272K | Stores additional data (e.g. images) |
The ota_0
and ota_1
entries are the actual app partitions, i.e. the complete firmware including the frontend is stored here. The abbreviation OTA refers to the so-called Over The Air update capability of the ESP32-S3. The update mechanism of the ESP-IDF framework toggles the active partition after each successful update. This also ensures that an error during the update does not lead to a bricked device.
The nvs
partition uses key-value pairs to store device settings, locomotives and accessories. The default settings are contained in a CVS file called nvs.csv
and are included when flashing a firmware.
The data partition is currently reserved but unused. It is planned to use a LittleFS file system for this partition.
To ensure that the firmware runs smoothly, there are a few important points to consider regarding performance. With the dual core architecture of the ESP32-S3, it is very important to pay close attention to which task is running on which core. To simplify this, Espressif provides a FreeRTOS extension in the form of the xTaskCreatePinnedToCore
function, which allows to explicitly specify the core to which the task should get pinned to.
One pitfall that can easily occur here is not considering the initialization of the peripherals. Although Espressif offers its own core-considering API when creating tasks, this is completely missing for all peripherals. This means that while initializing a peripheral, tasks and interrupts may be allocated in the background, which either have their own Kconfig option (e.g. LWIP_TCPIP_TASK_AFFINITY_CPU0), or are simply allocated on the core that called the function. To work around this, there is the utility function invoke_on_core()
, which executes a passed function on an assigned core.
invoke_on_core()
works in blocking mode and is really only used during startup.The following table provides an overview of the distribution of tasks across cores.
All other ESP-IDF internal tasks (ESP Timer, FreeRTOS Timer, Event Loop, lwIP and the Wi-Fi Driver) are pinned to core 0 either by default or by Kconfig options. For further details regarding performance I recommend the Espressif article Speed Optimization.