Перевод статьи 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.