Firmware  0.0.0
Loading...
Searching...
No Matches
Configuration

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.

Kconfig

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.

config.hpp

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.

inline struct AdcTask {
static constexpr auto name{"analog::adc"};
static constexpr auto stack_size{4096uz};
static constexpr UBaseType_t priority{ESP_TASK_PRIO_MAX - 2u};
static constexpr auto timeout{200u};
TaskHandle_t handle{};
struct analog::AdcTask adc_task
httpd_handle_t handle
Handle to server instance.
Definition config.hpp:302

Partition Table

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)
Warning
The partition table cannot be divided completely freely but is subject to certain restrictions.

OTA

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.

NVS

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.

Data

The data partition is currently reserved but unused. It is planned to use a LittleFS file system for this partition.

Performance

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.

Note
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.

Task Core
analog::adc_task 1
analog::temp_task 1
dcc::task 1
decup::task 1
mdu::task 1
ota::task 1
out::track::dcc::task 1
out::track::decup::task 1
out::track::mdu::task 1
out::zusi::task 1
usb::rx_task 1
usb::tx_task 1
usb::ulf_dcc_ein::task 1
usb::ulf_decup_ein::task 1
usb::ulf_susiv2::task 1
wifi::task 0
z21::task 0
zusi::task 1

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.

Previous Next
Development Architecture