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

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

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

ESP32-DevKitC

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

Перед началом программирования 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 к менеджеру плат

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

Нужна ли FreeRTOS для ESP32?

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

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

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

#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 IDE (то есть написанный без использования задач) – это всего лишь задача FreeRTOS, которая выполняется на втором ядре процессора #1, и которой выделено всего 8192 байт стека! Сама FreeRTOS при этом выполняется на ядре процессора #0. Это и хорошо, и плохо. Хорошо, что на ESP32 под Arduino обслуживание самого контроллера, WiFi и прочие служебные надобности теперь никак не пересекаются с Вашим скетчем. Теперь даже если скетч зависнет или уйдет в Sleep на столетие, то это никак не повлияет на работу служебных подпрограмм. А плохо тем, что Ваш скетч получит только доступ только к ограниченному объему стека, остальная доступная память просто не будет использоваться. 8192 – это далеко не весь стек, что доступен задачам FreeRTOS, на одном из моих контроллеров работает еще с десяток параллельных задач (каждый со своим выделенным стеком) и память еще не закончилась.

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

Использование Arduino Time Library

Весьма неприятной неожиданностью, с которой я столкнулся на ESP32, стала невозможность использования библиотеки Arduino Time Library (Time) из публичного каталога библиотек Arduino. Эту библиотеку я использовал для получения времени – даты и синхронизации времени с NTP-серверами. Сама по себе библиотека прекрасно компилируется под ESP32 и вроде бы работает. Но нормально работает в том случае, если Вы используете на ESP32 только одну единственную задачу. В противном случае может легко случиться ситуация, когда несколько задач попытаются одновременно получить время и, если при этом запустится синхронизацию с NTP, то это почти гарантированно приведет к зависанию контроллера.

Выход из данной ситуации я вижу в использовании “встроенных” в API функций даты – времени, например: 

// Запуск синхронизации с SNTP
if(sntp_enabled()){
sntp_stop();
}
sntp_setoperatingmode(SNTP_OPMODE_POLL);
sntp_setservername(0, sntpServer1);
sntp_setservername(1, sntpServer2);
sntp_setservername(2, sntpServer3);
sntp_setservername(3, sntpServer4);
sntp_setservername(4, sntpServer5);
sntp_init();

Потом синхронизацию выполняет уже ядро FreeRTOS самостоятельно. С одной стороны, синхронизация через встроенные в API функции даже проще, но теперь придется перевисать все функции, которые были в Time: now(), day() и т.д. Увы, из-за недостатка времени этот вопрос до конца для себя я так и не решил. В процессе….

Ping на ESP32

Организовать проверку доступности серверов на ESP32 также, как и в случае с временем, достаточно просто организовать с помощью встроенных в API функций. Увы, но информации в сети об этом практически нет, и мне пришлось потратить много времени, чтобы запустить этот процесс. Вот пример:

#include "esp_ping.h"
#include "ping/ping.h"

static esp_err_t pingerOnReceive(ping_target_id_t msgType, esp_ping_found * pf)
{
  #if defined(rSerialDebug)
  Serial.printf("PING :: Ping statistics: sent = %d, recieved = %d, lost = %d, resp time = %d ms, avg = %.1f ms, total time = %d ms\n", 
    pf->send_count, pf->recv_count, pf->timeout_count, pf->resp_time, (float)pf->total_time/pf->recv_count, pf->total_time);
  #endif
if (pf->send_count == pingLastCount) { pingOnEnd(pf); } else pingLastCount = pf->send_count; return ESP_OK; };
void pingOnEnd(esp_ping_found * pf) { // Расситываем потери и среднее время float pingLoss = 100; float pingAvgTime = 0; if(pf->recv_count > 0) { pingLoss = (pf->send_count - pf->recv_count) * 100 / pf->send_count; pingAvgTime = (float)pf->total_time/pf->recv_count; };
bool pingerPing(const char* hostName, const int count) { IPAddress hostIP; ip4_addr_t pingTarget; struct sockaddr_in address;
WiFi.hostByName(hostName, hostIP); address.sin_addr.s_addr = hostIP; pingTarget.addr = address.sin_addr.s_addr; pingLastCount = 0; uint32_t pingCount = count; uint32_t pingTimeout = 3000; uint32_t pingDelay = 500;
esp_ping_set_target(PING_TARGET_RES_RESET, NULL, 0); esp_ping_set_target(PING_TARGET_RCV_TIMEO, &pingTimeout, sizeof(uint32_t)); esp_ping_set_target(PING_TARGET_DELAY_TIME, &pingDelay, sizeof(uint32_t)); esp_ping_set_target(PING_TARGET_IP_ADDRESS, &pingTarget, sizeof(uint32_t)); esp_ping_set_target(PING_TARGET_IP_ADDRESS_COUNT, &pingCount, sizeof(uint32_t)); esp_ping_set_target(PING_TARGET_RES_FN, (void*)&pingerOnReceive, 0); ping_init();
return true; }

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

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

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

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

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