继ESP8266之后,ESP32上的 ESP-NOW 也已经开发出来。下面简单介绍一下 ESP32 上 ESP-NOW 的简单配置和使用。在本文末尾也给出了几个示例代码链接。 也可以参考
ESP8266 上 ESP-NOW 的使用文档。
在 ESP32 上使用 ESP-NOW 我们首先需要自己启动 Wi-Fi。
void start_wifi(void)
{
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg);
esp_wifi_set_mode(WIFI_MODE_STA); //WIFI 模式, 影响 espnow 配置的第三步.
esp_wifi_start();
}
Wi-Fi 启动后,接着对 ESP-NOW 进行配置。
Note : 在 ESP8266 中, Wi-Fi 是默认开启的, 所以没有启动 Wi-Fi 这一步.
espnow 的初始化由 esp_err_t esp_now_init(void); 完成.
if(esp_now_init() != ESP_OK) {
goto error;
}
PMK 用于加密匹配设备信息中的 LMK,生成通信时加密数据的 PSK. 只支持 16 字节长度。
PMK 的设置通过调用函数 esp_err_t esp_now_set_pmk(const uint8_t *pmk)
完成。
uint8_t pmk[16] = {0x01,0x02,0x03,0x04,0x01,0x02,0x03,0x04,0x01,0x02,0x03,0x04,0x01,0x02,0x03,0x04};
Note : 如果不设置 PMK, 可以不调用此接口。系统会使用一个默认的 PMK。如果设置 PMK, 则与之配对的设备需要设置相同的 PMK。
对于匹配设备,应用层会维护这样的一个信息结构:
struct esp_now_peer_info {
uint8_t peer_addr[6]; //匹配设备的 MAC 地址.
uint8_t lmk[16]; //通信时加密的密钥, 不使用加密时该密钥无效.
uint8_t channel; //通信时所处信道, 1~13 可用. 当 channel 为 0 时,
//表示从本地设备当前信道发送数据.
uint8_t ifidx; //通信时, 走 STA 接口还是 SoftAP接口. 与 wifi 初始化时的模式有关.
//如果 wifi 初始化为 Station 模式, 该值赋为0,
//如果 wifi 初始化为 SoftAP 模式, 该值赋为1,
//如果 wifi 初始化为 Station + SoftAP 模式, 该值赋为0或1均可.
//wifi 工作模式不影响数据的收发.
bool encrypt; //接收或发送的数据是否加密, 如果不设置加密,则赋值为 false.
void *priv; //私有数据,不用时可以为NULL.
};
添加匹配设备通过调用 esp_err_t esp_now_add_peer(const esp_now_peer_info_t *peer)
接口完成,参数就是包含匹配设备信息的结构体。
e.g.
esp_now_peer_info_t peer_info = {
.peer_addr = {0x24, 0x0a, 0xc4, 0x00, 0x01, 0x70},
.lmk = {0x02,0x03,0x04,0x05,0x02,0x03,0x04,0x05,0x02,0x03,0x04,0x05,0x02,0x03,0x04,0x05},
.channel = 1,
.ifidx = 0,
.encrypt = true,
.priv = NULL,
};
esp_now_add_peer(&peer_info);
ESP-NOW 发送回调函数用于接收应答信号,正常情况下每次发送数据后,会通过该回调来通知应用层底层是否发送成功
注 :不能保证对方应用层是否收到数据, 比如密钥不匹配时, 对方应用层不能收到数据, 但 ACK 仍是
ESP_NOW_SEND_SUCCESS
。
接收回调函数用于接收数据。
回调函数原型及回调注册函数如下所示:
void esp_now_send_cb(const uint8_t *mac_addr, esp_now_send_status_t status);
void esp_now_recv_cb(const uint8_t *mac_addr, const uint8_t *data, int data_len);
typedef void (*esp_now_recv_cb_t)(const uint8_t *mac_addr, const uint8_t *data, int data_len);
typedef void (*esp_now_send_cb_t)(const uint8_t *mac_addr, esp_now_send_status_t status);
esp_err_t esp_now_register_send_cb(esp_now_send_cb_t cb);
esp_err_t esp_now_register_recv_cb(esp_now_recv_cb_t cb);
Note : 收、发回调函数代码量不要太长, 因为回调函数是在 Wi-Fi task 中执行的, 代码量太大可能会引起堆栈溢出.
调用 esp_err_t esp_now_send(uint8_t *peer_addr, uint8_t *data, int len)
发送数据,单次发送数据长度不要超过 209 个字节。
e.g.
uint8_t data[] = "hello world";
uint8_t mac_addr[6] = {0x24, 0x0a, 0xc4, 0x00, 0x01, 0x70};
esp_now_send(mac_addr, data, sizeof(data));
Note : 该接口中 peer_addr 为通信对方的 mac 地址,如果本机维护了加密的匹配设备(第 3 步添加的设备),当 peer_addr 指定为 NULL 时,本机会遍历所 有匹配的设备并依次给其发送数据(广播地址除外),否则只发送给 mac_addr 指定的设备。需要注意的是当第三个参数 len 是 0 时,会导致看门狗复位,所以 当通过这种方式调用该接口时: esp_now_send(mac_addr, data, strlen((char *)data)); 要注意 data 是不是空的。
使用以上 API 时,建议对其返回值进行检查。返回值说明可以参考 esp-idf/components/esp32/include/esp_espnpw.h
里面的 API。
ESP32 上 ESP-NOW 支持发送广播数据,发送方需要调用 esp_now_add_peer
添加广播地址(即 0XFF-0XFF-0XFF-0XFF-0XFF-0XFF
)。广播数据不支持加密。
Station 或 Station + SoftAP 模式才可发送广播包,SoftAP 或 Station + SoftAP 模式才可接收广播数据。
当采用非加密通信方式时,两个设备第 3 步的 struct esp_now_peer_info
结构体中 encrypt 字段都要设置为 false,此时 PMK 和 LMK 无效。ESP32 支持 6
个加密密钥。加密设备若使用相同加密密钥,则设备个数可超过上述限制,非加密配对设备支持若干,与加密设备总数和不超过 20 个。
如果发送数据量相对较多时,可以将数据不断装入 buff 中(不要超过 209 个字节),然后循环发送,但如果发送间隔太短, 会导致发送回调混乱, 甚至系统重
启。一个解决办法是采用信号量。在发送回调里 give,每发送一个数据时,先尝试 take,这样可以保证每次发送数据时,前一次发送的回调函数已经退出。
e.g.
void espnow_send_cb(const uint8_t *mac_addr, esp_now_send_status_t status)
{
//do something;
xSemaphore.g.ive(xSemaphore);
}
void espnow_task(void *pvParameter)
{
//启动 WIFI
//初始化 espnow
//设置 PMK
//添加匹配设备
//注册发送回调
vSemaphoreCreateBinary(xSemaphore); //创建二值信号量
while(1) {
if(xSemaphoreTake(xSemaphore, (TickType_t) 10) == pdTRUE) {
//填充data
esp_now_send(mac_addr, date, strlen(date)); //发送数据
}
}
}
发送回调函数不能保证对方是否收到数据。如果需要,可以在应用层实现。每次接收方收到数据,就给发送方发送一个应答信号。
应用层同样可以实现发包重传,当发送方发现有限时间内没有等到接收方的应答信号,可以将数据重新发送一遍。但接收方要注意是否收到重包。可以对数据
包进行编号,根据编号来判断是否为重包。
如果 ESP-NOW 配置后不能发数据或接收数据,请检查 MAC 地址以及通信密钥是否错误。如果系统总是重启,请检查 Wi-Fi 模式与第三步 encrypt 的值是否冲突。
当接收设备为 station only 模式时,不建议开启 sleep 功能,在 sleep 状态下,会接收不到数据。
当不需要 ESP-NOW 功能时,可以通过 esp_now_deinit
接口关闭 ESP-NOW 功能。但不要先关闭 Wi-Fi 再关闭 ESP-NOW。