В этом руководстве мы рассмотрим, как отправлять данные с ESP8266 NodeMCU на несколько плат ESP8266 (конфигурация «one-master-multi-slave») с помощью протокола ESP-NOW. Платы будем программировать используя среду разработки Arduino.
Обзор
- Одна плата ESP8266 в роли отправителя;
- Несколько плат ESP8266 в роли приемников. В данном руководстве данные отправляются одновременно на две ESP8266, но вы, конечно, можете добавить и больше;
- Плата-отправитель получает подтверждение, если сообщения успешно доставлены, то есть, мы знаем, какие платы получили данные, а какие нет;
- Необходимо немного изменить код приемника в зависимости от того, какая у вас плата – ESP32 или ESP8266;
- В данном руководстве мы будем обмениваться случайными значениями между платами. Вы можете изменить код, чтобы отправлять команды или показания датчиков.
В этом руководстве рассматриваются две задачи:
- отправка одного и того же сообщения на все платы;
- отправка разных сообщений на каждую плату.
Что нам нужно?
Мы запрограммируем платы ESP8266 в среде разработки Arduino, поэтому перед тем, как продолжить, убедитесь, что эти платы установлены в вашей Arduino IDE.
Вам также понадобится несколько плат ESP8266.
Получение MAC-адреса платы
Чтобы отправлять сообщения через ESP-NOW, вам нужно знать MAC-адрес приемных плат. Каждая плата имеет уникальный MAC-адрес
Загрузите следующий код на каждую плату-приемник, чтобы получить их MAC-адреса.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#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 по протоколу ESP-NOW. В коде нужно указать MAC-адрес ваших плат-приемников, а также добавить или удалить строки кода в зависимости от количества плат-приемников.
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 83 84 85 |
/* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp-now-one-to-many-esp8266-nodemcu/ 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 <ESP8266WiFi.h> #include <espnow.h> // REPLACE WITH RECEIVER MAC Address uint8_t broadcastAddress1[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; uint8_t broadcastAddress2[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // Structure example to send data // Must match the receiver structure typedef struct test_struct { int x; int y; } test_struct; // Create a struct_message called test to store variables to be sent test_struct test; unsigned long lastTime = 0; unsigned long timerDelay = 2000; // send readings timer // Callback when data is sent void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) { char macStr[18]; Serial.print("Packet to:"); snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); Serial.print(macStr); Serial.print(" send status: "); if (sendStatus == 0){ Serial.println("Delivery success"); } else{ Serial.println("Delivery fail"); } } void setup() { // Init Serial Monitor Serial.begin(115200); // Set device as a Wi-Fi Station WiFi.mode(WIFI_STA); WiFi.disconnect(); // Init ESP-NOW if (esp_now_init() != 0) { Serial.println("Error initializing ESP-NOW"); return; } esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER); // Once ESPNow is successfully Init, we will register for Send CB to // get the status of Trasnmitted packet esp_now_register_send_cb(OnDataSent); // Register peer esp_now_add_peer(broadcastAddress1, ESP_NOW_ROLE_SLAVE, 1, NULL, 0); esp_now_add_peer(broadcastAddress2, ESP_NOW_ROLE_SLAVE, 1, NULL, 0); } void loop() { if ((millis() - lastTime) > timerDelay) { // Set values to send test.x = random(1, 50); test.y = random(1, 50); // Send message via ESP-NOW esp_now_send(0, (uint8_t *) &test, sizeof(test)); lastTime = millis(); } } |
Как работает код?
Подключаем библиотеки espnow.h и ESP8266WiFi.h
1 2 |
#include <ESP8266WiFi.h>; #include <espnow.h>; |
Записываем МАС-адреса получателей
В нашем случае – два получателя.
1 2 |
uint8_t broadcastAddress1[] = {0x5C, 0xCF, 0x7F, 0x99, 0xA1, 0x70}; uint8_t broadcastAddress2[] = {0x5C, 0xCF, 0x7F, 0x99, 0x9A, 0xEA}; |
Затем создаем структуру, в которой находятся наши данные. Мы ее назвали test_struct и она содержит две целочисленные переменные.
1 2 3 4 |
typedef struct test_struct { int x; int y; } test_struct; |
Создаем новую переменную в test_struct и называем ее test, в ней будут храниться значения переменных.
1 |
test_struct test; |
Функция OnDataSent()
Определяем функцию OnDataSent().Она будет вызываться при отправке сообщения. В данном случае функция показывает отправлено ли сообщение и на какой МАС-адрес. Таким образом, мы знаем какая плата получила сообщение.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) { char macStr[18]; Serial.print("Packet to:"); snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); Serial.print(macStr); Serial.print(" send status: "); if (sendStatus == 0){ Serial.println("Delivery success"); } else{ Serial.println("Delivery fail"); } } |
setup()
В функции setup(), запускаем монитор порта для отладки:
1 |
Serial.begin(115200); |
Выставляем режим клиента WiFi:
1 2 |
WiFi.mode(WIFI_STA); WiFi.disconnect(); |
Запускаем протокол ESP-NOW:
1 2 3 4 |
if (esp_now_init() != ESP_OK) { Serial.println("Error initializing ESP-NOW"); return; } |
Указываем назначение платы. В этом случае ESP8266 – отправитель, поэтому указываем:
1 |
esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER); |
После инициализации протокола определяем действия при отправке. В данном случае указываем ранее созданную функцию OnDataSent().
1 |
esp_now_register_send_cb(OnDataSent); |
Добавляем пиры
1 2 |
esp_now_add_peer(broadcastAddress1, ESP_NOW_ROLE_SLAVE, 1, NULL, 0); esp_now_add_peer(broadcastAddress2, ESP_NOW_ROLE_SLAVE, 1, NULL, 0); |
Если у вас больше плат – скопируйте строки выше и укажите ее МАС-адрес:
loop()
В цикле loop(), каждые две секунды отправляем сообщение (время задержки можно изменить).
Присваиваем значение каждой переменной:
1 2 |
test.x = random(0,20); test.y = random(0,20); |
Помните, что test является структурой. Здесь присваиваем значения, которые нужно отправить в структуру. В данном случае мы просто отправляем рандомные числа. На практике эти данные можно заменить, скажем, показаниями датчиков.
Отправка одинаковых данных на несколько плат
Наконец, отправляем следующее сообщение:
1 |
esp_now_send(0, (uint8_t *) &test, sizeof(test)); |
Первым аргументом функции esp_now_send () является MAC-адрес получателя. Если вы передадите 0 в качестве аргумента, плата отправит одно и то же сообщение всем зарегистрированным пирам. Проверяем, успешно ли отправилось сообщение:
Цикл loop() выполняется каждые 2000мс (2 секунды)
1 2 3 4 5 6 7 |
if ((millis() - lastTime) > timerDelay) { test.x = random(1, 50); test.y = random(1, 50); esp_now_send(0, (uint8_t *) &test, sizeof(test)); lastTime = millis(); } |
Отправка разных данных на платы
Данный код очень похож на предыдущий за исключением нескольких моментов:
Если вы хотите отправлять разные данные, нужно создать несколько структур, например:
1 2 |
test_struct test; test_struct test2; |
В данном случае структура одного вида (test_struct), однако, возможно отправлять и структуры разных видов.
Далее, присваиваем рандомные числа переменным их наших структур.
1 2 3 4 |
test.x = random(0,20); test.y = random(0,20); test2.x = random(0,20); test2.y = random(0,20); |
И наконец, нужно вызвать функцию esp_now_send() для каждого получателя.
Для примера, отправляем данные из структуры на плату с MAC-адресом broadcastAddress1.
1 |
esp_now_send(broadcastAddress1, (uint8_t *) &test, sizeof(test)); |
Отправляем данные из test2 на вторую плату:
1 2 |
esp_err_t result2 = esp_now_send( esp_now_send(broadcastAddress2, (uint8_t *) &test, sizeof(test2)); |
Далее представлен полный код для отправки разных сообщений на платы.
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 |
/********* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp-now-one-to-many-esp32-esp8266/ 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 <ESP8266WiFi.h> #include <espnow.h> // ЗАМЕНИТЕ МАС-АДРЕСАМИ ПЛАТ-ПОЛУЧАТЕЛЕЙ uint8_t broadcastAddress1[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; uint8_t broadcastAddress2[] = {0xFF, , , , , }; typedef struct test_struct { int x; int y; } test_struct; test_struct test; test_struct test2; unsigned long lastTime = 0; unsigned long timerDelay = 2000; // таймер задержки void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) { char macStr[18]; Serial.print("Packet to:"); snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); Serial.print(macStr); Serial.print(" send status: "); if (sendStatus == 0){ Serial.println("Delivery success"); } else{ Serial.println("Delivery fail"); } } void setup() { Serial.begin(115200); WiFi.mode(WIFI_STA); WiFi.disconnect(); if (esp_now_init() != 0) { Serial.println("Error initializing ESP-NOW"); return; } esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER); esp_now_register_send_cb(OnDataSent); // регистрируем пиры esp_now_add_peer(broadcastAddress1, ESP_NOW_ROLE_SLAVE, 1, NULL, 0); esp_now_add_peer(broadcastAddress2, ESP_NOW_ROLE_SLAVE, 1, NULL, 0); } void loop() { if ((millis() - lastTime) > timerDelay) { test.x = random(1, 50); test.y = random(1, 50); test2.x = random(1, 50); test2.y = random(1, 50); // отправляем сообщения esp_now_send(broadcastAddress1, (uint8_t *) &test, sizeof(test)); esp_now_send(broadcastAddress2, (uint8_t *) &test2, sizeof(test2)); lastTime = millis(); } } |
Код для платы-приемника
Загрузите следующий код на платы-приемники (в нашем случае используются две платы-приемника).
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 |
/* Rui Santos Complete project details at https://RandomNerdTutorials.com/esp-now-one-to-many-esp8266-nodemcu/ 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 <ESP8266WiFi.h> #include <espnow.h> // Структура должна совпадать с такой же у отправителя typedef struct test_struct { int x; int y; } test_struct; // создаем объект myData test_struct myData; // Callback-функция, вызываемая при получении сообщения void OnDataRecv(uint8_t * mac, uint8_t *incomingData, uint8_t len) { memcpy(&myData, incomingData, sizeof(myData)); Serial.print("Bytes received: "); Serial.println(len); Serial.print("x: "); Serial.println(myData.x); Serial.print("y: "); Serial.println(myData.y); Serial.println(); } void setup() { // Запускаем монитор порта Serial.begin(115200); // Выставляем режим работы WiFi WiFi.mode(WIFI_STA); WiFi.disconnect(); // Инициализируем протокол ESP-NOW if (esp_now_init() != 0) { Serial.println("Error initializing ESP-NOW"); return; } esp_now_set_self_role(ESP_NOW_ROLE_SLAVE); esp_now_register_recv_cb(OnDataRecv); } void loop() { } |
Как работает код?
Аналогично отправителю, сначала подключаем библиотеки:
1 2 |
#include <ESP8266WiFi.h> #include <espnow.h> |
Создаем структуру для получения данных. Она должна быть идентичной той, что содержится в коде платы-отправителя.
1 2 3 4 |
typedef struct test_struct { int x; int y; } test_struct; |
Создаем переменную test_struct с названием myData.
1 |
test_struct myData; |
Определяем callback-функцию, которая вызывается при получении сообщения по протоколу ESP-NOW. У функции onDataRecv() должны быть следующие параметры:
1 |
void OnDataRecv(uint8_t * mac, uint8_t *incomingData, uint8_t len) { |
Копируем данные из переменной incomingData и записываем в myData.
1 |
memcpy(&myData, incomingData, sizeof(myData)); |
Итак, структура myData содержит несколько переменных со значениями, полученных от платы-отправителя. Для доступа, например к переменной x, вызовите функцию myData.x.
1 2 3 4 5 6 7 |
Serial.print("Bytes received: "); Serial.println(len); Serial.print("x: "); Serial.println(myData.x); Serial.print("y: "); Serial.println(myData.y); Serial.println(); |
В функции setup(), запускаем монитор порта.
1 |
Serial.begin(115200); |
Выставляем режим клиента WiFi.
1 2 |
WiFi.mode(WIFI_STA); WiFi.disconnect(); |
Запускаем протокол ESP-NOW:
1 2 3 4 |
if (esp_now_init() != 0) { Serial.println("Error initializing ESP-NOW"); return; } |
Указываем, что ESP8266 – получатель.
1 |
esp_now_set_self_role(ESP_NOW_ROLE_SLAVE); |
Регистрируем callback-функцию. В данном случае – OnDataRecv(), которую мы создали ранее.
1 |
esp_now_register_recv_cb(OnDataRecv); |
Демонстрация
Включите все платы, откройте монитор порта Arduino для COM-порта, к которому подключен отправитель.
Должны начать поступать сообщения «Delivery Success» с соответствующим MAC-адресом получателя в мониторе порта.
Если вы отключите питание одной из плат, то получите сообщение «Delivery fail» для этой платы.
Если вы хотите проверить, действительно ли платы получают сообщения, можете открыть монитор порта для COM-порта, к которому они подключены, или использовать PuTTY для установки последовательной связи с вашими платами.
Если вы используете PuTTY, выберите «Последовательная связь», введите номер COM-порта и скорость передачи (115200), как показано ниже, и нажмите «Открыть».
Затем вы должны увидеть получаемые сообщения.
Повторите эти действия для каждой из плат и убедитесь, что они получают сообщения.
Заключение
В этом руководстве вы узнали, как отправлять данные на несколько плат ESP8266 с помощью ESP-NOW (конфигурация один-ко-многим). Используйте данное руководство как базу для создания более серьезных проектов.
Вопросы по прошивке и работе с кодом лучше писать напрямую автору в комментариях к статье (на англ. языке)
8 комментариев. Оставить новый
Привет! Исправьте здесь на странице ошибку в коде:
#include <esp_now.h>;
#include ;
Очевидно, что здесь конфликт HTML-разметки и исходного кода. Должны быть кавычки.
Здравствуйте! Спасибо, ошибки подправили!
Код для платы-отправителя неверный. В этой статье приведен код для ESP32. Однако, в статье-первоисточнике, на этом же месте другой, правильный код для ESP8266: https://randomnerdtutorials.com/esp-now-one-to-many-esp8266-nodemcu/
Код также заменили на корректный, спасибо за фидбек!
Здравствуйте. На плате – приёмнике не работает код из loop при работе esp-now, это баг илифича?)
Здравствуйте! В данном примере функция
OnDataRecv()
является callback-функцией для приема данных, которая регистрируется с помощьюesp_now_register_recv_cb()
. Эта функция вызывается автоматически при получении сообщения. Внутри функцииOnDataRecv()
полученные данные копируются в переменную myData, а затем выводятся в последовательный порт.Функция
loop()
в данном примере остаётся пустой, но она все еще может быть использована для других задач, не связанных с ESP-NOW.В моём случае код, имеющийся в loop, не имеющий отношения к esp-now, опрашивающий аналоговый порт 13 не исполняется, однако, код, выводящий стрелку спидометра, положение которой зависит от состояния аналоговых данных порта 13, рисуется.
Прошу прощения, нашол в даташите: “При использовании WiFi для аналоговых входов используйте пины 32, 33, 34, 35, 36, 39. Остальные могут вызвать проблемы.”