Перейти к содержимому

Особенности программирования ESP32 из Arduino IDE

Редакция 5 от 2022/11/25

Данная статья, возможно, несколько устарела и утратила актуальность. Но всё так же очень популярна в поиске, поэтому я не могу её просто взять и удалить с сайта. Вместо этого я советую вам прочитать другую статью: как и на чем программировать ESP32 и ESP8266. Благодарю за внимание.

Эта небольшая статья ориентирована в первую очередь на тех, кто только начинает осваивать микроконтроллер ESP32 в среде Arduino IDE. Я далеко не гуру в микроконтроллерах и в свое время я потратил много времени, пытаясь понять, чем же отличается программирование для ESP32 от уже привычных Adruino и ESP8266; и почему почти все примеры для ESP32 написаны как задачи для FreeRTOS. Меня долго занимал вопрос – что же это за FreeRTOS такая, и так ли уж необходимо ее осваивать, когда начинаешь работать с новым микроконтроллером. Забегая вперед скажу, что таки да, без FreeRTOS на ESP32 никуда, по крайней мере если Вы используете “классическую” Arduino IDE. Ну то есть если очень хочется, то можно обойтись без задач и FreeRTOS, но это будет как “микроскопом гвозди забивать”. Всё нижесказанное относится как к Arduino IDE, так и к PlatformIO (в случае использования framework-а ArduinoEspressif32, что практически равносильно Arduino IDE). Итак, обо всем по порядку… 

 

ESP32 имеет по сравнению с ESP8266 множество преимуществ – шустрый двухядерный процессор, гораздо больше портов ввода-вывода, в том числе и аналоговых, возможность подключения внешней WiFi-антенны (на некоторых моделях) и много чего еще. В рамках данной статьи я не буду рассматривать все аппаратные отличия ESP32 от своего предшественника, информации об этом в “этих ваших ынтирнетах” достаточно много. Скажу лишь, что лично для меня ключевым фактором перехода на использование ESP32 стало существенно большее количество портов GPIO для подключения внешних устройств и датчиков, с возможностью переноса шин I2C, SPI и других на любые свободные GPIO. ESP8266 обладает в этом плане ну очень скромными возможностями, да еще единственный АЦП обладает довольно низким разрешением. Конечно, цена ESP32 несколько выше, но с этим вполне можно смириться.

ESP32-DevKitC

ESP32-DevKitC – варианты с печатной антенной и внешней

Программирование в Arduino IDE

Сразу оговорюсь, что писать Arduino-скетчи для ESP32 можно не только в ArduinoIDE, но в Visual Studio Code с использованием плагина PlatformIO. Почитать об этом можно в других статьях, посвященных PlatformIO. Но в рамках данной статьи будем обсуждать только ArduinoIDE. Итак…

Перед началом программирования ESP32 в Arduino IDE необходимо установить пакет esp32 by Espressiv Systems через Менеджер плат:

esp32 by Espressiv Systems

Установленный пакет esp32 в менеджере плат

Версия пакета, разумеется, будет совсем другая.

Если в списке плат нет этого пакета, добавьте строчку “https://dl.espressif.com/dl/package_esp32_index.json” в Настройках Adruino IDE:

Настройки Adruino IDE

Подключение esp32 к менеджеру плат

После этого повторно попробуйте установить пакет esp32 через Менеджер плат.

Примечание: сейчас пакет ArduinoESP32 официально называется как “Arduino Core for ESP32” – как на GitHub, так и в справочной системе. Ранее он назывался ArduinoEspressif32. Возможно, приведенные скриншоты немного отличаются от текущего состояния, так как я давно уже не пользуюсь Arduino IDE. Но ссылка по прежнему рабочая.

Когда всё будет готово, можно приступать к программированию, как обычно.  При создании нового, пустого, скетча для ESP32 в шаблоне используются те же самые setup() и loop(). Вроде-бы всё как обычно, как мы привыкли с Arduino и ESP32. Нет отличий? На самом деле это не так!

 

Обязательна ли FreeRTOS для ESP32?

Первое, на что натыкаешься, когда смотришь примеры скетчей для ESP32 – в них очень часто используются функции xTaskCreate() или xTaskCreatePinnedToCore(). Если погуглить, то поиск выдает малопонятные слова “FreeRTOS” и “многозадачность”. Поначалу возникает чувство внутреннего протеста и возникает вопрос: а, что, разве нельзя сделать “по старому, по простому”, без использования всех этих задач? Так ли уж необходимо осваивать новую область программирования – FreeRTOS? Забегая вперед скажу – увы, без этого не обойтись. Если вы хотите осваивать ESP32 – вам придется осваивать и FreeRTOS, хотите вы этого или нет. Просто потому, что FreeRTOS выбрана разработчиком чипов (Espressif) в качестве legacy framework-а.

Кроме того, лично у меня сразу же возник еще один вопрос – если на борту ESP32 теперь два процессора, то как они будут использоваться скетчем? В свое время я задавал на форуме esp8266.ru, но потом сам же на него и ответил :). 

Ответы на свои вопросы я получил после того, как наткнулся на “обертку” скетча Arduino \packages\esp32\hardware\esp32\1.0.4\cores\esp32\main.cpp. Эта обертка используется для всех скетчей, которые Вы создаете в Arduino (и не только, если используется ArduinoFramework32).

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_task_wdt.h"
#include "Arduino.h"

TaskHandle_t loopTaskHandle = NULL;

#if CONFIG_AUTOSTART_ARDUINO

bool loopTaskWDTEnabled;

void loopTask(void *pvParameters)
{
setup();
    for(;;) {
        if(loopTaskWDTEnabled){
            esp_task_wdt_reset();
        }
        loop();
    }
}

extern "C" void app_main()
{
    loopTaskWDTEnabled = false;
    initArduino();
    xTaskCreateUniversal(loopTask, "loopTask", 8192, NULL, 1, &loopTaskHandle, CONFIG_ARDUINO_RUNNING_CORE);
}

#endif

Какую информацию отсюда можно почерпнуть? Видно, что FreeRTOS всегда по умолчанию подключается к Вашему скетчу (через #include "freertos/FreeRTOS.h"), а любой классический скетч Arduino (то есть якобы написанный без использования задач) – это всегда задача FreeRTOS, которая выполняется на втором ядре процессора #1, и которой выделено всего 8192 байт стека! Сама FreeRTOS при этом выполняется на ядре процессора #0. Становится немного понятнее, как на ESP32 реализована работа с двумя ядрами. 

Дальнейшее исследование данного вопроса привело на ресурсы, посвященные ESP-IDF. Оказалось, что ESP-IDF (Espressif IoT Development Framework) – это основной (legacy) фреймворк для программирования микроконтроллеров на базе ESP32, разработанный тем же Espressif Systems. А используемый в Arduino IDE фреймворк Arduino Core for ESP32 это адаптация ESP-IDF, чтобы втиснуть его в узкие рамки Arduino IDE.

Если вы продолжите программирование в данном классическом варианте (один скетч – одна задача), то ваш Arduino скетч получит только доступ только к 8192 байт стека (на ESP32 стек указывается именно в байтах, а не словах, как в классической FreeRTOS). В принципе, это достаточно много, даже слишком много. Почти наверняка вы даже не сможете его (стек задачи) полностью израсходовать (если только не будете предпринимать к этому специальных усилий, вроде 100500 аргументов в каждой функции). Остальную память вы сможете использовать в качестве общей кучи (heap). Подробнее о распределении памяти на ESP32 вы можете почитать в другой статье.

На самом деле, параллельно с вашим скетчем при этом будут работать ещё несколько задач, созданных и запущенных самим фреймворком Arduino Core for ESP32, например: планировщик задач, цикл событий, IPC и т.д., но это не отразится на вашем скетче. В принципе, на этом можно и закончить статью – вы вполне сможете написать рабочий классический скетч в классической Arduino IDE, но с ограничение стека 8192 байт.

Фреймворк Arduino Core for ESP32 создан для интеграции ESP-IDF в среду Arduino IDE, но может быть использован и под PlatformIO (что в 99% случаев и делается). 

Но как только вы захотите чего-то б0льшего – вам придется столкнуться с FreeRTOS в любом случае. Просто потому, что Arduino Core for ESP32, так же как ESP-IDF (что вполне логично), полностью построен на базе FreeRTOS. Если Вы хотите использовать все доступные на ESP32 возможности, Вам придется изучать и использовать функции FreeRTOS, хотите Вы этого или нет. В этом нет ничего страшного, осваивается FreeRTOS довольно легко.

Многопоточность позволяет легко и изящно отделить “мух от котлет”, когда требуется обеспечить стабильную работу автоматики вне зависимости от внешних факторов. Например у Вас “сгорел” WiFi-роутер, “упал” MQTT-сервер или случилось что-то ещё. Но при этом “основная” задача контроллера, где читаются показания датчиков и включаются и выключаются реле управления насосами и обогревателями продолжат стабильно и без задержек работать. На однопоточной системе сохранить такую стабильность гораздо сложнее (хотя возможно и можно, спорить не буду).

Но увы, когда Вы начнете использовать многопоточность и задачи на ESP, то Вам придется забыть про некоторые уже ставшие привычными библиотеки Arduino, так как они просто не способны работать в многозадачной среде. Например, для ESP32 не будет работать библиотека ESP8266Ping и Arduino Time Library. Проблема решаемая, есть варианты замены, но требуется внимательность и перепрограммирование некоторых участков кода под новые условия. 

 

ESP-IDF – основной framework для ESP32

Ещё более кардинальным решением является полный отказ от фреймворка Arduino Core for ESP32 как такового, то есть программирование контроллера на базовом фреймворке Espressif IoT Development Framework (чаще пишут сокращенно – ESP-IDF). Зачем это нужно и какие возможности это дает?

Начнем с преимуществ ESP-IDF

Во-первых, ещё раз повторюсь, Arduino Core for ESP32 – это всего-лишь “надстройка” над ESP- IDF, которая занимает лишние ресурсы и накладывает определенные ограничения (вроде заранее определенных выводов для шин I2C, RS485 и SPI). На мой лично взгляд это не самый оптимальный подход, так как код написанный под ESP-IDF будет по определению более оптимальным, чем код, выполняющий точно те же самые функции из под Arduino. Ну это если вы сами не налажаете конкретно, конечно 😉

Во вторых – возможности многозадачной операционной системы лично меня просто восхищают. Создав несколько задач для разных функций прошивки, можно больше не опасаться, что непредвиденная пауза в одной из задач вызовет завивание всего контроллера. Например отключение WiFi роутера не вызывает катастрофы в сервисах измерения данных с сенсоров и обработки результатов.

В третьих, в ESP-IDF API разработчики уже предусмотрели столько встроенных функций и библиотек “на все случаи жизни”, что необходимость в очень многих сторонних библиотеках просто отпадает. GPIO, UART, Modbus, SPI, SDIO, I2C, I2S, PWM, WIFI, PING, клиент MQTT, HTTP(S), аппаратные и программные таймеры, и многое, многое другое. Всё это достаточно хорошо документировано и описано, есть много готовых примеров. И никаких велосипедов изобретать не требуется. Правда стоит сразу отметить, что зачастую встроенные библиотеки ESP-IDF сжирают значительную часть памяти – например при подключении библиотек WiFi (“esp_wifi.h”, “esp_netif.h” и иже с ними) размер скетча скачкообразно увеличивается от ~14% до аж сразу ~50% от доступного объема раздела, при подключении MQTT – еще +10% и т.д.

В четвертых, Arduino Core for ESP32 – это однопоточная система по определению. Она не создавалась для многопоточных микроконтроллеров. Пока вы работаете с одной единственной задачей (скетчем), то проблем с совместным доступом к ресурсам, скорее всего, не будет. Но если вы попытаетесь создать несколько задач и обратиться из них к одному и тому же ресурсу, например WiFi – возможны проблемы. Низкоуровневое API ESP-IDF во многих случаях более адаптировано к многопоточности (что вполне логично).

В пятых, Arduino Core for ESP32 “теряет” некоторые аппаратные возможности ESP32. Например в ESP- IDF можно одновременно использовать две шины I2C (и навешать в два раза больше датчиков), но Arduino это не умеет.

Недостатки отказа от Arduino Core for ESP32

В этом не совершенном мире за всё приходится платить. С чем придется столкнуться, если Вы решите использовать ESP32 “на полную катушку”?

Во-первых, придется отказаться от привычной Arduino IDE, так как возможности установить “чистый” ESP- IDF в Arduino IDE нет. А использовать “урезанный” вариант мне не хотелось. Например перейти на VSCode + PlatformIO. Хоть VSCode и несравненно удобнее Arduino IDE, но понимаешь это только после того, как его полностью освоишь. Представьте себе, что вы всегда ездили только на велосипеде, а вдруг сели за руль автомобиля – в первые минуты вы растеряетесь от обилия рычагов, кнопочек и педалей. Так и тут. Да и плагин для работы с микроконтроллерами PlatformIO, лично на мой взгляд ещё далек от совершенства. В общем, прежде чем вы соберете первый более-менее полноценный проект на PlatformIO, помучаетесь изрядно. На данном сайте вы найдете достаточно много статей, призванный помочь вам в этом непростом процессе.

Во-вторых, и это так же очевидно, таки придется осваивать новые функции ESP-IDF. Но это даже интересно. ESP-IDF по большей части асинхронное API, то есть основанное на событиях. Некоторые задачи решаются настолько “изящно”, что получаешь истинное удовольствие от программирования. А над решением некоторых задач приходится поломать голову.

Во-третьих, на ESP32 каждая отдельная задача не может использовать весь доступный объем стека, ей выделяется только небольшой фиксированный кусочек. На это часто обращают внимание противники ESP-IDF на различных форумах. Но “не так страшен чoрт, как его малюют” – с этим просто приходится считаться, это несколько усложняет отладку, так как приходится каждый раз опытным путем подбирать объем стека для задачи. Дать больше – другим задачам может не хватить, дать меньше – контроллер будет перезагружаться из-за переполнения стека задачи. А в целом это не приводит к каким-либо катастрофическим ограничениям. Подробнее о работе с памятью на ESP32 читайте здесь.

Но главный недостаток (на мой взгляд) может напрочь перечеркнуть все достоинства ESP-IDF, что называется “на корню”. Дело в том, что под ESP-IDF очень мало библиотек драйверов устройств. Датчиков температуры и влажности, LCD, и много другого. Драйверов всего того, что легко находится в списке стандартных библиотек в Arduino IDE. То есть все библиотеки, скажем для DHT22 или BME280, которые можно просто и быстро найти в репозиториях Arduino IDE (да и PlatformIO) существуют только под Arduino. Версий под ESP-IDF пока очень мало – DHT22, вроде бы есть ещё что-то, и на этом в общем-то всё. Их просто никто не пишет, ибо все плотно сидят на анаболиках Arduino. Это не катастрофа, библиотека для работы с I2C имеется, и “покурив” мануалы, можно достаточно написать свой драйвер. Либо просто взять исходники драйвера для Arduino и заменить в нем команды обмена данными на новые. Впрочем, сейчас довольно много готовых драйверов можно найти на GitHub, например, вы можете использовать и мои репозитории на GitHub

В конце концов, я отказался от Arduino, и полностью перешел на ESP-IDF. Но это тема уже другой статьи…

 

Ссылки для изучения FreeRTOS:

В процессе поисков по интернету для себя я отобрал следующие ресурсы, которыми пользуюсь постоянно:

  1. Документация по ESP-IDF
  2. Андрей Курниц “FreeRTOS — операционная система для микроконтроллеров”
  3. FreeRTOS — практическое применение
  4. Справочник по Free RTOS API

Пожалуйста, оцените статью:
[ 5 из 5, всего 1 оценок ]

4 комментария для “Особенности программирования ESP32 из Arduino IDE”

  1. Александр

    Как раз выясняю недостатки esp32, насколько они критичны для меня. Даже в 2022 говорят, он сырой для начинающих. Эта заметка приводит аргументы за esp32. Что в общем обнадёживает. Спасибо, автор. Хочется сразу начинать с более продвинутого железа, и будет стимул научиться писать драйвер

    1. Не согласен с тем, что он “сырой”. Версия iDF уже пятая вышла, детских болезней уже почти нет, сейчас в основном появляется. Я вот тоже жду, когда мою issue (просьбу – доработку) наконец протестируют и выпустят в master.
      Я думаю, что он слишком сложен для начинающих, вот это вполне может быть. После примитивной Arduino все эти задачи, потоки, мьютексы и прочая прочая прочая…
      Я осваивал микроконтроллеры после многих лет программирования “больших” компьютеров. Когда уже достаточно хорошо владел многопоточностью. И после всего этого Arduino мне был как детский велосипедик после авто. Не хватало много. Наверное это было основной причиной того, что я почти не задумываясь, перешел на ESP32 и ESP-IDF.

  2. Аркадий

    К сожалению, обнаружил для себя ещё один минус ESP-IDF – он, как и FreeRTOS написан на чистом Си, без плюсов, а значит и без ООП и прочих фишек С++. Почему-то компилятор не знает слова class. Таким образом, здесь я не могу использовать свои “классные” программные наработки для других типов микроконтроллеров.
    А в остальном, конечно, материал, который вы даёте в своих статьях достоин самой высокой оценки.

    1. Это 100% не так! С++ отлично поддерживается.
      Пруфы у меня на гитхабе – половина моих библиотек это классы.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *