ESP-NOW: Двусторонняя связь между платами ESP32
В этом руководстве мы покажем, как установить двустороннюю связь между двумя платами ESP32 с использованием протокола связи ESP-NOW. В качестве примера две платы ESP32 будут обмениваться показаниями датчиков (с дальностью связи на открытой местности до 220 метров ~ 722 фута).
Смотрите видеовведение
Для ознакомления с протоколом ESP-NOW вы можете посмотреть следующее видео:
Если вы хотите узнать больше о ESP-NOW, вы можете прочитать это руководство: Начало работы с ESP-NOW (ESP32 с Arduino IDE).
Введение в ESP-NOW
ESP-NOW — это бесконнекционный протокол связи, разработанный Espressif, который обеспечивает передачу коротких пакетов. Этот протокол позволяет нескольким устройствам общаться друг с другом без использования Wi-Fi.
Это быстрый протокол связи, который можно использовать для обмена небольшими сообщениями (до 250 байт) между платами ESP32. ESP-NOW очень универсален, и вы можете использовать одностороннюю или двустороннюю связь в различных конфигурациях.
В этом руководстве мы покажем, как установить двустороннюю связь между двумя платами ESP32.
Примечание: прочитайте наше Руководство по началу работы с ESP-NOW для полного введения в протокол ESP-NOW с ESP32.
Обзор проекта
На следующей диаграмме показан общий обзор проекта, который мы создадим.
В этом проекте у нас будет две платы ESP32. Каждая плата подключена к OLED-дисплею и датчику BME280;
Каждая плата получает показания температуры, влажности и давления от своего соответствующего датчика;
Каждая плата отправляет свои показания другой плате через ESP-NOW;
Когда плата получает показания, она отображает их на OLED-дисплее;
После отправки показаний плата отображает на OLED, было ли сообщение успешно доставлено;
Каждая плата должна знать MAC-адрес другой платы для отправки сообщения.
В этом примере мы используем двустороннюю связь между двумя платами, но вы можете добавить больше плат в эту схему, обеспечив взаимную связь между всеми платами.
Предварительные требования
Перед тем как приступить к этому проекту, убедитесь, что вы выполнили следующие предварительные требования.
Дополнение ESP32 для Arduino IDE
Мы будем программировать ESP32 с помощью Arduino IDE, поэтому перед продолжением этого руководства у вас должно быть установлено дополнение ESP32 в вашей Arduino IDE. Следуйте следующему руководству:
Установка библиотек
Установите следующие библиотеки в вашей Arduino IDE. Эти библиотеки можно установить через менеджер библиотек Arduino. Перейдите в Sketch > Include Library > Manage Libraries и найдите название библиотеки.
Библиотеки OLED (Руководство по ESP32 OLED): библиотека Adafruit_SSD1306 и библиотека Adafruit_GFX
Библиотеки BME280 (Руководство по ESP32 BME280): библиотека Adafruit_BME280 и библиотека Adafruit Unified Sensor
Необходимые компоненты
Для этого руководства вам понадобятся следующие компоненты:
2x платы разработки ESP32 (читайте Лучшие платы ESP32)
Вы можете использовать приведённые выше ссылки или перейти непосредственно на MakerAdvisor.com/tools, чтобы найти все компоненты для ваших проектов по лучшей цене!
Получение MAC-адреса платы
Для отправки сообщений между платами нам нужно знать их MAC-адрес. Каждая плата имеет уникальный MAC-адрес (узнайте, как Получить и изменить MAC-адрес ESP32).
Загрузите следующий код на каждую из ваших плат, чтобы получить их MAC-адрес.
/*
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/get-change-esp32-esp8266-mac-address-arduino/
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 <WiFi.h>
#include <esp_wifi.h>
void readMacAddress(){
uint8_t baseMac[6];
esp_err_t ret = esp_wifi_get_mac(WIFI_IF_STA, baseMac);
if (ret == ESP_OK) {
Serial.printf("%02x:%02x:%02x:%02x:%02x:%02x\n",
baseMac[0], baseMac[1], baseMac[2],
baseMac[3], baseMac[4], baseMac[5]);
} else {
Serial.println("Failed to read MAC address");
}
}
void setup(){
Serial.begin(115200);
WiFi.mode(WIFI_STA);
WiFi.STA.begin();
Serial.print("[DEFAULT] ESP32 Board MAC Address: ");
readMacAddress();
}
void loop(){
}
После загрузки кода нажмите кнопку RST/EN, и MAC-адрес должен отобразиться в мониторе последовательного порта.
Запишите MAC-адрес каждой платы, чтобы чётко их идентифицировать.
Схема подключения
Подключите OLED-дисплей и датчик BME280 к каждой плате ESP32. Следуйте следующей схеме подключения.
Вы можете использовать следующую таблицу как справку при подключении датчика BME280.
BME280 |
ESP32 |
|---|---|
VIN |
3.3V |
GND |
GND |
SCL |
GPIO 22 |
SDA |
GPIO 21 |
Вы также можете использовать следующую таблицу для подключения OLED-дисплея к ESP32.
OLED Display |
ESP32 |
|---|---|
GND |
GND |
VCC |
VIN |
SCL |
GPIO 22 |
SDA |
GPIO 21 |
Узнайте больше о подключении нескольких I2C-периферийных устройств к ESP32.
Код двусторонней связи ESP32 ESP-NOW
Загрузите следующий код на каждую из ваших плат. Перед загрузкой кода вам нужно ввести MAC-адрес другой платы (платы, на которую вы отправляете данные).
/*
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/esp-now-two-way-communication-esp32/
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>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
Adafruit_BME280 bme;
// REPLACE WITH THE MAC Address of your receiver
uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
// Define variables to store BME280 readings to be sent
float temperature;
float humidity;
float pressure;
// Define variables to store incoming readings
float incomingTemp;
float incomingHum;
float incomingPres;
// Variable to store if sending data was successful
String success;
//Structure example to send data
//Must match the receiver structure
typedef struct struct_message {
float temp;
float hum;
float pres;
} struct_message;
// Create a struct_message called BME280Readings to hold sensor readings
struct_message BME280Readings;
// Create a struct_message to hold incoming sensor readings
struct_message incomingReadings;
esp_now_peer_info_t peerInfo;
// Callback when data is sent
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");
if (status ==0){
success = "Delivery Success :)";
}
else{
success = "Delivery Fail :(";
}
}
// Callback when data is received
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));
Serial.print("Bytes received: ");
Serial.println(len);
incomingTemp = incomingReadings.temp;
incomingHum = incomingReadings.hum;
incomingPres = incomingReadings.pres;
}
void setup() {
// Init Serial Monitor
Serial.begin(115200);
// Init BME280 sensor
bool status = bme.begin(0x76);
if (!status) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
// Init OLED display
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
// Set device as a Wi-Fi Station
WiFi.mode(WIFI_STA);
// Init ESP-NOW
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
// Once ESPNow is successfully Init, we will register for Send CB to
// get the status of Trasnmitted packet
esp_now_register_send_cb(esp_now_send_cb_t(OnDataSent));
// Register peer
memcpy(peerInfo.peer_addr, broadcastAddress, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
// Add peer
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
// Register for a callback function that will be called when data is received
esp_now_register_recv_cb(esp_now_recv_cb_t(OnDataRecv));
}
void loop() {
getReadings();
// Set values to send
BME280Readings.temp = temperature;
BME280Readings.hum = humidity;
BME280Readings.pres = pressure;
// Send message via ESP-NOW
esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &BME280Readings, sizeof(BME280Readings));
if (result == ESP_OK) {
Serial.println("Sent with success");
}
else {
Serial.println("Error sending the data");
}
updateDisplay();
delay(10000);
}
void getReadings(){
temperature = bme.readTemperature();
humidity = bme.readHumidity();
pressure = (bme.readPressure() / 100.0F);
}
void updateDisplay(){
// Display Readings on OLED Display
display.clearDisplay();
display.setTextSize(1);
display.setTextColor(WHITE);
display.setCursor(0, 0);
display.println("INCOMING READINGS");
display.setCursor(0, 15);
display.print("Temperature: ");
display.print(incomingTemp);
display.cp437(true);
display.write(248);
display.print("C");
display.setCursor(0, 25);
display.print("Humidity: ");
display.print(incomingHum);
display.print("%");
display.setCursor(0, 35);
display.print("Pressure: ");
display.print(incomingPres);
display.print("hPa");
display.setCursor(0, 56);
display.print(success);
display.display();
// Display Readings in Serial Monitor
Serial.println("INCOMING READINGS");
Serial.print("Temperature: ");
Serial.print(incomingReadings.temp);
Serial.println(" ºC");
Serial.print("Humidity: ");
Serial.print(incomingReadings.hum);
Serial.println(" %");
Serial.print("Pressure: ");
Serial.print(incomingReadings.pres);
Serial.println(" hPa");
Serial.println();
}
Как работает код
Мы подробно рассмотрели взаимодействие с OLED-дисплеем и датчиком BME280 в предыдущих руководствах. Здесь мы рассмотрим только соответствующие части, связанные с ESP-NOW.
Код хорошо прокомментирован, чтобы вы понимали, что делает каждая строка кода.
Для использования ESP-NOW вам нужно подключить следующие библиотеки.
#include <esp_now.h>
#include <WiFi.h>
В следующей строке вставьте MAC-адрес платы-получателя:
uint8_t broadcastAddress[] = {0x30, 0xAE, 0xA4, 0x07, 0x0D, 0x64};
Создайте переменные для хранения показаний температуры, влажности и давления от датчика BME280. Эти показания будут отправлены на другую плату:
// Define variables to store BME280 readings to be sent
float temperature;
float humidity;
float pressure;
Создайте переменные для хранения показаний датчика, поступающих от другой платы:
// Define variables to store incoming readings
float incomingTemp;
float incomingHum;
float incomingPres;
Следующая переменная будет хранить сообщение об успешной доставке, если показания были успешно доставлены на другую плату.
// Variable to store if sending data was successful
String success;
Создайте структуру, которая хранит показания влажности, температуры и давления.
typedef struct struct_message {
float temp;
float hum;
float pres;
} struct_message;
Затем вам нужно создать два экземпляра этой структуры. Один для получения показаний и другой для хранения показаний, которые будут отправлены.
BME280Readings будет хранить показания для отправки.
// Create a struct_message called BME280Readings to hold sensor readings
struct_message BME280Readings;
incomingReadings будет хранить данные, поступающие от другой платы.
// Create a struct_message to hold incoming sensor readings
struct_message incomingReadings;
Создайте переменную типа esp_now_peer_info_t для хранения информации о пире (peer).
esp_now_peer_info_t peerInfo;
Затем нам нужно создать две функции обратного вызова (callback). Одна будет вызываться при отправке данных, а другая — при получении данных.
Функция обратного вызова OnDataSent()
Функция OnDataSent() будет вызываться при отправке новых данных. Эта функция просто выводит, было ли сообщение успешно доставлено или нет. Если сообщение доставлено успешно, переменная status возвращает 0, и мы можем установить наше сообщение об успехе в «Delivery Success»:
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
if (status ==0) {
success = "Delivery Success :)";
}
Если сообщение об успехе возвращает 1, это означает, что доставка не удалась:
else {
success = "Delivery Fail :(";
}
Функция обратного вызова OnDataRecv()
Функция OnDataRecv() будет вызываться при поступлении нового пакета.
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
Мы сохраняем новый пакет в структуру incomingReadings, которую мы создали ранее:
memcpy(&incomingReadings, incomingData, sizeof(incomingReadings));
Мы выводим длину сообщения в монитор последовательного порта. Вы можете отправлять только 250 байт в каждом пакете.
Serial.print("Bytes received: ");
Serial.println(len);
Затем сохраните входящие показания в соответствующие переменные. Чтобы получить доступ к переменной temperature внутри структуры incomingReadings, вам просто нужно обратиться к incomingReadings.temp следующим образом:
incomingTemp = incomingReadings.temp;
Тот же процесс выполняется для других переменных:
incomingHum = incomingReadings.hum;
incomingPres = incomingReadings.pres;
setup()
В функции setup() инициализируйте ESP-NOW.
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
Затем зарегистрируйте функцию обратного вызова OnDataSent.
esp_now_register_send_cb(OnDataSent);
Для отправки данных на другую плату вам нужно добавить её в качестве пира (peer). Следующие строки регистрируют и добавляют нового пира.
// Register peer
memcpy(peerInfo.peer_addr, broadcastAddress, 6);
peerInfo.channel = 0;
peerInfo.encrypt = false;
// Add peer
if (esp_now_add_peer(&peerInfo) != ESP_OK){
Serial.println("Failed to add peer");
return;
}
Зарегистрируйте функцию обратного вызова OnDataRecv.
esp_now_register_recv_cb(esp_now_recv_cb_t(OnDataRecv));
loop()
В функции loop() мы вызываем функцию getReadings(), которая отвечает за получение новых показаний температуры от датчика. Эта функция создаётся после loop().
После получения новых показаний температуры, влажности и давления мы обновляем нашу структуру BME280Reading этими новыми значениями:
BME280Readings.temp = temperature;
BME280Readings.hum = humidity;
BME280Readings.pres = pressure;
Затем мы можем отправить структуру BME280Readings через ESP-NOW:
// Send message via ESP-NOW
esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &BME280Readings, sizeof(BME280Readings));
if (result == ESP_OK) {
Serial.println("Sent with success");
}
else {
Serial.println("Error sending the data");
}
Наконец, вызовите функцию updateDisplay(), которая обновит OLED-дисплей показаниями, поступающими от другой платы ESP32.
updateDisplay();
Функция loop() выполняется каждые 10 секунд.
Вот, в общих чертах, как работает код. Вам следует загрузить код на обе ваши платы. Вам нужно только изменить код, указав MAC-адрес платы, на которую вы отправляете данные.
Рекомендуемое чтение: Руководство по OLED-дисплею с ESP32 и Руководство по датчику BME280 с ESP32.
Демонстрация
После загрузки кода на обе платы вы должны увидеть, что OLED отображает показания датчика другой платы, а также сообщение об успешной доставке.
Как видите, всё работает как ожидалось:
Мы протестировали дальность связи между двумя платами и получили стабильную связь на расстоянии до 220 метров (приблизительно 722 фута) на открытой местности. В этом эксперименте встроенные антенны обеих плат ESP32 были направлены друг на друга.
Заключение
В этом руководстве мы показали, как установить двустороннюю связь между двумя платами ESP32 с использованием ESP-NOW. Это очень универсальный протокол связи, который можно использовать для отправки пакетов размером до 250 байт. Протокол связи ESP-NOW также можно использовать с платами ESP8266: Начало работы с ESP-NOW (ESP8266 NodeMCU с Arduino IDE).
В качестве примера мы показали взаимодействие между двумя платами, но вы можете добавить множество плат в вашу конфигурацию. Вам просто нужно знать MAC-адрес платы, на которую вы отправляете данные.
Мы будем публиковать больше руководств по ESP-NOW, так что следите за обновлениями.
Чтобы узнать больше о плате ESP32, ознакомьтесь с нашими ресурсами:
Источник: ESP-NOW Two-Way Communication Between ESP32 Boards