В данной статье рассмотрим, как работать с ESP32 под управлением родного фреймворка ESP-IDF.

Введение
ESP-IDF — самый близкий к железу по уровню абстракции фреймворк для работы с ESP32.
Многие возразят, что лучше бы использовать Arduino, но это совсем не так.
С Arduino просто нельзя добиться того быстродействия и той гибкости настроек, что обеспечивает нам IDF. Плюсом ещё и возможности урезаются. Это мы рассмотрим в последующих статьях, например про REST API.
Железо
Обзор
У ESP в массовом продакшне представлены 3 группы SoC.
Есть стандартная ESP32, без доп. наименований. Этот чип самый богатый по количеству периферии. Его мы и будем в дальнейшем использовать по умолчанию.
Есть S-серия, например S2, S3. Базируется на том же ядре Xtensa, что и “классическая” ESP32.
Модель S2 имеет одно ядро, при этом она сильно урезана, например лишена Bluetooth.
Чип S3 более интересный, имеет 2 ядра, а также возможность подключить боснословный объём внешней памяти. Но с периферией тоже беда, например нет Ethernet.
Есть C-серия. Она базируется на ядре RISC-V.
Например C3 — вариант интереснее, чем S2, но скуднее, чем обычная серия.
Или C6, чип, находящийся в разработке, весьма интересный, так как поддерживает Wi-Fi 6, вдобавок и IEEE 802.15.4 (Thread, Zigbee, etc.). Идеальный вариант для протокола Matter.
Вот такой зоопарк:

Прошивка и отладка
Прошивка осуществляется через обыкновенный USB-UART. Можем взять абсолютно любой.
Пробовал использовать VCP в ST-Link v2.1, но ничего корректно не работает.
При этом есть особенность. ESP32 вводится в режим прошивки с помощью пина BOOT0. Его нужно нажимать вручную. Но можно применить подобную схему, которая встречается в специализированных программаторах:

Внутрисхемную отладку (точки останова и просмотр переменных) можно осуществлять только с помощью программатора USB-JTAG. Можно взять “родной” ESP-Prog, но цена на него неоправданно высокая. Эту тему разберём в дальнейших материалах.

Где купить?
С вопросом о покупке выходим на Aliexpress.
Для меня самым удобным вариантом оказалось исполнение в формате Arduino Uno.

Для кого-то может подойти и “классический формат” NodeMCU.

Естественно, модули представлены и самостоятельно, для запайки на свою плату.

ClimateGuard
Отдельно хотелось бы отметить наших хороших друзей — компанию ClimateGuard.
Они разрабатывают и производят электронные модули, а также правильные UART-программаторы и отладочные платы ESP32. Идеальный вариант для прототипирования и внедрения в проекты!
Продукция представлена на OZON, AliExpress, в Амперкоте.
Поддержим отечественного производителя!
Переходите по ссылке и знакомьтесь с продукцией:

Установка ESP-IDF
IDF поставляется в формате самостоятельного программного пакета, в котором содержится компилятор, конфигуратор, система отладки и прошивки.
Его можно подключить в виде плагина к Eclipse или VS Code, либо же использовать самостоятельно в своей любимой IDE.
PlatformIO не рассматриваем. Дело в том, что пакеты там обновляются не регулярно. Ещё и система сборки своя.
Установка на Windows
Для виндусятников возьмём самый простой вариант — ESP IDE. Это “родная” среда разработки, основанная на Eclipse.
Переходим на страницу загрузки, качаем онлайн-инсталлятор. Обратите внимание, в путях установки не должно находиться кириллических символов!
Проходим все шаги установки…



После установки попадаем в обычный Eclipse. Вся дальнейшая работа с ним описана в видео к статье.

Установка IDF вручную
Установку будем производить согласно инструкции от JetBrains. Но она не очень корректная, хочется дополнить её своими комментариями.
Первый шаг — скачивание фреймворка из официального репозитория.
В отличие от оригинальной инструкции, где качается ветка master, мы загрузим один из стабильных релизов.
В ветке master ведётся промежуточная разработка, поэтому можно поймать вот такую чепуху, связанную с версиями пакетов:

На момент написания статьи последняя стабильная версия — 4.4.3.
Проверить это можно в двух местах: в документации и в тегах git репозитория.


Способов установки 3:
Первый: скачивание напрямую
mkdir -p ~/esp # Создаём директорию в домашней папке
cd ~/esp # Переместимся в неё
git clone -b v4.4.3 --recursive https://github.com/espressif/esp-idf.git
В данном случае берём ветку (branch) с именем “v4.4.3” — здесь нужно указать необходимую версию. Параметр --recursive
указывает, что нужно загрузить все зависимые репозитории (submodules).
Второй: скачивание zip-архива из релиза репозитория. (Располагается внизу)
Третий: скачивание zip-архива с сервера espressif.

В обоих случаях нужно докачать зависимости. В директории со скачанным архивом:
git submodule update --init --recursive
Установка
Перед установкой фреймворка, нужно установить все недостающие пакеты:
Debian-подобные:
sudo apt-get install -y wget flex bison gperf python3 python3-pip python3-setuptools cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
Fedora:
sudo dnf -y update && sudo dnf install -y wget flex bison gperf python3 python3-pip python3-setuptools cmake ninja-build ccache dfu-util libusbx
Теперь время запускать установочный скрипт. Он установит все необходимые пакеты и компиляторы. Переходим в директорию с idf, и запускаем.
cd ~/esp/esp-idf
./install.sh
Теперь мы готовы к работе. В любой директории с проектом можем сделать:
. ~/esp/esp-idf/export.sh
idf.py ...
В данном случае запускаем скрипт export, который прописывает пути PATH для текущей сессии терминала (окна, например).
Просто так этим пользоваться скучно, поэтому давайте переходить к работе с IDE.
Программирование
Программирование в IDF осуществляется с помощью языка C. Но есть также враппер под C++, пока в статусе beta.
Создание проекта
Создавать проект с полного нуля нет смысла. Поэтому возьмём готовый шаблон, который содержит правильную структуру и правильные файлы CMake.
Расположен он по пути: .../esp-idf/examples/get-started/sample_project
. Копируем к себе и переименовываем.

Запуск проекта
Работать будем в IDE CLion. Запускаем проект.

После запуска, перед началом работы, IDE ничего не знает о путях IDF. Нам нужно рассказать ей об этом. Сразу после запуска откроется окно конфигурации.


Если Wizard не открылся при запуске проекта, то это всегда можно сделать в настройках.

Таким образом мы передали в IDE все переменные и пути к IDF.

Если видим такую структуру проекта, то сделали всё правильно. Можем начинать работу.
Знакомство с системой сборки
Открываем конфигурацию сборки. Видим много-много таргетов.

По факту нам в работе нужны будут только: app, flash, monitor. Всё остальное — либо не сможем использовать, либо является “системным”.
App — сборка “всухую”, без прошивки.
Flash — сборка и прошивка.
Monitor — система отладки.
Остальное разберём позже.
Вся работа при этом происходит через инструмент Build. Кнопки Run и Debug не используем, так как у нас нет оладчика (есть только программатор). А вся работа происходит через инструменты IDF.
Пробуем запустить Target APP:

Знакомство с API
Для работы всё время будем пользоваться документацией.
Для примера попробуем сконфигурировать пин GPIO. Проходим в раздел Peripherals > GPIO. Линк.
Пин настраивается с помощью такой функции:
esp_err_t gpio_config(const gpio_config_t *pGPIOConfig);
В неё нужно передать экземпляр структуры gpio_config_t
.
struct gpio_config_t {
uint64_t pin_bit_mask; // Маска назначения ног, объединение через лог. "ИЛИ"
gpio_mode_t mode; // Режим пина — вход или выход
gpio_pullup_t pull_up_en; // Включение подтяжки на VCC
gpio_pulldown_t pull_down_en; // Включение подтяжки на GND
gpio_int_type_t intr_type; // Тип входного прерывания
};
Соберём всё вместе, настроим ножку отладочной платы на выход:
#include "driver/gpio.h"
void app_main(void){
gpio_config_t gpioConfig = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = GPIO_SEL_2,
};
gpio_config(&gpioConfig);
}
Особенности RTOS
Программный пакет ESP IDF построен на системе FreeRTOS. Её применение обусловлено наличием двух ядер в чипе.
В любом месте программы мы не можем глобально затормозить систему, как например вызовом HAL_Delay
в STM32.
Для этого применяем задержку из API RTOS.
vTaskDelay(1000 / portTICK_PERIOD_MS); // Задержка в 1000 миллисекунд
Значение делим на portTICK_PERIOD_MS
по той причине, что метод vTaskDelay
отсчитывает задержку не в миллисекундах, а в тиках операционной системы, которые могут иметь и другую частоту. Для понимания обращайтесь к концепции RTOS.
Не забываем добавить хэдеры для RTOS и для задержек!
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
Можем обернуть всё в удобный define:
#define delay(x) vTaskDelay((x) / portTICK_PERIOD_MS)
Обернём всё в цикл while. В итоге получится:
while (1){
gpio_set_level(GPIO_NUM_2, 0);
delay(1000);
gpio_set_level(GPIO_NUM_2, 1);
delay(1000);
}
Прошиваемся через Flash, смотрим на результат. Диодик на плате должен мигать. Для своей отладки укажите свой пин, разберётесь. =)
Разрешения для Linux
В Linux присутствуют некоторые политики безопасности, которые запрещают просто так получить доступ к последовательному порту. Поэтому прошить чип просто так не сможем.
Открываем терминал и вписываем:
sudo usermod -aG dialout $USER
sudo newgrp dialout
После этого нужно перезагрузиться, и всё должно работать. Обычного Log Out недостаточно.
Система сборки CMake
В больших проектах принято делить проект на отдельные файлы. У нас есть прекрасный CMake, в него прописываются зависимости сборки.
CMake файлы, к которым у нас есть доступ — фронтенд к внутренней системе CMake у IDF.
Зависимости в ней прописываются через свои константы, они описаны в документации.
.c файлы добавляются через SRCS.
Директории, где лежат .h файлы — через INCLUDE_DIRS.
Обычно я создаю подобную структуру:

В main/CMakeLists.txt прописываем:

В gpio_user.h:
#ifndef MAIN_GPIO_USER_H
#define MAIN_GPIO_USER_H
void gpio_user(void);
#endif //MAIN_GPIO_USER_H
В gpio_user.c:
#include "gpio_user.h"
#include "driver/gpio.h"
#include "hal/gpio_types.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#define delay(x) vTaskDelay((x) / portTICK_PERIOD_MS)
void gpio_user(void){
gpio_config_t gpioConfig = {
.mode = GPIO_MODE_OUTPUT,
.pin_bit_mask = GPIO_SEL_2,
};
gpio_config(&gpioConfig);
while (1){
gpio_set_level(GPIO_NUM_2, 0);
delay(1000);
gpio_set_level(GPIO_NUM_2, 1);
delay(1000);
}
}
Не забываем вызвать нашу функцию в главном методе. Прошиваемся и любуемся на результат.
Отладка
В самом базовом варианте отладка ESP32 осуществляется с помощью последовательного порта, как и Arduino.
Для этого нужно подключить хэдер:
#include "esp_log.h"
Данные в последовательный порт можно отправить с помощью обыкновенного printf.
Но, мы воспользуемся функциями библиотечки, которая является по сути над обёрткой над ним.
Методы библиотеки:
ESP_LOGE(tag, format, ...) // Сообщение уровня Error
ESP_LOGW(tag, format, ...) // Сообщение уровня Warning
ESP_LOGI(tag, format, ...) // Сообщение уровня Info
ESP_LOGD(tag, format, ...) // Сообщение уровня Debug
ESP_LOGV(tag, format, ...) // Сообщение уровня Verbose
В аргументах мы видим некоторый tag. С помощью него сообщения маркируются в консоли. Это удобно для отладки большой программы, когда есть много библиотек, много файлов, много зон ответственности.
Прошьём тестовый код и посмотрим, как это выглядит в консоли. Для этого нужно запускать сборку с таргетом monitor:

Видим, что сообщения красятся в свои цвета. Видим разные теги. Также видим, что Debug и Verbose не вывелись. Это связано с уровнями логирования. Всего их 5 — в соответствии с форматами отладочных сообщений.
Они выставляются на разных уровнях программы: глобально для всей прошивки, локально для файла, локально для тега. Это нужно, чтобы например спрятать сообщения от конкретных компонентов в консоли.
Для всей прошивки уровень устанавливается в menuconfig.
Для конкретного файла — перед объявлением библиотеки нужно объявить макрос:
#define LOG_LOCAL_LEVEL ESP_LOG_WARN
#include "esp_log.h"
Этих уровней маскировки может быть 6, все они описаны в документации:
ESP_LOG_NONE // Блокируются все сообщения
ESP_LOG_ERROR // Выводится только уровень Error
ESP_LOG_WARN // Выводится Error, Warning
ESP_LOG_INFO // Выводится Info, Error, Warning
ESP_LOG_DEBUG // Выводится Debug, Info, ...
ESP_LOG_VERBOSE // Выводится Vebose, Debug, ...
Тут есть небольшой нюанс, как правило уровень выше INFO мы выставить не можем, так заложено по стандарту в menuconfig. Но это сейчас нам не столь интересно, можно изучить самостоятельно. Лучше посмотрим, как уровни отладки влияют на теги.
Для тега есть подобная конструкция:
esp_log_level_set("Some-TAG", ESP_LOG_VERBOSE);
esp_log_level_set(TAG, ESP_LOG_ERROR);
Загрузим подобный пример:
static const char *TAG = "LED1";
esp_log_level_set("LED", ESP_LOG_INFO); // Можно вызывать тег вот так
esp_log_level_set(TAG, ESP_LOG_ERROR); // А можно и вот так
int i = 0;
int j = 0;
while (1) {
ESP_LOGI("LED", "%d", i++); // "Контрольный" info для тега LED
ESP_LOGI(TAG, "%d", j += 10); // info для тега LED1 - его не должны увидеть
ESP_LOGE(TAG, "%d", j += 2); // error для тега LED1 - его должны увидеть
delay(1000);
}
В консоли увидим что-то такое:

Из этого можем сделать вывод, что строка INFO для LED1 всё-таки вызывается, но по факту в консоли прячется.
Важно ещё понимать: monitor — таргет блокирующий. Он не остановится пока мы сами того не попросим. Прошивка не начнётся автоматически. Поэтому не забываем останавливать сессию отладки!

Конфигурация чипа
Вся конфигурация чипа происходит через menuconfig. Это и система сборки, и система тактирования, и включение/отключение периферии, и программные настройки (в т.ч. загрузчика).
Просто так запустить этот таргет мы не можем. В CLion консоль сборки односторонняя, т.е. никакое управления мы туда передать не сможем.

Поэтому, порядок действий такой:
Открываем терминал, переходим в директорию с проектом. Ещё проще сделать это из окна IDE — оно сразу откроет нужную папку.

Вспоминаем, где лежит esp-idf и прописываем:
. ~/esp-idf/export.sh

Теперь запускаем тулзу menuconfig:
idf.py menuconfig
Нас встретит вот такое окно, думаю, пояснять тут ничего не нужно, всё предельно интуитивно. Подробнее, конкретно под наше применение, рассмотрим как-нибудь потом.

Вывод
ESP32 — весьма интересная для разработки платформа. А ESP-IDF, нативный для разработки фреймворк, оказывается не сложнее Arduino, но однозначно эффективней.
Я всегда топлю за нативные фреймворки. Надеюсь, эта статья помогла вам в его обуздании.
Мы и далее будем изучать ESP-IDF в рамках блога. Чтобы материалы выходили чаще, можно поддержать проект. Спасибо!
Ну, и как обычно, удачи Вам в освоении нашего нелёгкого ремесла!
Прочитав статью, не удивлён отсутствием комментариев, на мой взгляд она абсолютно лишена смысла (в том смысле что никому не будет полезна), для тех кто пытается перейти с arduino или начать с нуля присутствует неизвестная терминология, а тем кто с опытом программирования на c под операционные системы должно быть достаточно документации от изготовителя.