利用ESP8266+ESPNOW实现多点无线通信

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

  • 6
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值