ESP32 快速入门(二): ESP-NOW 使用

ESP-NOW on ESP32


继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 这一步.


1 . ESP-NOW 初始化

espnow 的初始化由 esp_err_t esp_now_init(void); 完成.

if(esp_now_init() != ESP_OK) {
        goto error;
}

2 . 设置 PMK

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。


3 . 添加匹配设备

对于匹配设备,应用层会维护这样的一个信息结构:

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

4 . 注册数据发送回调与接收回调函数

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 中执行的, 代码量太大可能会引起堆栈溢出.


5 . 使用 ESP-NOW 进行通讯

调用 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。


6 . 使用注意事项

  • 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。

你可能感兴趣的:(ESP32,ESP-NOW)