Посмотр рубрик

Отличия ESP от Arduino

ESP8266 и ESP32 поддерживают Arduino-фреймворк, но с некоторыми отличиями и добавлениями. Рассмотрим некоторые из них.

Математика #

Деление на 0 #

Целочисленное деление на 0 имеет неопределённое поведение и на ESP часто приводит к исключению и перезагрузке. Деление чисел с плавающей точкой обычно даёт бесконечность или NaN, но полагаться на это вместо проверки аргументов не стоит.

Типы данных #

  • int, size_t - 32 бит
  • double - 64 бит
  • Указатель/ссылка - 32 бит
  • Выравнивание памяти - 4 байта

При передаче данных между ESP и условно AVR, где int 16 битный, лучше использовать явные типы данных - short/long, а лучше int16_t/int32_t и так далее

Arduino-функции #

map() #

В функции map(val, min, max, to_min, to_max) нет защиты от деления на 0, поэтому при равных min и max получается неопределённое поведение. Если границы задаются внешними условиями - проверяйте их равенство вручную.

min() и max() #

Функции min() и max() реализованы как функции, а не как макросы, поэтому должны использоваться с данными одного типа. Использование переменных разного типа приведёт к ошибке компиляции:

unsigned long v;
v = max(v, 123);    // ошибка
v = max(v, 123ul);  // ОК

analogWrite #

В старом Arduino-ESP32 2.x встречалось поведение, при котором после analogWrite() пин не возвращался к обычному GPIO через digitalWrite(). Для отсоединения LEDC использовалась функция ledcDetachPin(pin). В Arduino-ESP32 3.x API LEDC изменён, а analogWrite() заявлен как совместимый с Arduino, поэтому этот обходной путь относится именно к старому ядру.

printf #

В отличие от классических Arduino, в ESP к классу Print добавлен метод printf - форматированный вывод (см. урок про него). Это означает, что можно печатать с форматированием в разные интерфейсы, например выводить в монитор порта как

Serial.printf("Hello %d world", 12);    // Hello 12 world

Прерывания #

  • Номера прерываний соответствуют номерам GPIO
  • В обработчике нельзя использовать задержки и отправку по интерфейсам связи (Serial, SPI...)
  • В обработчике нельзя использовать динамическое выделение и перераспределение памяти (new, malloc, realloc), соответственно работать со String тоже нельзя
  • Функция-обработчик должна быть объявлена с атрибутом IRAM_ATTR, он размещает код в оперативной памяти
void IRAM_ATTR myIsr() {}

void setup() {
    attachInterrupt(digitalPinToInterrupt(1), myIsr, RISING);   // на GPIO 1
}

Есть ещё атрибут DRAM_ATTR для данных, к которым обработчик должен обращаться из RAM. Сам IRAM_ATTR переносит код функции, но не гарантирует размещение используемых ею констант в RAM

EEPROM #

Основной урок про EEPROM

EEPROM является эмуляцией из Flash памяти, поэтому мы можем выбрать нужный размер:

  • Перед началом работы нужно вызвать EEPROM.begin(4.. 4096) с указанием размера области памяти в байтах
  • Для применения изменений в памяти нужно вызвать EEPROM.commit()
  • В некоторых версиях SDK отсутствует EEPROM.update() и EEPROM.length()
  • У Flash-памяти ограничен ресурс стирания, типично от десятков тысяч циклов на сектор, но точное значение нужно смотреть в документации конкретного чипа

На ESP8266 после EEPROM.begin(4.. 4096) выбранная область дублируется в оперативной памяти, а EEPROM.commit() при изменениях перезаписывает сектор Flash размером 4 кБ. Часто вызывать commit() не стоит. Реализация и допустимый размер на ESP32 отличаются, а сама библиотека EEPROM там считается устаревшей в пользу Preferences.

Вместо EEPROM используйте библиотеку FileData для удобного хранения любых данных в файловой системе

PROGMEM #

Основной урок про PROGMEM

ESP8266 #

PROGMEM работает как на AVR, без него данные могут располагаться в оперативной памяти. Для совместимости есть даже _P и pgm_-функции из avrlibs.

ESP32 #

Обычные глобальные const-данные размещаются во Flash и доступны через общее адресное пространство. Arduino PROGMEM API поддерживается для совместимости со скетчами AVR/ESP8266, но отдельные функции чтения здесь обычно не нужны.

UART/Serial #

Основной урок про Serial

ESP8266 #

Имеет 1.5 (полтора) аппаратных UART:

  • Serial (UART0) - подключен к USB-UART для прошивки и отладки
    • RX - 3
    • TX - 1
    • При вызове Serial.swap() (после Serial.begin()) пины переместятся на TX 15 и RX 13. После повторного вызова - обратно
  • Serial1 - доступен только на отправку
    • RX - нет
    • TX - 2

ESP32 #

Имеет несколько аппаратных UART в зависимости от модели чипа и/или платы, стандартные пины также зависят от модели и версии ядра ESP32 Arduino. Пины можно переназначить на любые другие (кроме системных, SPI Flash, а также внимательнее к тем которые только для чтения):

void begin(uint32_t baud);  // стандартные пины
void begin(uint32_t baud, uint32_t config = SERIAL_8N1, int8_t rxPin, int8_t txPin);    // свои пины
void setup() {
    Serial.begin(9600, SERIAL_8N1, 1, 2);
    Serial1.begin(9600, SERIAL_8N1, 3, 4);
    Serial2.begin(115200, SERIAL_8N1, 5, 6);
}

Посмотреть стандартный номер пина можно в константах RX, TX, RX1, TX1 и так далее. Верить распиновкам и описаниям на различных сайтах не рекомендуется, т.к. пины могут не соответствовать выбранной плате и вашей версии ядра.

I2C/Wire #

Основной урок про Wire

ESP8266 #

Используется программная реализация I2C, стандартные пины как на Arduino Nano:

  • SDA - 4
  • SCL - 5

При запуске можно переназначить пины на любые другие:

void begin();   // стандартные пины
void begin(int sda, int scl);   // свои пины
void begin(int sda, int scl, uint8_t addr); // свои пины, свой адрес (режим slave)

ESP32 #

Имеет 2 аппаратных I2C: системные объекты Wire и Wire1. При вызове begin() устанавливаются стандартные пины:

  • SDA - 21
  • SCL - 22

При запуске можно переназначить пины на любые другие (кроме системных, SPI Flash, а также внимательнее к тем которые только для чтения). Порядок аргументов отличается от ESP8266:

void begin();   // стандартные пины
bool begin(int sda, int scl, uint32_t frequency = 0);   // свои пины, частота
bool begin(uint8_t addr, int sda, int scl, uint32_t frequency); // адрес (slave), пины, частота

Для использования Wire1 нужно обязательно указать ему пины!

void setup() {
    Wire.begin();           // Wire на пинах 21 22
    Wire1.begin(25, 26);    // Wire1 на пинах 25 26
}

SPI #

Основной урок про SPI

ESP8266 #

Один аппаратный SPI, пины привязаны к стандартным:

MISO MOSI SCK SS
12 13 14 15

Пином выбора ведомого устройства нужно управлять как обычным выходом. В отличие от AVR, вход SS на ESP8266 не переключает весь контроллер SPI в Slave-режим.

void setup() {
    SPI.begin();
    pinMode(SS, OUTPUT);
}

ESP32 #

Несколько аппаратных SPI, но доступны только два - HSPI и VSPI, их пины по умолчанию (см. распиновку конкретной платы, на некоторых моделях пины отличаются):

SPI MISO MOSI SCK SS
VSPI 19 23 18 5
HSPI 12 13 14 15

Системный объект SPI - это VSPI. При вызове begin() будет работать на указанных выше пинах. Пины можно указать любые другие (кроме системных, SPI Flash, а также внимательнее к тем которые только для чтения):

void begin();   // запуск на стандартных пинах
void begin(int8_t sck, int8_t miso, int8_t mosi, int8_t ss);    // свои пины

Для использования второго SPI (HSPI) нужно самостоятельно создать объект класса SPIClass и передать ему константу HSPI:

SPIClass hSPI(HSPI);    // создание HSPI

// точно так же можно создать VSPI - "синоним" системного объекта SPI
// SPIClass vSPI(VSPI);     // == SPI

void setup() {
    // VSPI, стандартные пины
    SPI.begin();

    // VSPI, свои пины
    SPI.begin(18, 19, 23, 5);
    pinMode(5, OUTPUT);     // CS выбранного устройства

    // HSPI, стандартные пины
    hSPI.begin();

    // HSPI, свои пины
    hSPI.begin(14, 12, 13, 15);
    pinMode(15, OUTPUT);    // CS выбранного устройства
}

Полезные страницы #


(5 голосов)
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Прокрутить вверх