ESP-IDF и ESP32: ключевые концепции для тех, кто пришёл из Arduino

Перевод статьи ESP-IDF with ESP32: Key Programming Concepts — Rui Santos & Sara Santos, Random Nerd Tutorials.
Поначалу программирование ESP32 в ESP-IDF выглядит подавляюще — особенно если вы пришли из Arduino Core. Но стоит разобраться с несколькими ключевыми концепциями, и оказывается, что ESP-IDF не так уж сложен.
Если вы переходите с Arduino Core, именно эти вещи обычно требуют наибольшей перестройки:
- структуры (
struct) и пользовательские типы (_t); - хендлы;
- указатели (
*) и оператор взятия адреса (&); - задачи FreeRTOS и синхронизация;
- событийное программирование с колбэками;
- коды ошибок и логирование.
Структуры#
Структуры группируют связанные данные. В ESP-IDF вы почти не встретите функций с длинным списком параметров, как это принято в Arduino Core.
Вместо этого передаётся структура, содержащая все относящиеся к делу настройки. Таких конфигурационных структур в ESP-IDF множество — под GPIO, ADC, SPI, Wi-Fi и всю остальную периферию.
Например, структура gpio_config_t хранит все настройки для одного или нескольких выводов. Вместо того чтобы передавать каждую настройку отдельным аргументом, вы сначала заполняете структуру, а потом отдаёте её функции gpio_config():
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL << GPIO_NUM_2), // выбираем GPIO 2
.mode = GPIO_MODE_OUTPUT, // как выход
.pull_up_en = GPIO_PULLUP_DISABLE, // без подтяжки вверх
.pull_down_en = GPIO_PULLDOWN_DISABLE, // без подтяжки вниз
.intr_type = GPIO_INTR_DISABLE // без прерываний
};
gpio_config(&io_conf);
Здесь структура io_conf хранит все параметры: номер вывода, режим, подтягивающие резисторы и тип прерывания. Функция gpio_config() читает эти значения и настраивает вывод соответствующим образом.
Пользовательские типы: typedef и _t#
В ESP-IDF вы постоянно встречаете типы с суффиксом _t. Это пользовательские типы, созданные через typedef, который даёт новое имя уже существующему типу.
Вместо того чтобы писать:
struct gpio_config config;
можно просто написать:
gpio_config_t config;
Почти у каждой периферии есть один или несколько таких типов для хранения настроек — скажем, wifi_config_t для параметров Wi-Fi.
Хендлы#
Ещё одна вещь, которая постоянно встречается в коде ESP-IDF, — хендлы. Хендл — это переменная, идентифицирующая периферию или ресурс, созданный ESP-IDF. Когда вы хотите с ним поработать, вы обращаетесь к нему по хендлу.
Например, при создании блока АЦП:
adc_oneshot_unit_handle_t adc_handle;
adc_oneshot_read(adc_handle, channel, &value);
Переменная adc_handle идентифицирует конкретный блок АЦП: при вызове adc_oneshot_read() именно хендл говорит ESP-IDF, откуда читать. Точно так же хендлы используются для шины I²C, устройств на SPI и многого другого.
Указатели и оператор &#
Эта тема обычно вызывает больше всего путаницы: в Arduino указатели используются редко, а в ESP-IDF — постоянно. Разобравшись с ней, вы начнёте гораздо легче читать не только программы на ESP-IDF, но и код на C вообще.
Каждая переменная где-то лежит в памяти ESP32 и имеет уникальный адрес. Указатель — это переменная, которая хранит адрес другой переменной.
Например:
int value = 25;
value хранит число 25 где-то в памяти. У каждой ячейки памяти есть уникальный адрес. Указатель хранит этот адрес вместо самого числа:
int *ptr = &value;
Звёздочка в int *ptr означает, что ptr — указатель на целое число.
Оператор & возвращает адрес переменной в памяти. Вернёмся к примеру с АЦП:
int value;
adc_oneshot_read(adc_handle, channel, &value);
Здесь &value — это адрес переменной value. Функция adc_oneshot_read() не возвращает результат напрямую: она записывает его в переменную по переданному адресу.
Логирование#
В Arduino IDE для отладки обычно используют Serial.print(). В ESP-IDF есть собственная система логирования:
ESP_LOGI()— информационное сообщение;ESP_LOGW()— предупреждение;ESP_LOGE()— ошибка.
Буквы I, W и E и задают уровень. Функции принимают два аргумента: тег и само сообщение.
ESP_LOGI("MAIN", "Web Server Ready!");
По сути это тот же Serial.print(), только к тексту добавляются полезные вещи: уровень, временная метка и тег.
Обработка ошибок#
Многие функции ESP-IDF возвращают значение типа esp_err_t. Оно говорит, отработала ли функция успешно, а если нет — какая именно ошибка произошла.
Например, при инициализации I²C функция вернёт ESP_OK при успехе или код ошибки в противном случае:
esp_err_t err = i2c_new_master_bus(&bus_config, &bus_handle);
if (err == ESP_OK) {
printf("I2C initialized successfully.\n");
} else {
printf("Failed to initialize I2C.\n");
}
Можно поступить проще и воспользоваться макросом ESP_ERROR_CHECK(). Если вернулся ESP_OK, программа продолжает работу; если ошибка — макрос напечатает информацию о ней и остановит программу. Отлаживаться так куда удобнее, чем расставлять по всему коду отладочные печати.
Самые частые коды:
ESP_OK— операция выполнена успешно;ESP_FAIL— общая ошибка;ESP_ERR_INVALID_ARG— неверный аргумент;ESP_ERR_NO_MEM— не хватило памяти;ESP_ERR_TIMEOUT— истекло время ожидания.
Полный список есть в справочнике кодов ошибок ESP-IDF.
Событийное программирование и колбэки#
В Arduino Core программа крутится в loop() и обычно опрашивает события: снова и снова проверяет, нажата ли кнопка, пришли ли новые данные, изменилось ли показание датчика.
В ESP-IDF большинство приложений построены на событийной модели. Вместо постоянного опроса вы регистрируете функцию обратного вызова (колбэк), привязанную к конкретному событию. Когда событие происходит, ESP-IDF сам вызывает эту функцию.
FreeRTOS#
Ещё одно отличие: ESP-IDF построен поверх FreeRTOS — операционной системы реального времени. (Впрочем, FreeRTOS доступен и в Arduino Core — достаточно подключить соответствующий модуль.)
Вместо того чтобы складывать весь код в loop(), приложение делится на несколько задач. Каждая задача отвечает за своё: чтение датчиков, подключение к Wi-Fi, вывод данных на дисплей и так далее.
Итог#
Это компактный справочник по ключевым понятиям ESP-IDF, к которому удобно возвращаться, пока привыкаете к фреймворку. Как только структуры, хендлы и указатели перестают пугать, оказывается, что остальное — дело техники.
По теме на сайте: ESP-IDF в VS Code: начало работы с ESP32, сканер устройств I²C на ESP-IDF и веб-сервер в режиме точки доступа на ESP-IDF.