151. ESP-IDF и инструменты: idf.py, компоненты, Kconfig, разделы, esptool, QEMU, Arduino
Для STM32 есть выбор: Keil, CubeIDE, Makefile, PlatformIO — и любой из
них лишь оболочка над компилятором. Для ESP32 выбор другого рода: либо
ESP-IDF — фирменная среда Espressif, где сборка, конфигурация,
прошивка и монитор собраны в одну команду idf.py, либо Arduino-ESP32,
которая сама построена поверх ESP-IDF. Раздел разбирает первую по
косточкам на проекте из двух компонентов и показывает, что в сборке
лежит где. Всё — в контейнере espressif/idf:v5.4.1 (_verify/esp151/),
запуск — в эмуляторе QEMU из той же поставки.
Как это называется в документации
Оригинал Перевод ESP-IDF (IoT Development Framework) среда разработки Espressif: библиотеки, FreeRTOS, сборка idf.py единая команда: сборка, конфигурация, прошивка, монитор component компонент: каталог с CMakeLists.txt, библиотека проектаKconfig, menuconfig, sdkconfig описание параметров, меню для их выбора, файл с выбором sdkconfig.defaults значения по умолчанию для проекта, лежат в git target целевой чип: esp32, esp32s3, esp32c3 … partition table (CSV) таблица разделов флеш-памяти в текстовом виде flash_args адреса и файлы для прошивки esptool утилита прошивки и разбора образов по UART monitor терминал с раскраской журнала и расшифровкой адресов ESP_LOGI / LOGW / LOGE / LOGD журнал по уровням с меткой времени и тегом Arduino core for ESP32 ядро Arduino поверх ESP-IDF
151.1 Проект: три файла и два компонента
Минимальный проект ESP-IDF — каталог с CMakeLists.txt из трёх строк и
папкой main:
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(hello151)
main — это компонент, и в нём свой CMakeLists.txt с одной командой:
idf_component_register(SRCS "main.c" INCLUDE_DIRS "." REQUIRES greet spi_flash)
Всё в ESP-IDF — компоненты: FreeRTOS, драйверы, Wi-Fi, наш код. Свой
компонент кладут в components/имя с таким же CMakeLists.txt; здесь
это greet с одной функцией:
void greet(const char *who)
{
for (int i = 1; i <= CONFIG_GREET_TIMES; i++)
printf("привет, %s (%d из %d)\n", who, i, CONFIG_GREET_TIMES);
}
REQUIRES перечисляет, от кого зависит компонент. У main есть
поблажка: пока REQUIRES не написан, ему видны заголовки всех
компонентов сборки. Стоит написать REQUIRES хотя бы с одним именем —
видны только перечисленные и базовые, вроде freertos и log. Поэтому
без spi_flash в списке заголовок esp_flash.h не найдётся. В сборке
этого проекта участвуют 106 компонентов — почти все из самой ESP-IDF.
151.2 Kconfig: параметры без #define
Параметр компонента объявляют в файле Kconfig рядом с ним:
config GREET_TIMES
int "How many times to greet"
range 1 10
default 3
После этого он появляется в меню idf.py menuconfig и в коде как
CONFIG_GREET_TIMES из sdkconfig.h. Выбор пользователя хранится в
файле sdkconfig, который в git не кладут; в git кладут
sdkconfig.defaults — только отличия от значений по умолчанию:
CONFIG_GREET_TIMES=2
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
Вторая строка — та самая правка из раздела 150: модулю с 4 МБ флеш-памяти нужно сказать о них. Программа подтверждает и то и другое:
I (1512) hello151: старт, флеш 4096 КиБ, GREET_TIMES из Kconfig = 2
привет, ESP32 (1 из 2)
привет, ESP32 (2 из 2)
W (1512) hello151: предупреждение печатается жёлтым, ошибка — красным
E (1512) hello151: ошибка ради примера
Так же, через Kconfig, настраивается вся ESP-IDF: частота ядра, тик,
размер стеков, стек Wi-Fi — тысячи параметров, и menuconfig их
единственный честный справочник.
151.3 Журнал: ESP_LOG
printf работает, но у ESP-IDF есть свой журнал с уровнями:
ESP_LOGI(TAG, "старт, флеш %lu КиБ, GREET_TIMES из Kconfig = %d", (unsigned long)(flash / 1024), CONFIG_GREET_TIMES);
greet("ESP32");
ESP_LOGW(TAG, "предупреждение печатается жёлтым, ошибка — красным");
ESP_LOGE(TAG, "ошибка ради примера");
ESP_LOGD(TAG, "отладка: по умолчанию не печатается");
Буква уровня, время в миллисекундах от старта, тег — и idf.py monitor
раскрашивает строки по уровню. Уровень по умолчанию — 3 (INFO,
CONFIG_LOG_DEFAULT_LEVEL=3), поэтому ESP_LOGD в выводе нет: строка не
просто не печатается, она вырезана при компиляции вместе со своей
строкой формата. Отладочный вывод для всего проекта включают в
menuconfig («Default log verbosity»). Чтобы включать отладку одного тега
во время работы, там же поднимают «Maximum log verbosity»
(CONFIG_LOG_MAXIMUM_LEVEL_DEBUG), а уровень по умолчанию оставляют INFO:
тогда строки ESP_LOGD остаются в прошивке, но молчат, пока
esp_log_level_set(TAG, ESP_LOG_DEBUG) не откроет их для нужного тега.
151.4 Что делает idf.py
all app bootloader clean erase-flash flash fullclean gdb menuconfig monitor openocd partition-table qemu reconfigure set-target size size-components size-files
set-target esp32 создаёт sdkconfig под чип; build (он же all) вызывает
CMake и Ninja; flash зовёт esptool, monitor открывает терминал, flash
monitor — оба подряд. size и size-components — куда ушла память:
│ libc.a │ 43459 │ 576 │ 316 │ 260 │ 0 │ 0 │ 0 │ 40035 │ 40035 │ 2848 │ 2848 │ 0 │ 0 │ 0 │ 0 │ 0 │
│ libesp_app_format.a │ 26262 │ 10 │ 10 │ 0 │ 0 │ 0 │ 0 │ 417 │ 417 │ 25835 │ 25579 │ 256 │ 0 │ 0 │ 0 │ 0 │
│ libfreertos.a │ 18076 │ 3841 │ 741 │ 3100 │ 12915 │ 12915 │ 0 │ 364 │ 364 │ 956 │ 956 │ 0 │ 0 │ 0 │ 0 │ 0 │
Первый столбец — всего байт: библиотека C 43 КБ, описание приложения с
хэшем сертификатов 26 КБ, FreeRTOS 18 КБ, из которых 12,9 КБ — в IRAM.
Так находят, кто съел ОЗУ команд. qemu запускает эмулятор, gdb и
openocd — отладку.
Версии в поставке 5.4.1: компилятор xtensa-esp-elf-gcc 14.2.0,
esptool 4.8.1, CMake 3.30.2, Ninja 1.11.1, QEMU 9.0.0 сборки Espressif.
Примеров в каталоге examples — 360, из них периферия 105, Bluetooth
102, система 62, протоколы 43, хранение 23, Wi-Fi 21.
151.5 Что лежит в build и как это прошивается
После сборки в build/ — hello151.bin, bootloader/bootloader.bin,
partition_table/partition-table.bin и файл flash_args, который
idf.py flash отдаёт esptool:
--flash_mode dio --flash_freq 40m --flash_size 4MB
0x1000 bootloader/bootloader.bin
0x10000 hello151.bin
0x8000 partition_table/partition-table.bin
Три образа по трём адресам — ровно та схема загрузки из раздела 150.
Сам esptool умеет и обратное: image_info разбирает образ по
сегментам, merge_bin склеивает все три в один файл флеш-памяти —
именно так собирается образ для QEMU. Загрузчик в эмуляторе делает то
же, что на плате, и рассказывает об этом:
boot: ESP-IDF v5.4.1 2nd stage bootloader
boot: Partition Table:
boot: 2 factory factory app 00 00 00010000 00100000
esp_image: segment 0: paddr=00010020 vaddr=3f400020 size=0984ch ( 38988) map
esp_image: segment 3: paddr=0001bb6c vaddr=40080000 size=044ach ( 17580) load
boot: Loaded app from partition at offset 0x10000
cpu_start: cpu freq: 160000000 Hz
app_init: Project name: hello151
app_init: ESP-IDF: v5.4.1
map — сегмент остаётся во флеш-памяти и отображается кэшем; load —
копируется в ОЗУ по vaddr. Образ приложения этой сборки — 179 144
байта, из 1 МБ раздела занято 17 %.
151.6 Таблица разделов своя
Таблицу задают файлом CSV (partitions.csv в проекте плюс
CONFIG_PARTITION_TABLE_CUSTOM). Готовая таблица «приложение и две
области OTA» из поставки:
nvs,data,nvs,0x9000,16K,
otadata,data,ota,0xd000,8K,
phy_init,data,phy,0xf000,4K,
factory,app,factory,0x10000,1M,
ota_0,app,ota_0,0x110000,1M,
ota_1,app,ota_1,0x210000,1M,
gen_esp32part.py переводит CSV в двоичный вид (3072 байта — таблица
с контрольной суммой, она лежит в разделе на 4 КБ) и обратно, так что
таблицу с чужой платы можно прочитать esptool по смещению 0x8000 и
расшифровать. Три приложения по мегабайту требуют 4 МБ флеш-памяти —
ещё одна причина строки FLASHSIZE_4MB в sdkconfig.defaults.
151.7 Arduino-ESP32 и PlatformIO
Ядро Arduino для ESP32 — компонент ESP-IDF: setup()/loop() работают
внутри задачи FreeRTOS, а Serial, Wire и SPI зовут драйверы
ESP-IDF. Цена удобства — потеря menuconfig (ядро собрано с одним
sdkconfig на всех) и разрыв между версиями. Одна и та же функция в
ядре 2.0.17 и 3.2.0:
2.0.17: hw_timer_t * timerBegin(uint8_t timer, uint16_t divider, bool countUp);
3.2.0: hw_timer_t *timerBegin(uint32_t frequency);
Код с номером таймера и делителем на ядре 3.x не собирается, и таких
мест в переходе на 3.0 много: таймеры, LEDC, АЦП. Примеры из интернета
чаще всего написаны под 2.x. PlatformIO с платформой espressif32
ставит любое из двух ядер или чистую ESP-IDF; сборка в контейнере без
графики — как в разделе 115.
151.8 Грабли
| Симптом | Причина |
|---|---|
fatal error: esp_flash.h: No such file |
у main написан REQUIRES — теперь зависимости только явные |
menuconfig показал не то, что в sdkconfig.defaults |
sdkconfig уже создан: значения по умолчанию берутся только для параметров, которых в нём нет; удалить sdkconfig (fullclean его не трогает) и собрать заново |
ESP_LOGD молчит, даже после esp_log_level_set |
уровень по умолчанию INFO; отладка вырезана при сборке — поднять «Maximum log verbosity» |
set-target отказывается чистить build |
недособранный каталог; удалить build руками |
| образ собран под 2 МБ, модуль на 4 | CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y |
idf.py flash пишет три файла, а не один |
так и должно быть: загрузчик, таблица, приложение по своим адресам |
скетч с timerBegin(0, 80, true) не собирается |
ядро Arduino 3.x: timerBegin(частота) |
| таблица разделов не влезла | 3 × 1 МБ приложений — только с 4 МБ флеш-памяти |
| сборка в контейнере на томе Windows «не видит» правку | грубые метки времени; копировать исходники в контейнер |
151.9 Итоги
- Проект —
CMakeLists.txtиз трёх строк и компоненты; свой компонент — каталог сidf_component_register; в сборке 106 компонентов. Kconfig→menuconfig→sdkconfig; в git —sdkconfig.defaults;GREET_TIMESпо умолчанию 3, в проекте 2.ESP_LOGx: уровень, миллисекунды, тег; по умолчанию INFO, отладка вырезана.idf.py set-target build flash monitor size qemu;flash_args:0x1000,0x8000,0x10000; образ 179 144 байта.- Таблица разделов — CSV; две области OTA:
0x110000и0x210000. - Arduino-ESP32 — компонент поверх ESP-IDF; ядра 2.x и 3.x несовместимы по API.
Полные исходники
main.c — компонент main проекта hello.
/* Раздел 151: проект ESP-IDF из двух компонентов — main и свой greet с параметром в Kconfig. */
#include <stdio.h>
#include "esp_log.h"
#include "esp_flash.h"
#include "greet.h"
#include "sdkconfig.h"
static const char *TAG = "hello151";
void app_main(void)
{
uint32_t flash = 0;
esp_flash_get_size(NULL, &flash);
ESP_LOGI(TAG, "старт, флеш %lu КиБ, GREET_TIMES из Kconfig = %d", (unsigned long)(flash / 1024), CONFIG_GREET_TIMES);
greet("ESP32");
ESP_LOGW(TAG, "предупреждение печатается жёлтым, ошибка — красным");
ESP_LOGE(TAG, "ошибка ради примера");
ESP_LOGD(TAG, "отладка: по умолчанию не печатается");
printf("done\n");
}
greet.c — свой компонент greet с параметром из Kconfig.
#include <stdio.h>
#include "greet.h"
#include "sdkconfig.h"
void greet(const char *who)
{
for (int i = 1; i <= CONFIG_GREET_TIMES; i++)
printf("привет, %s (%d из %d)\n", who, i, CONFIG_GREET_TIMES);
}
Дальше: раздел 152 — FreeRTOS на двух ядрах.