Embedded HandbookПриложения
Все разделы › XVIII. ESP32: второй микроконтроллер
ESP32 · уровень базовый · 10 мин чтения
Открыть в интерактивном справочнике

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.

Проект ESP-IDF: idf.py собирает компоненты по CMakeLists и Kconfig в образ, esptool кладёт загрузчик, таблицу разделов и приложение по своим адресам, QEMU или плата исполняют


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 их единственный честный справочник.

Путь параметра: Kconfig компонента объявляет GREET_TIMES с диапазоном 1–10 и умолчанием 3, menuconfig даёт выбрать 2, выбор хранится в sdkconfig, который в git не кладут, из него собирается sdkconfig.h с CONFIG_GREET_TIMES 2, и код печатает «привет, ESP32 (1 из 2)»; в git идёт только sdkconfig.defaults с отличиями — GREET_TIMES 2 и флеш 4 МБ


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 Итоги

Полные исходники

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 на двух ядрах.

Открыть в интерактивном справочнике