В данном руководстве рассмотрим как использовать протокол беспроводной связи ESP-NOW для обмена короткими пакетами данных между платами ESP32.
Вы также можете ознакомиться с другими руководствами для ESP-NOW с ESP32:
Знакомство с ESP-NOW
После сопряжения устройств друг с другом соединение остается постоянным, то есть при перезапуске платы автоматически подключаются друг к другу.
ESP-NOW поддерживает следующие функции:
- Зашифрованная и незашифрованная одноадресная передача;
- Смешанные зашифрованные и незашифрованные одноранговые сети;
- Полезная нагрузка может составлять до 250 байт ;
- Выполнение обратных функций, которые могут служить для обратной связи.
Технология ESP-NOW также имеет следующие ограничения:
- В режиме станции поддерживается не более 10 зашифрованных одноранговых узлов; Не более 6 в режиме SoftAP или SoftAP + Station;
- Поддерживается несколько незашифрованных одноранговых узлов, однако их общее количество должно быть не больше 20, включая зашифрованные одноранговые узлы;
- Полезная нагрузка ограничена 250 байтами.
Проще говоря, ESP-NOW – это протокол связи, который можно использовать для обмена небольшими сообщениями (до 250 байт) между платами ESP32.
Односторонняя связь ESP-NOW
Рассмотрим пример, когда плата ESP32 отправляет данные на другую плату ESP32.
Эту конфигурацию очень легко реализовать, и она отлично подходит для отправки данных с одной платы на другую, таких как показания датчиков или команды включения и выключения GPIO.
Отправка данных по структуре one-master-multiple-slaves
Одна плата ESP32 отправляет команды на разные платы ESP32. Эта конфигурация идеальна для создания чего-то вроде пульта дистанционного управления. В доме может быть несколько плат ESP32, которые управляются одной основной платой ESP32.
Отправка данных по структуре one-slave-multi-master
Эта конфигурация подходит, если вы хотите собрать данные с нескольких плат на одну. Это может быть конфигурация веб – сервера для отображения данных из других плат, например:
Примечание: в документации ESP-NOW нет такого понятия, как «sender/master» и «receiver/slave». Каждая плата может быть отправителем и получателем. Однако для ясности мы будем использовать термины «отправитель» и «получатель» или «master» и «slave».
Двусторонняя связь ESP-NOW
С ESP-NOW каждая плата может быть отправителем и получателем одновременно.
Например, у вас может быть две платы, взаимодействующие друг с другом.
Вы можете добавить больше плат в эту конфигурацию и получить нечто похожее на сеть (все платы ESP32 будут взаимодействовать друг с другом).
Таким образом, ESP-NOW также подходит для построения сети, в которой может быть несколько плат ESP32, обменивающихся данными друг с другом.
Получение MAC-адреса платы
Чтобы отправлять сообщения через ESP-NOW, вам нужно знать MAC-адреса плат. Каждая плата имеет уникальный MAC-адрес
Загрузите следующий код на каждую плату-приемник, чтобы получить их MAC-адреса.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
#ifdef ESP32 #include <WiFi.h> #else #include <ESP8266WiFi.h> #endif void setup() { Serial.begin(115200); Serial.println(); Serial.print("ESP Board MAC Address: "); Serial.println(WiFi.macAddress()); } void loop() { } |
После загрузки кода нажмите кнопку RST / EN, и MAC-адрес должен отобразиться в мониторе порта.
Стоит записать MAC-адрес платы на бирке, чтобы было удобнее идентифицировать каждую плату.
Односторонняя связь «точка-точка»
Чтобы ознакомиться с принципом работы беспроводной связи ESP-NOW, создадим простой проект, который будет отправлять сообщение от одной ESP32 к другой. Одна ESP32 будет «отправителем», а другая – «получателем».
Мы отправим переменные типа char , int , float , String и boolean . Поняв как это работает, вы сможете изменить код и отправлять любые типы переменных, подходящие для вашего проекта (например, показания датчиков или логические переменные для включения или выключения чего-либо).
Для лучшего понимания мы будем называть плату-отправителя номером 1 и получателя номером 2.
Вот что мы должны включить в скетч для платы №1:
- Запуск протокола ESP-NOW;
- Создание обратной функции OnDataSent, которая будет выполняться при отправке сообщения.;
- Добавка MAC-адреса получателя.
- Отправка сообщения.
Со стороны платы №2 в скетче должно быть:
- Запуск протокола ESP-NOW;
- Создание обратной функции OnDataRecv, которая будет выполняться при получении сообщения.;
- Внутри этой функции должно быть реализовано сохранение сообщения в переменной, для выполнения задач с этой информацией.
ESP-NOW работает с обратными функциями, которые вызываются, когда устройство получает сообщение или когда сообщение отправлено.
Полезные функции ESP-NOW
Вот краткое о наиболее важных функциях ESP-NOW:
Функция | Название и описание |
---|---|
esp_now_ init() | Инициализирует ESP-NOW. Перед инициализацией ESP-NOW необходимо подключиться к Wi-Fi. |
esp_now_add_ peer() | Требуется, чтобы связать устройство и передать в качестве аргумента MAC-адрес устройства. |
esp_now_ send() | Отправляет данные с помощью ESP-NOW. |
esp_now_register_send_ cb() | Создает обратную функцию, которая вызывается при отправке данных. Когда сообщение отправлено, функция сообщает была ли доставка успешной. |
esp_now_register_rcv_ cb() | Создает обратную функцию, которая вызывается при получении данных. Когда сообщение получено, функция сообщает было ли получение успешным. |
Для получения дополнительной информации об этих функциях прочтите документацию.
Скетч для платы-отправителя ESP32
Вот код для платы-отправителя. Скопируйте код в Arduino IDE, но пока не загружайте его. Вам нужно будет сделать несколько исправлений.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
/* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp-now-esp32-arduino-ide/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #include <esp_now.h> #include <WiFi.h> // ЗАМЕНИТЕ МАС-АДРЕСОМ ПЛАТЫ-ПОЛУЧАТЕЛЯ uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // Структура в скетче платы-отправителя // должна совпадать с оной для получателя typedef struct struct_message { char a[32]; int b; float c; String d; bool e; } struct_message; // Создаем структуру сообщения myData struct_message myData; // Обратная функция отправки void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { Serial.print("\r\nLast Packet Send Status:\t"); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail"); } void setup() { // Запускаем монитор порта Serial.begin(115200); // Выбираем режим WiFi WiFi.mode(WIFI_STA); // Запускаем протокол ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println("Error initializing ESP-NOW"); return; } // Регистрируем отправку сообщения esp_now_register_send_cb(OnDataSent); // Указываем получателя esp_now_peer_info_t peerInfo; memcpy(peerInfo.peer_addr, broadcastAddress, 6); peerInfo.channel = 0; peerInfo.encrypt = false; if (esp_now_add_peer(&peerInfo) != ESP_OK){ Serial.println("Failed to add peer"); return; } } void loop() { // Указываем данные, которые будем отправлять strcpy(myData.a, "THIS IS A CHAR"); myData.b = random(1,20); myData.c = 1.2; myData.d = "Hello"; myData.e = false; // Отправляем сообщение esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData)); if (result == ESP_OK) { Serial.println("Sent with success"); } else { Serial.println("Error sending the data"); } delay(2000); } |
Как работает код?
Сначала подключаем библиотеки esp_now.h и WiFi.h.
1 |
#include"esp_now.h"; |
1 |
#include "WiFi.h"; |
В следующей строке мы должны вставить MAC-адрес получателя ESP32.
1 |
uint8_t broadcastAddress [] = {0x30, 0xAE, 0xA4, 0x07, 0x0D, 0x64}; |
В нашем случае MAC-адрес получателя: 30: AE: A4: 07: 0D: 64, но вам нужно будет заменить эту переменную своим собственным MAC-адресом.
Затем создаём структуру, содержащую тип данных, которые мы хотим отправить. Мы назвали эту структуру struct_message, и она содержит 5 различных типов переменных.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
typedef struct struct_message { char a[32]; int b; float c; String d; bool e; } struct_message; |
Затем создаём новую переменную типа struct_message с именем myData, в которой будут храниться значения переменных.
1 |
struct_message myData; |
Затем вводим функцию OnDataSent (). Она будет выполняться при отправке сообщения. В нашем случае эта функция просто выводит, было ли сообщение успешно доставлено.
1 2 3 4 5 6 7 |
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { Serial.print("\r\nLast Packet Send Status:\t"); Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail"); } |
В функции setup () инициализируем монитор порта для отладки:
1 |
Serial.begin (115200); |
Устанавливаем режим Wi-Fi:
1 |
WiFi.mode (WIFI_STA); |
Запускаем ESP-NOW:
1 2 3 4 5 6 7 |
if (esp_now_init() != ESP_OK) { Serial.println("Error initializing ESP-NOW"); return; } |
После успешной инициализации ESP-NOW вводим обратную функцию OnDataSent ().
1 |
esp_now_register_send_cb (OnDataSent); |
После этого нам нужно выполнить сопряжение с другим устройством для отправки данных. Вот что мы делаем в следующих строках:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// Зарегистрируем получателя esp_now_peer_info_t peerInfo; memcpy(peerInfo.peer_addr, broadcastAddress, 6); peerInfo.channel = 0; peerInfo.encrypt = false; // Добавляем пира if (esp_now_add_peer(&peerInfo) != ESP_OK){ Serial.println("Failed to add peer"); return; } |
В loop() мы будем отправлять сообщение через ESP-NOW каждые 2 секунды (вы можете изменить время задержки).
Сначала мы устанавливаем значения переменных следующим образом:
1 2 3 4 5 6 7 8 9 |
strcpy(myData.a, "THIS IS A CHAR"); myData.b = random(1,20); myData.c = 1.2; myData.d = "Hello"; myData.e = false; |
Помните, что myData – это структура. Здесь мы назначаем значения, которые хотим отправить внутри структуры. Например, первая строка назначает char, вторая строка назначает случайное число Int, Float, String и логическую переменную.
Мы создаем такую структуру, чтобы показать, как отправлять наиболее распространенные типы переменных.
Наконец, отправляем сообщение следующим образом:
1 |
esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData)); |
Проверяем, успешно ли было отправлено сообщение:
1 2 3 4 5 6 7 8 9 10 11 |
if (result == ESP_OK) { Serial.println("Sent with success"); } else { Serial.println("Error sending the data"); } |
loop () выполняется каждые 2000 миллисекунд (2 секунды).
1 |
delay(2000); |
Скетч для платы-получателя
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
/* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp-now-esp32-arduino-ide/ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. */ #include <esp_now.h> #include <WiFi.h> // Структура должна совпадать со структурой // на плате-отправителе typedef struct struct_message { char a[32]; int b; float c; String d; bool e; } struct_message; // Создаем myData struct_message myData; // Обратная функция при получении void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) { memcpy(&myData, incomingData, sizeof(myData)); Serial.print("Bytes received: "); Serial.println(len); Serial.print("Char: "); Serial.println(myData.a); Serial.print("Int: "); Serial.println(myData.b); Serial.print("Float: "); Serial.println(myData.c); Serial.print("String: "); Serial.println(myData.d); Serial.print("Bool: "); Serial.println(myData.e); Serial.println(); } void setup() { // Запускаем монитор порта Serial.begin(115200); // Выставляем режим работы WiFi WiFi.mode(WIFI_STA); ESP_ // Запускаем протокол ESP-NOW if (esp_now_init() != ESP_OK) { Serial.println("Error initializing ESP-NOW"); return; } // Получаем состояние отправки esp_now_register_recv_cb(OnDataRecv); } void loop() { } |
Как работает код?
Как и на отправителе, начинаем с подключения библиотек:
1 2 3 |
#include "esp_now.h"; #include "WiFi.h"; |
Создаем такую же как и на отправителе структуру.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
typedef struct struct_message { char a[32]; int b; float c; String d; bool e; } struct_message; |
Создаем переменную myData.
1 |
struct_message myData; |
Создаем обратную функцию onDataRecv () со следующими параметрами:
1 |
void OnDataRecv (const uint8_t * mac, const uint8_t * incomingData, int len) { |
Копируем содержимое переменной данных incomingData в переменную myData.
1 |
memcpy (& myData, incomingData, sizeof (myData)); |
Теперь структура myData содержит несколько переменных со значениями, отправленными другой платой. Например, чтобы получить доступ к переменной a, нам нужно вызвать myData.a.
В этом примере мы просто выводим полученные данные.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
Serial.print("Bytes received: "); Serial.println(len); Serial.print("Char: "); Serial.println(myData.a); Serial.print("Int: "); Serial.println(myData.b); Serial.print("Float: "); Serial.println(myData.c); Serial.print("String: "); Serial.println(myData.d); Serial.print("Bool: "); Serial.println(myData.e); Serial.println(); } |
В setup () запускаем монитор порта.
1 |
Serial.begin (115200); |
Выставляем режим работы WiFi
1 |
WiFi.mode (WIFI_STA); |
Запускаем ESP-NOW:
1 2 3 4 5 6 7 |
if (esp_now_init() != ESP_OK) { Serial.println("Error initializing ESP-NOW"); return; } |
Используем функцию OnDataRecv (), которая была создана ранее.
1 |
esp_now_register_recv_cb (OnDataRecv); |
Демонстрация
Загрузите скетчи на платы ESP32 отправителя, откройте два окна в среде Arduino. Один для получателя, а другой для отправителя. Откройте монитор порта для каждой платы. Для каждой платы должен быть свой COM-порт.
Это то, что вы должны получить на стороне отправителя.
И это то, что вы должны получить на стороне приемника. Обратите внимание, что переменная Int изменяется при каждом запуске (потому что мы отправляем рандомное число).
Заключение
Мы постарались сделать примеры максимально простыми, чтобы вы лучше понимали, как все работает. Есть много функций, связанных с ESP-NOW, которые могут быть полезны в ваших проектах, например: управление и удаление пиров, сканирование ведомых устройств и так далее Чтобы просмотреть их все в Arduino IDE перейдите Файл > Примеры > ESP32 > ESPNow и выберите один из скетчей.
Кроме того, с ESP-NOW каждая плата может быть отправителем и получателем одновременно, и одна плата может отправлять данные на несколько плат, а также получать данные с нескольких плат. Эти темы будут рассмотрены в будущих руководствах, так что следите за обновлениями.
Кстати, максимальное расстояние, при котором можно установить стабильное соединение между платами – 220 метров (без препятствий)
Надеемся, вам понравилось.
Вопросы по прошивке и работе с кодом лучше писать напрямую автору в комментариях к статье (на англ. языке)
3 комментария. Оставить новый
Привет! Исправьте ошибку:
#endifvoid
Здесь void склеилось c endif. Должно быть:
#endif
void
Код получения Мас адреса выводит нули, чтобы заработало:
Необходимо выполнить в void setup() WiFi.begin(“YouLogin, “YouPass”);
и Serial.println(WiFi.macAddress());
Здравствуйте!
MAC-адрес не зависит от того, подключена ESP32 к Wi-Fi или нет. Он хранится в памяти микроконтроллера и может быть получен без подключения ESP32 к Wi-Fi.