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()) пины переместятся на TX15и RX13. После повторного вызова - обратно
- RX -
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 выбранного устройства
}
Полезные страницы #
- Набор GyverKIT – наш большой стартовый набор Arduino, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту (alex@alexgyver.ru)
- Поддержать автора за работу над уроками