一.ESP8266简介

ESP8266是乐鑫(Espressif)制作的WiFi芯片,可以将其理解为一块带有WiFi功能的MCU,就像是stm32一样,它也有GPIO、UART、SPI、I2C、I2S等硬件资源,它也可以跑RTOS等操作系统。

基于乐鑫提供的ESP8266芯片,许多厂商设计了自己的WiFi模组,比较有名的是安信可(Ai-Thinker)的ESP系列模组,正点原子也制作了它自己的模组:ATK-ESP8266,除此之外还有非常非常多种类的WiFi模组,他们很多都是基于ESP8266芯片开发的。

很多WiFi模组外都自带一个屏蔽罩,用来减少外界的电磁干扰,ATK-ESP8266使用的是板载PCB天线,除此之外还有陶瓷天线和IPEX天线等。

我们可以把WiFi模组理解成MCU的最小系统板,可以直接用串口为它烧录程序(和51单片机比较相似)。一般WiFi模块出厂都自带了固件,它其实就是出厂时厂家给模块烧录的程序,我们可以在这个基础上用AT指令实现一些简单的功能,但是更复杂的功能就需要自己编程实现了。

二.ESP8266开发

开发ESP8266有很多种方式,可以使用乐鑫官方提供的ESP-IDF开发环境,基于官方SDK开发,也可以使用LUA语言开发,也可以使用Arduino方式开发,我使用的是Arduino方式开发。

使用Arduino开发ESP8266的环境配置,网上有非常多的教程,我随便找了一个:【ESP8266之Arduino开发】

一、环境配置

在使用NodeMcu(可以理解为开发板)时,因为其自带了自动下载电路(见下图),所以直接插上USB就可以用Arduino IDE为其烧录程序,并且会自动运行烧录进去的程序。

esp32天线可以掰掉吗 esp8266天线_esp8266


要注意的是使用正点原子ATK-ESP8266模块时,IO_0引脚拉低才能进入烧录模式,IO_0拉高是运行模式(悬空应该也是运行模式),在改变IO_0的电平之后,一定要给模块复位,才能成功切换模式。

三.ESP-NOW协议

ESP-NOW 是由乐鑫开发的另一款协议,可以使多个设备在没有或不使用 Wi-Fi 的情况下进行通信。这种协议类似常见于无线鼠标中的低功耗 2.4GHz 无线连接——设备在进行通信之前要进行配对。配对之后,设备之间的连接是持续的、点对点的,并且不需要握手协议。

有关ESP-NOW更详细的介绍可以查看官方的手册:ESP-NOW 用户指南,也可以看乐鑫在其SDK中的介绍:ESP-IDF编程指南,这应该是有关ESP-NOW所有的官方资料了。除此之外,该网站上有关使用Arduino开发ESP8266的教程也非常棒。

利用ESP-NOW协议我们可以实现多设备点对点的双向通信。

esp32天线可以掰掉吗 esp8266天线_无线通讯_02


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

分别将esp8266_1.inoesp8266_2.inoesp8266_3.ino烧录进模块1、2、3即可。

效果如下图所示:

esp32天线可以掰掉吗 esp8266天线_esp8266_03

五.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