Введение в ESP-IDF

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

esp32-video
🔗 Ссылка на видео 🔗

Введение

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.

Вот такой зоопарк:

esp32-comparison
Линк на сравнение

Прошивка и отладка

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

При этом есть особенность. ESP32 вводится в режим прошивки с помощью пина BOOT0. Его нужно нажимать вручную. Но можно применить подобную схему, которая встречается в специализированных программаторах:

Введение в ESP-IDF
Доп. линии UART используются для перевода в режим программирования

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

Введение в ESP-IDF

Где купить?

С вопросом о покупке выходим на Aliexpress.

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

esp32-uno

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

esp32-classic

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

esp32-module

ClimateGuard

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

Поддержим отечественного производителя!
Переходите по ссылке и знакомьтесь с продукцией:

https://climateguard.ru/esp32

climateguard

Установка ESP-IDF

IDF поставляется в формате самостоятельного программного пакета, в котором содержится компилятор, конфигуратор, система отладки и прошивки.

Его можно подключить в виде плагина к Eclipse или VS Code, либо же использовать самостоятельно в своей любимой IDE.

PlatformIO не рассматриваем. Дело в том, что пакеты там обновляются не регулярно. Ещё и система сборки своя.

Установка на Windows

Для виндусятников возьмём самый простой вариант — ESP IDE. Это “родная” среда разработки, основанная на Eclipse.

Переходим на страницу загрузки, качаем онлайн-инсталлятор. Обратите внимание, в путях установки не должно находиться кириллических символов!

Проходим все шаги установки…

idf-install1
Качаем IDF из сети
idf-install2
Выбираем самую стабильную версию
idf-install3
Выставляем пункты в меню

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

esp-ide
Окно Espressif IDE

Установка IDF вручную

Установку будем производить согласно инструкции от JetBrains. Но она не очень корректная, хочется дополнить её своими комментариями.

Первый шаг — скачивание фреймворка из официального репозитория.
В отличие от оригинальной инструкции, где качается ветка master, мы загрузим один из стабильных релизов.

В ветке master ведётся промежуточная разработка, поэтому можно поймать вот такую чепуху, связанную с версиями пакетов:

idf5.0-error
Баги в версии фреймворка 5.0

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

Введение в ESP-IDF
В левом меню есть выбор версий
Введение в ESP-IDF
Выбираем версию из тэгов

Способов установки 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.

Введение в ESP-IDF
Ссылка на сервер указана в описании релиза

В обоих случаях нужно докачать зависимости. В директории со скачанным архивом:

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. Копируем к себе и переименовываем.

Введение в ESP-IDF

Запуск проекта

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

Введение в ESP-IDF
Открываем как CMake

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

Введение в ESP-IDF
Настраиваем тулчейн…
Введение в ESP-IDF
Прикручиваем файл export

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

Введение в ESP-IDF
В настройках всё то же самое

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

Введение в ESP-IDF
Структура проекта

Если видим такую структуру проекта, то сделали всё правильно. Можем начинать работу.

Знакомство с системой сборки

Открываем конфигурацию сборки. Видим много-много таргетов.

Введение в ESP-IDF

По факту нам в работе нужны будут только: app, flash, monitor. Всё остальное — либо не сможем использовать, либо является “системным”.

App — сборка “всухую”, без прошивки.
Flash — сборка и прошивка.
Monitor — система отладки.

Остальное разберём позже.

Вся работа при этом происходит через инструмент Build. Кнопки Run и Debug не используем, так как у нас нет оладчика (есть только программатор). А вся работа происходит через инструменты IDF.

Пробуем запустить Target APP:

Введение в ESP-IDF
Если так, то всё в порядке

Знакомство с 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.

Обычно я создаю подобную структуру:

Введение в ESP-IDF
Создаём папку Libs, туда раскладываем по папкам пользовательские файлы.

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

Введение в ESP-IDF
Указываем директории, прикручиваем .c файлы.

В 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:

Введение в ESP-IDF

Видим, что сообщения красятся в свои цвета. Видим разные теги. Также видим, что 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);
}

В консоли увидим что-то такое:

Введение в ESP-IDF

Из этого можем сделать вывод, что строка INFO для LED1 всё-таки вызывается, но по факту в консоли прячется.

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

Введение в ESP-IDF
Вот сюда тык

Конфигурация чипа

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

Просто так запустить этот таргет мы не можем. В CLion консоль сборки односторонняя, т.е. никакое управления мы туда передать не сможем.

Введение в ESP-IDF
При запуске получим вот такую ерунду

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

Введение в ESP-IDF

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

. ~/esp-idf/export.sh
Введение в ESP-IDF
Получится что-то вроде

Теперь запускаем тулзу menuconfig:

idf.py menuconfig

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

Введение в ESP-IDF

Вывод

ESP32 — весьма интересная для разработки платформа. А ESP-IDF, нативный для разработки фреймворк, оказывается не сложнее Arduino, но однозначно эффективней.

Я всегда топлю за нативные фреймворки. Надеюсь, эта статья помогла вам в его обуздании.

Мы и далее будем изучать ESP-IDF в рамках блога. Чтобы материалы выходили чаще, можно поддержать проект. Спасибо!

Ну, и как обычно, удачи Вам в освоении нашего нелёгкого ремесла!

Подписаться
Уведомить о
guest
2 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Гость
Гость
10 месяцев назад

Прочитав статью, не удивлён отсутствием комментариев, на мой взгляд она абсолютно лишена смысла (в том смысле что никому не будет полезна), для тех кто пытается перейти с arduino или начать с нуля присутствует неизвестная терминология, а тем кто с опытом программирования на c под операционные системы должно быть достаточно документации от изготовителя.

Дмитрий
Дмитрий
Ответить на  Гость
2 месяцев назад

попробуйте настроить VS CODE + ESP-IDF 5.1.2. Будете удивлены разнообразию безответственности, глупости и тривиальности ошибок установщика и отношению разрабов к пользователям.