Введение в 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
1 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Гость
Гость
4 месяцев назад

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