ESP32的ESP-NOW通讯踩坑记

描述

1、背景

前段时间看到乐鑫推出了一种很有意思的Wi-Fi通讯协议,它允许设备在连接的时候进行直接通讯

乐鑫对它的概述如下:

ESP-NOW 是一种由乐鑫公司定义的无连接 Wi-Fi 通信协议。在 ESP-NOW 中,应用程序数据被封装在各个供应商的动作帧中,然后在无连接的情况下,从一个 Wi-Fi 设备传输到另一个 Wi-Fi 设备。

感觉很有意思,于是找了乐鑫 IDF 提供的demo进行验证。这里用到的demo在IDF的路径为:esp-idf/examples/wifi/espnow。

找来几个ESP32-C3模组,烧录完之后,确实在没有连接路由器的情况下可以进行直接通讯。

2、例程改造

通过运行demo:esp-idf/examples/wifi/espnow发现这个Demo有点不足的地方是没有连接路由器,那么我在想,有没有可能在既连接路由器的情况下,又可以直接与其他没有连接路由器的设备进行通讯呢?

那么我的想法如下图

ESP32

在上面这里,A设备既连接路由器也直接与B/C/D三个设备基于ESP_NOW进行直接通讯。

这里我让A设备工作在STA+AP模式下,其中STA用于连接路由,AP用于ESP_NOW进行通讯。基于此设想,我还专门问了deepSeek,它也给出了方案可行的确定回答;ESP32

于是开开心心的写了代码,其中Wi-Fi和ESP_NOW的初始化程序如下,然后分别替换esp-idf/examples/wifi/espnow这里的初始化;

  • Wi-Fi的初始化如下

#define ESP_AP_CHANNEL 6 // 固定使用信道6
static void example_wifi_init(void)
{
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = "xLogMonitor",
            .password = "1234567890",
        },
    };
    // 配置AP模式参数 - 关键部分!
    wifi_config_t ap_config = {
        .ap = {
            .ssid = "",             // 空SSID,不广播
            .ssid_len = 0,
            .channel = ESP_AP_CHANNEL, // 这是我们真正关心的参数
            .authmode = WIFI_AUTH_OPEN,
            .max_connection = 0,    // 不允许任何站连接
        },
    };

    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();
    esp_netif_t *ap_netif = esp_netif_create_default_wifi_ap();
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
    ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                        ESP_EVENT_ANY_ID,
                                                        &event_handler,
                                                        NULL,
                                                        &instance_any_id));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
                                                        IP_EVENT_STA_GOT_IP,
                                                        &event_handler,
                                                        NULL,
                                                        &instance_got_ip));
    ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_APSTA) );
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap_config));
    ESP_ERROR_CHECK( esp_wifi_start());
    // ESP_ERROR_CHECK( esp_wifi_set_channel(CONFIG_ESPNOW_CHANNEL, WIFI_SECOND_CHAN_NONE));

#if CONFIG_ESPNOW_ENABLE_LONG_RANGE
    ESP_ERROR_CHECK( esp_wifi_set_protocol(ESPNOW_WIFI_IF, WIFI_PROTOCOL_11B|WIFI_PROTOCOL_11G|WIFI_PROTOCOL_11N|WIFI_PROTOCOL_LR) );
#endif

}
 

  • ESP_NOW的初始化如下

static esp_err_t example_espnow_init(void)
{
    example_espnow_send_param_t *send_param;

    s_example_espnow_queue = xQueueCreate(ESPNOW_QUEUE_SIZE, sizeof(example_espnow_event_t));
    if (s_example_espnow_queue == NULL) {
        ESP_LOGE(TAG, "Create mutex fail");
        return ESP_FAIL;
    }

    /* Initialize ESPNOW and register sending and receiving callback function. */
    ESP_ERROR_CHECK( esp_now_init() );
    ESP_ERROR_CHECK( esp_now_register_send_cb(example_espnow_send_cb) );
    ESP_ERROR_CHECK( esp_now_register_recv_cb(example_espnow_recv_cb) );

    /* Set primary master key. */
    ESP_ERROR_CHECK( esp_now_set_pmk((uint8_t *)CONFIG_ESPNOW_PMK) );

    /* Add broadcast peer information to peer list. */
    esp_now_peer_info_t *peer = malloc(sizeof(esp_now_peer_info_t));
    if (peer == NULL) {
        ESP_LOGE(TAG, "Malloc peer information fail");
        vSemaphoreDelete(s_example_espnow_queue);
        esp_now_deinit();
        return ESP_FAIL;
    }
    memset(peer, 0sizeof(esp_now_peer_info_t));
    peer->channel = ESP_AP_CHANNEL;
    peer->ifidx = ESP_IF_WIFI_AP;
    peer->encrypt = false;
    memcpy(peer->peer_addr, s_example_broadcast_mac, ESP_NOW_ETH_ALEN);
    ESP_ERROR_CHECK( esp_now_add_peer(peer) );
    free(peer);

    /* Initialize sending parameters. */
    send_param = malloc(sizeof(example_espnow_send_param_t));
    if (send_param == NULL) {
        ESP_LOGE(TAG, "Malloc send parameter fail");
        vSemaphoreDelete(s_example_espnow_queue);
        esp_now_deinit();
        return ESP_FAIL;
    }
    memset(send_param, 0sizeof(example_espnow_send_param_t));
    send_param->unicast = false;
    send_param->broadcast = true;
    send_param->state = 0;
    send_param->magic = esp_random();
    send_param->count = CONFIG_ESPNOW_SEND_COUNT;
    send_param->delay = CONFIG_ESPNOW_SEND_DELAY;
    send_param->len = CONFIG_ESPNOW_SEND_LEN;
    send_param->buffer = malloc(CONFIG_ESPNOW_SEND_LEN);
    if (send_param->buffer == NULL) {
        ESP_LOGE(TAG, "Malloc send buffer fail");
        free(send_param);
        vSemaphoreDelete(s_example_espnow_queue);
        esp_now_deinit();
        return ESP_FAIL;
    }
    memcpy(send_param->dest_mac, s_example_broadcast_mac, ESP_NOW_ETH_ALEN);
    example_espnow_data_prepare(send_param);

    xTaskCreate(example_espnow_task, "example_espnow_task"2048, send_param, 4NULL);

    return ESP_OK;
}
 

程序跑起来了,确实也是符合我的预期,A设备连接上了路由器,然后同时也和BCD进行了通讯,感觉一切都很完美,如果不出意外的话就好出意外了

3、问题出现了

在当我为改造例程的成功而高兴的时候,问题出现了。我换了一个路由器,发现通讯失败了。esp_send出现了如下错误:

E (12554) ESPNOW: Peer channel is not equal to the home channel, send fail!
 

这下懵逼了,为啥换个路由器就不行,难道这协议还和路由器有关系,乐鑫的官方文档也没提到呀~

乐鑫文档: https://docs.espressif.com/projects/esp-idf/zh_CN/v4.4.6/esp32c3/api-reference/network/esp_now.html?highlight=esp_now_init#

4、问题分析

从错误的日志上来看这个错误指的是Peer channel 和home channel,不一致导致的。

这里Peer指的应该是要发送的对端通道,home应该指的是本身;

但是这两个地方我在配置的时候都是指定了#define ESP_AP_CHANNEL 6 // 固定使用信道6这个通道为啥还会出现这个错误呢?

后续我继续运行多几次,发现只有在A设备连接上网络之后才会出现这个错误。

难道是连接路由器之后A设备的信道发生了变化?经过查阅资料得知,确实是这样的,STA设备在连接路由器之后,设备的Wi-Fi信道是由路由器决定的,所以当路由器分配的信道和#define ESP_AP_CHANNEL 6不一致的时候就会出现问题了。

于是我在esp_now_send之前打印了设备当前的信道信息,获取到信息如下

I (12554) sta_ap_espnow: STA连接信息: SSID=xLogMonitor, 信道=8, RSSI=-61
I (12554) sta_ap_espnow: 当前工作信道: 8
E (12554) ESPNOW: Peer channel is not equal to the home channel, send fail!
E (12564) sta_ap_espnow: ESP-NOW数据发送失败: 0x3066
 

理论和实验结果一致,当设备连接之后信道变为了8

于是想要解决当前这个问题也很简单了,只需要把所有的ESP_NOW的设备改成和所要连接的路由器同个信道8即可。

结果证实了,当信道改为8之后所有设备通讯就正常了;

5、总结

虽然我们知道了问题,也解决了问题。但是乐鑫文档在描述这一块时也没着重,导致当时看文档也没特别留意。ESP32

ESP32-C3的STA+AP模式,在STA连接完路由器之后,AP模式指定的信道并不能作为ESP_NOW通讯;

如果设备需要连接路由器,需要提前知道连接的路由器信道再给其他设备指定一个统一的信道才能进行ESP_NOW进行通讯。这个在后续的产品落地确实是一个问题,需要看看如何优化

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分