ESP-NOW

  • ESP-NOW介绍
    • ESP-NOW支持以下特性
    • ESP-NOW技术也存在以下局限性
    • 获取ESP32的MAC地址
    • ESP-NOW单向通信(One-way communication)
    • ESP32单板间的双向通信
    • 一对多通信(一发多收)
    • 一对多通信(多发一收)

部分图片来自网络

ESP-NOW介绍

ESP-NOW是一种由Espressif开发的协议,可以让多个设备在不使用Wi-Fi的情况下相互通信。该协议类似于低功耗的2.4GHz无线连接。设备之间的配对需要在通信之前完成。配对完成后,连接是安全的、点对点的,不需要握手。这意味着在设备彼此配对后,连接是持久的。换句话说,如果你的某块单板突然失去电源或复位,当它重启时,它将自动连接到它的频道继续通信

ESP-NOW支持以下特性

  1. 混合加密和未加密的对端设备
  2. 加密和不加密的单播通信
  3. 最多可携带250字节的有效载荷(小数据传输);
  4. 发送回调函数,可以设置为通知应用层传输成功或失败;

ESP-NOW技术也存在以下局限性

  1. 有限的加密。Station模式最多支持10个加密对等体;“软拨号”或“软拨号+工作站”模式最多为6个
  2. 支持多个未加密的对等体,包括加密的对等体,总数不能超过20个
  3. 3.最大消息长度限制在250字节

获取ESP32的MAC地址

在使用ESP-NOW协议前需要知道ESP32 的MAC地址

#include "WiFi.h"void setup(){Serial.begin(115200);WiFi.mode(WIFI_MODE_STA);Serial.println(WiFi.macAddress());
}void loop(){}

上串口打开串口监视器,可以得到板子的MAC地址,例如

最好拿个小纸条记下来

ESP-NOW单向通信(One-way communication)

一个ESP32作为发送方,另一个ESP32作为接收方


发送端的程序

#include <esp_now.h>
#include <WiFi.h>// 接收端的MAC地址
uint8_t broadcastAddress[] = {0x30, 0xAE, 0xA4, 0x07, 0x0D, 0x64};// 发送结构体类型
typedef struct struct_message {char a[32];int b;float c;bool d;
} struct_message;// 创建一个结构体变量
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模式为STA模式,即无线终端WiFi.mode(WIFI_STA);//  初始化ESP-NOWif (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;//是否加密为Falseif (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 = false;//发送信息到指定ESP32上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);
}

接收端的程序

#include <esp_now.h>
#include <WiFi.h>// 创建一个结构体接收数据
typedef struct struct_message {char a[32];int b;float c;bool d;
} struct_message;// 创建一个结构体变量
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("Bool: ");Serial.println(myData.d);Serial.println();
}void setup() {// 初始化串口波特率Serial.begin(115200);// 设置wifi模式WiFi.mode(WIFI_STA);// 初始化esp-nowif (esp_now_init() != ESP_OK) {Serial.println("Error initializing ESP-NOW");return;}//注册接收信息的回调函数esp_now_register_recv_cb(OnDataRecv);
}void loop() {}

分别长传到两块ESP32上,打开串口监视器
发送端:
接收端:

ESP32单板间的双向通信

两块ESP32之间互相发送接收
这里我们用BME280温湿度传感器做实验,并在OLED上显示
相关库连接(前两个是关于OLED的,后两个是BME280的驱动库):
1.Adafruit_GFX library
2.Adafruit_SSD1306 library
3.Adafruit_BME280_Library
4.Adafruit_Sensor

上传下面的代码到两块开发板上,注意MAC地址是两块板子的地址
,关于如何获取板子的MAC地址,前面已经有介绍

#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;// 这里换为对方板子的MAC地址
uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};// 定义几个浮点型变量存储BME280传感器的数值,用于发送
float temperature;
float humidity;
float pressure;// 这里定义的变量用于接收 注意类型也是浮点型
float incomingTemp;
float incomingHum;
float incomingPres;// 数据发送成功标志
String success;//定义结构体
typedef struct struct_message {float temp;float hum;float pres;
} struct_message;// 创建一个结构体变量 用于发送
struct_message BME280Readings;// 创建一个结构体变量 用于接收
struct_message incomingReadings;// 发送数据回调函数
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 :(";}
}// 收到消息的回调函数
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() {// 初始化波特率Serial.begin(115200);// 初始化BME280bool status = bme.begin(0x76);  if (!status) {Serial.println("Could not find a valid BME280 sensor, check wiring!");while (1);}// 初始化OLEDif(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println(F("SSD1306 allocation failed"));for(;;);}// 设置ESP32为STA模式WiFi.mode(WIFI_STA);//初始化 ESP-NOWif (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;}//注册接收回调函数esp_now_register_recv_cb(OnDataRecv);
}void loop() {getReadings();// 准备发送的变量BME280Readings.temp = temperature;BME280Readings.hum = humidity;BME280Readings.pres = pressure;//发送数据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(){// 收掉信息在OLED上显示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();// 串口打印信息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();
}

实验效果图

一对多通信(一发多收)

  1. 一块ESP32发送
  2. 多个ESP32作为接收
    同样需要先获取接收板子的Mac地址
    发送端的程序:
#include <esp_now.h>
#include <WiFi.h>/*接收端板子的Mac地址,这里为三块板子*/
uint8_t broadcastAddress1[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
uint8_t broadcastAddress2[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
uint8_t broadcastAddress3[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
//用于测试的数据
typedef struct test_struct {int x;int y;
} test_struct;test_struct test;// 发送时的回调函数
void OnDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) {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:\t");Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}void setup() {Serial.begin(115200);WiFi.mode(WIFI_STA);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;peerInfo.channel = 0;  peerInfo.encrypt = false;//配置第一块接收接收的Mac地址memcpy(peerInfo.peer_addr, broadcastAddress1, 6);if (esp_now_add_peer(&peerInfo) != ESP_OK){Serial.println("Failed to add peer");return;}// 配置第二块接收接收的Mac地址memcpy(peerInfo.peer_addr, broadcastAddress2, 6);if (esp_now_add_peer(&peerInfo) != ESP_OK){Serial.println("Failed to add peer");return;}/// 配置第三块接收接收的Mac地址memcpy(peerInfo.peer_addr, broadcastAddress3, 6);if (esp_now_add_peer(&peerInfo) != ESP_OK){Serial.println("Failed to add peer");return;}
}void loop() {//这里使用随机数作为发送的数据test.x = random(0,20); test.y = random(0,20);//esp_now_send()中的第一个参数为0表示向所有接收的板子发送,也是传入指定的板子地址esp_err_t result = esp_now_send(0, (uint8_t *) &test, sizeof(test_struct));if (result == ESP_OK) {Serial.println("Sent with success");}else {Serial.println("Error sending the data");}delay(2000);
}

接收端

#include <esp_now.h>
#include <WiFi.h>//发送的端是数据是结构体,所以这里也创建一个结构体
typedef struct test_struct {int x;int y;
} test_struct;test_struct 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("x: ");Serial.println(myData.x);Serial.print("y: ");Serial.println(myData.y);Serial.println();
}void setup() {//初始化串口波特率Serial.begin(115200);//设置为WIFI_STA模式WiFi.mode(WIFI_STA);//初始化ESP_NOWif (esp_now_init() != ESP_OK) {Serial.println("Error initializing ESP-NOW");return;}// 注册接收的回调函数esp_now_register_recv_cb(OnDataRecv);
}void loop() {}

经过本人测试,并不是每次发送所有的板子都能成功的接收到信息

一对多通信(多发一收)

  • 一块ESP32板作为接收;多个ESP32板充当发送,本次示例程序采用3块板子作为发送
  • ESP32接收板接收来自所有发送方的消息,并识别发送消息的板子
  • 还是一样,需要先获取接收板子的MAC地址

发送端的程序

#include <esp_now.h>
#include <WiFi.h>// 这里改为接收板子的MAC地址
uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};// 还是创建一个结构体类型
typedef struct struct_message {int id; //注意这里的id非常重要,作为区分不同发送端板子的标int x;int y;
} struct_message;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.mode(WIFI_STA);// 初始化ESP_NOWif (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() {// 设置发送的数据myData.id = 1; //注意这里的id,每块发送的板子不能重复myData.x = random(0,50);myData.y = random(0,50);// 向指定MAC地址发送数据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(10000);
}

接收端:

#include <esp_now.h>
#include <WiFi.h>// 和发送端一样,这里也声明一个结构体
typedef struct struct_message {int id;int x;int y;
}struct_message;struct_message myData;//创建三个结构体变量来保存每个板上的读数
struct_message board1;
struct_message board2;
struct_message board3;// 创建一个结构体数组
struct_message boardsStruct[3] = {board1, board2, board3};// 接收回调函数
void OnDataRecv(const uint8_t * mac_addr, const uint8_t *incomingData, int len) {char macStr[18];Serial.print("Packet received from: ");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.println(macStr);memcpy(&myData, incomingData, sizeof(myData));Serial.printf("Board ID %u: %u bytes\n", myData.id, len);// 更新数据boardsStruct[myData.id-1].x = myData.x;boardsStruct[myData.id-1].y = myData.y;Serial.printf("x value: %d \n", boardsStruct[myData.id-1].x);Serial.printf("y value: %d \n", boardsStruct[myData.id-1].y);Serial.println();
}void setup() {Serial.begin(115200);WiFi.mode(WIFI_STA);//初始化ESP_NOWif (esp_now_init() != ESP_OK) {Serial.println("Error initializing ESP-NOW");return;}//注册接收回调函数esp_now_register_recv_cb(OnDataRecv);
}void loop() {/*int board1X = boardsStruct[0].x;int board1Y = boardsStruct[0].y;int board2X = boardsStruct[1].x;int board2Y = boardsStruct[1].y;int board3X = boardsStruct[2].x;int board3Y = boardsStruct[2].y;*/delay(10000);
}

Arduino for ESP32-----ESP-NOW介绍及使用相关推荐

  1. 爱招飞软件开发工具与 Arduino 与 ESP32 的关系

    为何使用开发板进行物联网开发工作?    国内机电产业环境大部分以硬件为主,对于软件设计人员,缺乏硬件专业训练,或是对于机械机构与机电整合原理没有概念.在学习机电整合设计时,会有很多的困扰与障碍,因为 ...

  2. 使用Arduino开发ESP32(11):IO口与相关外设说明与记录

    文章目录 目的 数字IO口 基本使用 外部中断 使用示例 参考链接 LEDC(PWM) 常用方法 使用示例 参考链接 SigmaDelta 参考链接 ADC 常用方法 使用示例 参考链接 存在的问题 ...

  3. 使用Arduino开发ESP32(13):SD卡的使用

    文章目录 目的 SDMMC方式 常用方法 使用示例 SPI方式 常用方法 使用示例 注意事项 总结 目的 对于嵌入式设备来说SD卡也是个比较好用的功能,比如用来存放设备的配置文件.日志文件.执行脚本. ...

  4. 基于arduino的ESP32 学习笔记(一) 基于ESP32的智能花盆

    前言 本文的目的是为了给将要制作的ESP32手环做技术储备 准备学习下ESP32,还有嵌入式GUI框架LVGL,通过做几个小项目练手是不错的选择,最终目标是做一个ESP32的手环 做一个ESP32手环 ...

  5. Arduino ESP8266/ESP32读取和改写MAC

    Arduino ESP8266/ESP32读取和改写MAC ESP8266/ESP32读取MAC示例代码 /*读取MAC*/ #ifdef ESP32#include <WiFi.h> # ...

  6. Arduino安装esp32 SDK(Windows)问题:AzureIoT: no headers files解决

    一.前言 https://www.arduino.cn/forum.php?mod=viewthread&tid=81194&highlight=esp32 这是Arduino 中文社 ...

  7. 使用Arduino开发ESP32(八):ESP32的EPROM的写入读取

    写入EPROM /* 该代码向EEPROM写入4096字节数据 */ #include <EEPROM.h>void setup() {Serial.begin(115200);Seria ...

  8. 使用乐鑫官方资源搭建基于Arduino的ESP32的开发环境

    目录 一.配置IDE管理器 二.自动安装板支持包 三.手动安装板支持包 四.网盘的ESP32全系列基本库的板支持包 使用乐鑫官方库搭建Arduino开发环境. 乐鑫官方Github:GitHub - ...

  9. 基于arduino的ESP32 学习笔记(二) TFT_eSPI和LVGL库使用笔记

    前言 本文的目的是为了给将要制作的ESP32手环做技术储备 记录基于arduino的ESP32驱动TFT-LCD屏幕的配置过程,并且进一步使用LVGL这个GUI框架 硬件准备 ST7789 240x2 ...

  10. Arduino For EEPROM相关函数使用介绍

    Arduino For EEPROM相关函数使用介绍 EEPROM.read():每次只能读取一个字节的数据.而大部分数据类型占用的字节数量都是超过1个字节的,如浮点型数据,整形数据等. EEPROM ...

最新文章

  1. How to call DLL and LIB files (SDK)
  2. 本地连接不见了怎么办?
  3. 首次使用Cesium加载3D数据成功
  4. 去中心化交易所前路明朗,基于EOS的去中心化交易所力拔头筹
  5. Django-Json 数据返回
  6. 【视频教程】利用Excel轻松爬取网页上的数据
  7. DNS(三)--子域授权和视图
  8. 直播系统中使用SEI传输用户自定义数据方案讨论
  9. 实现electron-bridge
  10. centos5.5和6.5中vncservervncviewer最基本配置
  11. python入门学校_如何学习Python,以及新手如何入门?
  12. 手动升级麦咖啡(McAfee)病毒库的步骤
  13. unity导入导出excel的功能
  14. 讯飞tts文转语错误分析即解决方法
  15. 错误代码741 因为文件名产生符号链接,所以需由对象管理器重新运行分析操作。
  16. 天眼查数据采集、分析、深度挖掘
  17. python如何提问并回答_如何提问 - nashviller - 博客园
  18. debian7系统设置固定IP
  19. 服务器装win10稳定吗,win10哪个版本最稳定好用 目前win10最稳定的版本推荐
  20. 桌面虚拟化项目的前期规划和测算

热门文章

  1. Android 拍照和图库功能(适配Android 6.0和7.0系统和华为机型问题)
  2. layaair引擎做的一个三消游戏
  3. Angular—Dom操作
  4. Keil5中 go to define 功能无法使用
  5. 配置nginx域名转发
  6. 机器视觉(八):图像特征提取
  7. 一文搞懂线程世界级难题——线程状态到底是6种还是5种!!!
  8. enspar启动失败40_分析AR启动失败错误代码40终极解决方案
  9. 常见自动化测试工具及框架的选用
  10. 论信息技术在银行战略转型中的作用