ESP-NOW协议
ESP-NOW 是由乐鑫开发的另一款协议,可以使多个设备在没有或不使用 Wi-Fi 的情况下进行通信。这种协议类似常见于无线鼠标中的低功耗 2.4GHz 无线连接——设备在进行通信之前要进行配对。配对之后,设备之间的连接是持续的、点对点的,并且不需要握手协议。
有关ESP-NOW更详细的介绍可以查看官方的手册:ESP-NOW 用户指南,也可以看乐鑫在其SDK中的介绍:ESP-IDF编程指南,这应该是有关ESP-NOW所有的官方资料了。除此之外,该网站上有关使用Arduino开发ESP8266的教程也非常棒。
利用ESP-NOW协议我们可以实现多设备点对点的双向通信。
而ESP-NOW同样有Arduino的库文件espnow.h,我利用这个库文件编写了多节点相互通信的程序。
实现多点通信
1.获取WiFi模块的MAC地址
首先我们需要将print_mac_address.ino文件烧录进WiFi模块,获取其MAC地址,其源代码如下。
#include <ESP8266WiFi.h>
void setup()
{
Serial.begin(115200);//设置串口波特率为115200
WiFi.mode(WIFI_STA);//设置WiFi模式为STA,这里要与之后使用的模式相对应
}
void loop()
{
//每隔一秒钟打印一次MAC地址
Serial.println(WiFi.macAddress());
delay(1000);
}
打开串口调试助手,WiFi模块每秒钟会打印一次自己的MAC地址,将其记录下来。
2.烧录通信程序
我使用三个WiFi模块进行通讯,每个模块都会发送数据到另两个模块,这里就分析模块1的程序,另两个模块的程序非常相似,稍作更改即可。
#include <ESP8266WiFi.h>
#include <espnow.h>
uint8_t esp_1_address[] = {0x80, 0x7D, 0x3A, 0x42, 0xD3, 0xEB};//模块1的mac地址
uint8_t esp_2_address[] = {0x8C, 0xAA, 0xB5, 0x0D, 0x97, 0x2C};//模块2的mac地址
uint8_t esp_3_address[] = {0x8C, 0xAA, 0xB5, 0x0D, 0x80, 0x7C};//模块3的mac地址
//Callback when data is sent
//发送信息时的回调函数,每次发送信息会自动调用该函数
void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus)
{
//Serial.print("Last Packet Send Status: ");
if (sendStatus == 0)//信息发送成功时
{
//打印接收方的mac地址
Serial.print("deliver data to: ");
for(int i = 0; i < 5; ++i)
{
Serial.printf("%x:", *(mac_addr + i));
}
Serial.printf("%x", *(mac_addr + 5));
Serial.println(" ");
//Serial.println("Delivery success");
}
else{
Serial.println("Delivery fail");
}
}
// Callback when data is received
//接收信息时的回调函数,每次接收信息会自动调用该函数
void OnDataRecv(uint8_t * mac, uint8_t *incomingData, uint8_t len)
{
//在此处先通过mac地址判断是哪一个设备发送的数据,再进行数据解析(在主控中写代码时)
//或者只是使用memcpy函数存储数据,在主函数中处理(类似于DMA的形式)
//打印发送方的mac地址
Serial.print("receive data from: ");
for(int i = 0; i < 5; ++i)
{
Serial.printf("%x:", *(mac + i));
}
Serial.printf("%x", *(mac + 5));
Serial.println(" ");
/*
Serial.print("Bytes received: ");
Serial.println(len);
*/
//打印接收到的数据
for(int i = 0; i < len; ++i)
{
Serial.printf("%c", *(incomingData + i));
}
Serial.println(" ");
}
void setup() {
Serial.begin(115200);//初始化串口,设置其波特率为115200
WiFi.mode(WIFI_STA);//设置WIFI模式为STA
WiFi.disconnect();//断开WIFI连接
// Init ESP-NOW
if (esp_now_init() != 0)//初始化espnow
{
Serial.println("Error initializing ESP-NOW");
return;
}
else
{
Serial.println("esp_now init success");
}
// Set ESP-NOW Role
//双向通信时需要设定角色为 ESP_NOW_ROLE_COMBO
esp_now_set_self_role(ESP_NOW_ROLE_COMBO);
// 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
//与模块2和3配对
esp_now_add_peer(esp_2_address, ESP_NOW_ROLE_COMBO, 1, NULL, 0);
esp_now_add_peer(esp_3_address, ESP_NOW_ROLE_COMBO, 2, NULL, 0);
// Register for a callback function that will be called when data is received
//执行完这个函数,每次接收数据就会自动调用回调函数了
esp_now_register_recv_cb(OnDataRecv);
}
uint8_t data_to_send[] = {"test from esp8266_1"};//要发送的数据
void loop()
{
// Send message via ESP-NOW
//向模块2和3发送数据
esp_now_send(esp_2_address, data_to_send, sizeof(data_to_send));
esp_now_send(esp_3_address, data_to_send, sizeof(data_to_send));
delay(500);
}
发送端代码
MAC需要改为接受的ESP8266的MAC
//发送
#include <ESP8266WiFi.h>
#include <espnow.h>
//接收方MAC地址 根据自己的板子修改
uint8_t broadcastAddress[] = {0x50, 0x02, 0x91, 0x67, 0xF5, 0xF4};
//发送数据的结构体
typedef struct struct_message {
char a[32];
int b;
float c;
String d;
bool e;
} struct_message;
//创建一个新的类型变量
struct_message myData;
//这是一个回调函数,将在发送消息时执行。
//在这种情况下,无论是否成功发送该消息,都会简单地打印出来
void OnDataSent(uint8_t *mac_addr, uint8_t sendStatus) {
Serial.print("Last Packet Send Status: ");
if (sendStatus == 0){
Serial.println("Delivery success");
}
else{
Serial.println("Delivery fail");
}
}
void setup() {
//初始化串行监视器以进行调试
Serial.begin(115200);
//将设备设置为Wi-Fi站点
WiFi.mode(WIFI_STA);
//立即初始化ESP
if (esp_now_init() != 0) {
Serial.println("Error initializing ESP-NOW");
return;
}
//设置ESP8266角色 ESP_NOW_ROLE_CONTROLLER, ESP_NOW_ROLE_SLAVE,
//ESP_NOW_ROLE_COMBO, ESP_NOW_ROLE_MAX。
esp_now_set_self_role(ESP_NOW_ROLE_CONTROLLER);
//先前创建的功能。
esp_now_register_send_cb(OnDataSent);
//与另一个ESP-NOW设备配对以发送数据
esp_now_add_peer(broadcastAddress, ESP_NOW_ROLE_SLAVE, 1, NULL, 0);
}
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_now_send(broadcastAddress, (uint8_t *) &myData, sizeof(myData));
//延时两秒
delay(2000);
}
接受端代码
//接收
#include <ESP8266WiFi.h>
#include <espnow.h>
//接收数据保存的结构体和发送方一致
typedef struct struct_message {
char a[32];
int b;
float c;
String d;
bool e;
} struct_message;
//创建结构体变量
struct_message myData;
//创建一个回调函数作为接收数据后的串口显示
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("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);
//设置ESP8266模式
WiFi.mode(WIFI_STA);
//初始化 ESP-NOW
if (esp_now_init() != 0) {
Serial.println("Error initializing ESP-NOW");
return;
}
//设置ESP8266角色:
esp_now_set_self_role(ESP_NOW_ROLE_SLAVE);
//先前创建的功能 测试ESP-NOW通信
esp_now_register_recv_cb(OnDataRecv);
}
void loop(){
}
分别将esp8266_1.ino、esp8266_2.ino和esp8266_3.ino烧录进模块1、2、3即可。
效果如下图所示:
Espnow库函数注释
Arduino的espnow.h似乎没有官方文档,我把我研究的一小部分内容写成注释加进去,不保证完全正确,还是得以手册为准。
#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