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);
}

效果

posted @ 2023-07-16 22:48  qsBye  阅读(707)  评论(0编辑  收藏  举报