esp32笔记[4]-基于ESP-NOW协议的点对点数据收发
摘要
基于ESP-NOW协议的点对点数据收发,用两片ESP8266/ESP32实现远程控制小灯亮灭。
硬件平台
- ESP8266
- 小灯:IO2
开发平台
- ArduinoIDE
ESP-NOW协议简述
[https://www.zhihu.com/tardis/zm/art/344109867?source_id=1002]
ESP-NOW 是由乐鑫开发的另一款无线通信协议,可以使多个设备在没有或不使用 Wi-Fi 的情况下进行通信。这种协议类似常见于无线鼠标中的低功耗 2.4GHz 无线连接——设备在进行通信之前要进行配对。配对之后,设备之间的连接是持续的、点对点的,并且不需要握手协议。它是一种短数据传输、无连接的快速通信技术,可以让低功耗控制器直接控制所有智能设备而无需连接路由器,适用于智能灯、遥控控制、传感器数据回传等场景。
使用了 ESP-NOW 通信之后,如果某一个设备突然断电之后,只要它一旦重启,就是自动连接到对应的节点中重新进行通信。
ESP-NOW 支持如下特性:
- 单播包加密或单播包不加密通信;
- 加密配对设备和非加密配对设备混合使用;
- 可携带最长为 250 字节的有效 payload 数据;
- 支持设置发送回调函数以通知应用层帧发送失败或成功。
同样,ESP-NOW 也存在一些限制:
- 暂时不支持广播包;
- 加密配对设备有限制,Station 模式下最多支持10 个加密配对设备;SoftAP 或 SoftAP + Station 混合模式下最多支持 6 个加密配对设备。非加密配对设备支持若干,与加密设备总数和不超过 20 个;
- 有效 payload 限制为 250 字节。
ESP-NOW 支持多种通信方式:
- 一对一单向通信
- 一对多单向通信
- 多对一单向通信
- 双向通信
具体配置:
//设置ESP8266角色:ESP_NOW_ROLE_CONTROLLER(控制端), ESP_NOW_ROLE_SLAVE(从端),
//ESP_NOW_ROLE_COMBO(双向通信,主从,sta), ESP_NOW_ROLE_MAX(双向通信,主从,softap)
实现
获取MAC地址
#include <ESP8266WiFi.h>
void setup(){
Serial.begin(115200);
Serial.println();
Serial.print("ESP8266 Board MAC Address: ");
Serial.println(WiFi.macAddress());
}
void loop()
{
}
A板(发送指令)
/*
甲号板(地面)
双向模式
甲号板MAC:4c:75:25:0a:86:48
功能:串口<->ESP-NOW
拉低IO2,丙板开启激光10s
*/
#include <ESP8266WiFi.h>
#include <espnow.h>
#define DEBUG 1
#define KEY 2
/*开始函数原型*/
void on_data_send_callback(uint8_t *mac_addr, uint8_t send_status);
void on_data_rec_callback(uint8_t *mac_addr,uint8_t* incoming_data,uint8_t len);
uint32_t crc32(uint32_t crc, char *buff, int len);
void crc32_make_table(void);
const char cmd_on[]="toggle";
void send_cmd(void);
/*结束函数原型*/
/*开始全局变量*/
//配对设备MAC地址
//丙号板MAC:3c:61:05:fc:fd:4c
uint8_t broadcast_address[]={0x3c,0x61,0x05,0xfc,0xfd,0x4c};
//CRC32
uint32_t POLYNOMIAL = 0x04C11DB7;//多项式CRC32-Normal
uint8_t crc32_have_table = 0;//是否有表
uint32_t crc32_table[256];
//串口接收相关
uint16_t usart_buffer_pos=0;
uint8_t usart_buffer[256];
//发送数据的结构体
typedef struct _frame_type{
uint8_t header;//帧头0x5A
uint8_t source_address;//消息发送源地址
uint8_t target_address;//消息发送目标地址
uint16_t length;//消息内容长度(仅包括content)
uint8_t content_type;//内容类型:命令/数据
uint8_t content[236];//内容0-236Byte(ESP-NOW最大250B)
uint32_t CRC32;//CRC32校验值(header~content)
uint8_t footer;//帧尾0xA5
}frame_type;
//简短消息结构体
typedef struct _frame_type2{
uint8_t header;//0x78;
uint8_t payload[16];//简短消息
uint8_t footer;//0x87
}frame_type2;
//创建一个新的类型变量
frame_type frame;
frame_type2 frame2;
//单条消息重试次数
uint8_t count=10;
//接收应答标志
const uint8_t ACK=1;
const uint8_t NACK=2;
/*结束全局变量*/
void setup() {
//初始化串行监视器以进行调试
Serial.begin(115200);
//将设备设置为Wi-Fi站点
WiFi.mode(WIFI_STA);
//立即初始化ESP-NOW
if (esp_now_init() != 0) {
Serial.println("ERROR");
return;
}
//设置ESP8266角色 ESP_NOW_ROLE_CONTROLLER, ESP_NOW_ROLE_SLAVE,
//ESP_NOW_ROLE_COMBO(双向通信,sta), ESP_NOW_ROLE_MAX(双向通信,softap)
esp_now_set_self_role(ESP_NOW_ROLE_COMBO);
//绑定发送回调函数
esp_now_register_send_cb(on_data_send_callback);
//绑定接收回调函数
esp_now_register_recv_cb(on_data_rec_callback);
//与另一个ESP-NOW设备配对以发送数据
esp_now_add_peer(broadcast_address, ESP_NOW_ROLE_COMBO, 1, NULL, 0);
#ifdef DEBUG
//发送本机地址
Serial.print("First MAC: ");
Serial.println(WiFi.macAddress());
#endif
//IO口中断
pinMode(KEY,INPUT_PULLUP);
//attachInterrupt(digitalPinToInterrupt(KEY), send_cmd, CHANGE);
}
void loop() {
if(Serial.available()>0){
while(Serial.available()>0){
usart_buffer[usart_buffer_pos]=Serial.read();
delay(2);
usart_buffer_pos++;
}
//发送数据
esp_now_send(broadcast_address,(uint8_t*)usart_buffer,usart_buffer_pos++);
#ifdef DEBUG
// uint8_t b[usart_buffer_pos];
// memcpy(b,usart_buffer,usart_buffer_pos);
Serial.printf("Get:%s\n",usart_buffer);
#endif
usart_buffer_pos=0;
}
if(KEY==0){
delay(10);
if(KEY==0){
send_cmd();
}
}
}
/*开始函数本体*/
/*
@功能:发送消息回调函数
@备注:这是一个回调函数,将在发送消息时执行。
在这种情况下,无论是否成功发送该消息,都会简单地打印出来
*/
void on_data_send_callback(uint8_t *mac_addr, uint8_t send_status) {
#ifdef DEBUG
Serial.print("Status: ");
if (send_status == 0){
Serial.println("ACK2");
}
else{
count--;
Serial.printf("Retrying:count=%d\n",count);
if(count<=0){
Serial.println("NACK2");
count=10;
}
}
#endif
}
/*
@功能:接收消息回调函数
*/
void on_data_rec_callback(uint8_t *mac_addr,uint8_t* incoming_data,uint8_t len){
#ifdef DEBUG
Serial.printf("Rec from Third Node:%s\n",incoming_data);
#endif
//发送应答
//转发到串口
Serial.printf("%s",incoming_data);
}
/*
@功能:生成CRC32表格
*/
void crc32_make_table(void){
int i, j, crc ;
crc32_have_table = 1 ;
for (i = 0 ; i < 256 ; i++)
for (j = 0, crc32_table[i] = i ; j < 8 ; j++)
crc32_table[i] = (crc32_table[i]>>1)^((crc32_table[i]&1)?POLYNOMIAL:0) ;
}
/*
@功能:crc32计算
*/
uint32_t crc32(uint32_t crc, char *buff, int len){
if (!crc32_have_table) crc32_make_table() ;
crc = ~crc;
for (int i = 0; i < len; i++)
crc = (crc >> 8) ^ crc32_table[(crc ^ buff[i]) & 0xff];
return ~crc;
}
/*
@功能:发送指令
*/
void send_cmd(void){
esp_now_send(broadcast_address,(uint8_t*)cmd_on,sizeof(cmd_on));
}
B板(点亮小灯)
/*
丙号板(机载)
双向模式
丙号板MAC:3c:61:05:fc:fd:4c
*/
#include <ESP8266WiFi.h>
#include <espnow.h>//ESP-NOW协议
#include <string.h>//处理字符串
/*开始宏定义*/
#define DEBUG 1
#define LASER 2
/*结束宏定义*/
/*开始函数原型*/
void on_data_send_callback(uint8_t *mac_addr, uint8_t send_status);
void on_data_rec_callback(uint8_t *mac_addr,uint8_t* incoming_data,uint8_t len);
void led(uint8_t state);//led状态设置
/*结束函数原型*/
/*开始全局变量*/
//配对设备MAC地址
//甲号板MAC:4c:75:25:0a:86:48
uint8_t broadcast_address[]={0x4c,0x75,0x25,0x0a,0x86,0x48};
//led状态
uint8_t led_state=1;//off
uint8_t led_board_state=0;
/*结束全局变量*/
void setup() {
//初始化串行监视器以进行调试
Serial.begin(115200);
//将设备设置为Wi-Fi站点
WiFi.mode(WIFI_STA);
//立即初始化ESP-NOW
if (esp_now_init() != 0) {
Serial.println("ERROR");
return;
}
//设置ESP8266角色 ESP_NOW_ROLE_CONTROLLER, ESP_NOW_ROLE_SLAVE,
//ESP_NOW_ROLE_COMBO(双向通信,sta), ESP_NOW_ROLE_MAX(双向通信,softap)
esp_now_set_self_role(ESP_NOW_ROLE_COMBO);
//绑定发送回调函数
esp_now_register_send_cb(on_data_send_callback);
//绑定接收回调函数
esp_now_register_recv_cb(on_data_rec_callback);
//与另一个ESP-NOW设备配对以发送数据
esp_now_add_peer(broadcast_address, ESP_NOW_ROLE_COMBO, 1, NULL, 0);
#ifdef DEBUG
//发送本机地址
Serial.print("Third MAC: ");
Serial.println(WiFi.macAddress());
#endif
//初始化引脚
pinMode(2,OUTPUT);
}
void loop() {
}
/*开始函数本体*/
/*
@功能:发送消息回调函数
@备注:这是一个回调函数,将在发送消息时执行。
在这种情况下,无论是否成功发送该消息,都会简单地打印出来
*/
void on_data_send_callback(uint8_t *mac_addr, uint8_t send_status) {
}
/*
@功能:接收消息回调函数
*/
void on_data_rec_callback(uint8_t *mac_addr,uint8_t* incoming_data,uint8_t len){
//转发数据
Serial.printf("Rec:%s",incoming_data);
//比较字符串
if(strstr((char*)incoming_data, "toggle") != NULL) {
led(led_state);
}
esp_now_send(broadcast_address,(uint8_t *)"ACK",sizeof("ACK"));
}
/*
@功能:切换LED/LASER状态
*/
void led(uint8_t state){
pinMode(LASER,OUTPUT);
led_state=!led_state;
digitalWrite(LASER,led_state);
Serial.printf("toggle to(1,off;0,on):%d",led_state);
}