目录
ESP-NOW介绍
ESP-NOW支持以下特性
ESP-NOW技术也存在以下局限性
Espnow库函数注释
获取ESP32的MAC地址
修改MAC地址
ESP-NOW单向通信(One-way communication)
发送端的程序
接收端的程序
ESP32单板间的双向通信
一对多通信(一发多收)
发送端的程序:
接收端的程序
一对多通信(多发一收)
发送端的程序
接收端的程序:
ESP-NOW是一种由Espressif开发的协议,可以让多个设备在不使用Wi-Fi的情况下相互通信。该协议类似于低功耗的2.4GHz无线连接。设备之间的配对需要在通信之前完成。配对完成后,连接是安全的、点对点的,不需要握手。这意味着在设备彼此配对后,连接是持久的。换句话说,如果你的某块单板突然失去电源或复位,当它重启时,它将自动连接到它的频道继续通信
#ifndef __ESPNOW_H__
#define __ESPNOW_H__
#ifdef __cplusplus
extern "C" {
#endif
enum esp_now_role {
ESP_NOW_ROLE_IDLE = 0,//未设置角色,不允许发送数据
ESP_NOW_ROLE_CONTROLLER,//控制方
ESP_NOW_ROLE_SLAVE,//被控制方
ESP_NOW_ROLE_COMBO,//控制方&被控制方双角色,双向通信时就用它
ESP_NOW_ROLE_MAX,//不懂
};
//回调函数
typedef void (*esp_now_recv_cb_t)(u8 *mac_addr, u8 *data, u8 len);
typedef void (*esp_now_send_cb_t)(u8 *mac_addr, u8 status);
int esp_now_init(void);//初始化esp_now
int esp_now_deinit(void);//取消esp_now的初始化
int esp_now_register_send_cb(esp_now_send_cb_t cb);//使用该函数之后,接收到数据会自动调用接收回调函数,回调函数的写法可以参考我上面的代码
int esp_now_unregister_send_cb(void);//与上面的函数作用相反
int esp_now_register_recv_cb(esp_now_recv_cb_t cb);//使用该函数之后,发送数据后会自动调用发送回调函数,回调函数的写法可以参考我上面的代码
int esp_now_unregister_recv_cb(void);//与上面的函数作用相反
int esp_now_send(u8 *da, u8 *data, int len);//发送数据,MAC地址中传入NULL会广播
int esp_now_add_peer(u8 *mac_addr, u8 role, u8 channel, u8 *key, u8 key_len);//与新设备配对
int esp_now_del_peer(u8 *mac_addr);//将已配对的设备删除
int esp_now_set_self_role(u8 role);//设定设备自己的角色
int esp_now_get_self_role(void);//获取设备自己的角色
int esp_now_set_peer_role(u8 *mac_addr, u8 role);//设定某个已配对设备的角色
int esp_now_get_peer_role(u8 *mac_addr);//获取某个已配对设备的角色
int esp_now_set_peer_channel(u8 *mac_addr, u8 channel);//设定某个已配对设备的WiFi通道
int esp_now_get_peer_channel(u8 *mac_addr);//获取某个已配对设备的WiFi通道
int esp_now_set_peer_key(u8 *mac_addr, u8 *key, u8 key_len);//设定某个已配对设备的密钥
int esp_now_get_peer_key(u8 *mac_addr, u8 *key, u8 *key_len);//获取某个已配对设备的密钥
u8 *esp_now_fetch_peer(bool restart);//不懂
int esp_now_is_peer_exist(u8 *mac_addr);//检查已经配对的设备是否在线
int esp_now_get_cnt_info(u8 *all_cnt, u8 *encrypt_cnt);//不懂
int esp_now_set_kok(u8 *key, u8 len);//对通信的key进行加密,不设置时使用默认的PMK
#ifdef __cplusplus
}
#endif
#endif
在使用ESP-NOW协议前需要知道ESP32 的MAC地址
#include "WiFi.h"
void setup(){
Serial.begin(115200);
WiFi.mode(WIFI_MODE_STA);
Serial.println(WiFi.macAddress());
}
void loop(){
}
上串口打开串口监视器,可以得到板子的MAC地址,例如
#include
// Set your new MAC Address
uint8_t newMACAddress[] = {0x00, 0x08, 0x22, 0xa0, 0xa1, 0x34};
void setup(){
Serial.begin(9600);
Serial.println();
WiFi.mode(WIFI_STA);
WiFi.softAP("SD", "sd168888");
Serial.print("[OLD] ESP8266 Board MAC Address: ");
Serial.println(WiFi.macAddress());
// For Soft Access Point (AP) Mode
//wifi_set_macaddr(SOFTAP_IF, &newMACAddress[0]);
// For Station Mode
wifi_set_macaddr(STATION_IF, &newMACAddress[0]);
Serial.print("[NEW] ESP8266 Board MAC Address: ");
Serial.println(WiFi.macAddress());
}
void loop(){
}
一个ESP32作为发送方,另一个ESP32作为接收方
#include
#include
// 接收端的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-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;//是否加密为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 = 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
#include
// 创建一个结构体接收数据
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-now
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
//注册接收信息的回调函数
esp_now_register_recv_cb(OnDataRecv);
}
void loop() {
}
接收端:
两块ESP32之间互相发送接收
这里我们用BME280温湿度传感器做实验,并在OLED上显示
相关库连接(前两个是关于OLED的,后两个是BME280的驱动库):
Adafruit_GFX library
Adafruit_SSD1306 library
Adafruit_BME280_Library
Adafruit_Sensor
上传下面的代码到两块开发板上,注意MAC地址是两块板子的地址,关于如何获取板子的MAC地址,前面已经有介绍
#include
#include
#include
#include
#include
#include
#include
#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);
// 初始化BME280
bool status = bme.begin(0x76);
if (!status) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
// 初始化OLED
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
// 设置ESP32为STA模式
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;
}
//注册接收回调函数
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();
}
一块ESP32发送 ,多个ESP32作为接收
同样需要先获取接收板子的Mac地址
#include
#include
/*接收端板子的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
#include
//发送的端是数据是结构体,所以这里也创建一个结构体
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_NOW
if (esp_now_init() != ESP_OK) {
Serial.println("Error initializing ESP-NOW");
return;
}
// 注册接收的回调函数
esp_now_register_recv_cb(OnDataRecv);
}
void loop() {
}
经过本人测试,并不是每次发送所有的板子都能成功的接收到信息
#include
#include
// 这里改为接收板子的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_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() {
// 设置发送的数据
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
#include
// 和发送端一样,这里也声明一个结构体
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_NOW
if (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);
}