由"wifi: Haven't to connect to a suitable AP now"引出的关于ESP32 WIFI模块状态变化认识

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhejfl/article/details/85042368

目录

1、背景

1.1 参考资料

2、ESP32 连接WIFI热点流程​​

3、流程中各函数源码分析

3.1 void tcpip_adapter_init(void)

3.2 esp_err_t esp_event_loop_init(system_event_cb_t cb, void *ctx);

3.2.1仔细看esp_event_loop_init的源码

3.2.1.1继续深入,查看esp_event_loop_task,了解该任务主要做什么

3.2.1.1.1对于默认处理esp_event_process_default,继续深入

3.2.1.1.1.1深入查看defualt_event_handlers[event->event_id],包括哪些处理

3.2.1.2再看状态转给用户函数的处理

3.3 esp_err_t esp_wifi_init(const wifi_init_config_t *config);

3.4 esp_err_t esp_wifi_set_mode(wifi_mode_t mode);

3.5 esp_err_t esp_wifi_set_config(wifi_interface_t interface, wifi_config_t *conf);

3.6 esp_err_t esp_wifi_start(void);

3.7 esp_err_t esp_wifi_connect(void);

3.8 esp_err_t esp_wifi_stop(void);

4.出现wifi: Haven't to connect to a suitable AP now 异常


1、背景

现在想实现这样一个需求:在需要时,连接热点,HTTP下载指定东西;在不需要是,WIFI不工作(或者保持最低资源、功率消耗)。

我暂时没有太好的办法,请各位看到这篇博客的人,不吝赐教。接下去,是我对WIFI状态变化的一些资料收集。

1.1 参考资料

ESP-IDF源码

深入分析 ESP32 的 WiFi 状态机 https://blog.csdn.net/tidyjiang/article/details/71703241

2、ESP32 连接WIFI热点流程

下面是ESP32的WIFI连接流程

初始化tcp/ip适配层tcpip_addapter_init()---->初始化事件调度器esp_event_loop_init()--->初始化WIFI驱动esp_wifi_init()---->

设置WIFI模式esp_wifi_set_mode()------>配置WIFI接口的参数esp_wifi_set_config()----->启动WIFI esp_wifi_start()

在ESP-IDF中,整个WIFI协议栈是一个状态机,即它在各个时刻都有一个状态。用户可以根据自己的需求,让协议栈在运行到某个状态时自动处理某些工作。通常在事件循环evnet_loop中做这些存在。event 是状态机在WIFI协议栈的外在表现形式,下面用状体代替事件。

从esp_event_legacy.h可知状态类型()

3、流程中各函数源码分析

3.1 void tcpip_adapter_init(void)

设置 CONFIG_TCPIP_LWIP为1以使能这个函数。该函数主要完成初始化TCP/IP,默认IP地址信息,初始化同步信号和时钟信号。

3.2 esp_err_t esp_event_loop_init(system_event_cb_t cb, void *ctx);

该函数初始化事件循环,并创建event handler 和事件任务。

参数:system_event_cb_t 这是一个回调函数指针类型的参数,当某一事件发送时(即状态机的状态改变时),会调用这个回调函数

           ctx: 这是函数相关的上下文(contex),即系统在调用回调函数时需要传递给回调函数的参数

typedef esp_err_t  (*system_event_cb_t)(void *ctx, system_event_t *event);

该回调函数也有两个形参

void *ctx:这个参数是应用程序指定的,在调用esp_event_loop_init的第二个参数。

system_event_t *event: 指向系统事件(状态机的状态),包括状态ID, 状态信息,结构如下:

typedef struct {
    system_event_id_t     event_id;      /**< event ID */
    system_event_info_t   event_info;    /**< event information */
} system_event_t;

当状态机改变时,系统调用回调函数,回调函数根据这个来判断状态机处于哪个状态。

3.2.1仔细看esp_event_loop_init的源码

esp_err_t esp_event_loop_init(system_event_cb_t cb, void *ctx)
{
    if (s_event_init_flag) {
        return ESP_FAIL;
    }
    s_event_handler_cb = cb;
    s_event_ctx = ctx;
    s_event_queue = xQueueCreate(CONFIG_SYSTEM_EVENT_QUEUE_SIZE, sizeof(system_event_t));

    xTaskCreatePinnedToCore(esp_event_loop_task, "eventTask",
            ESP_TASKD_EVENT_STACK, NULL, ESP_TASKD_EVENT_PRIO, NULL, 0);

    s_event_init_flag = true;
    return ESP_OK;
}

从源码可以看出,

1、esp_event_loop将形参赋值给两个全局变量s_event_handler_cb/s_event_ctx。

2、创建一个状态队列s_event_queue,队列元素就是system_event_t 结构体类型,包括状态类型+状态信息。

3、该函数还创建了一个名为eventTask/优先级为20的任务。该函数仅能调用一次。

3.2.1.1继续深入,查看esp_event_loop_task,了解该任务主要做什么

static void esp_event_loop_task(void *pvParameters)
{
    while (1) {
        system_event_t evt;
        if (xQueueReceive(s_event_queue, &evt, portMAX_DELAY) == pdPASS) {
            esp_err_t ret = esp_event_process_default(&evt);
            if (ret != ESP_OK) {
                ESP_LOGE(TAG, "default event handler failed!");
            }
            ret = esp_event_post_to_user(&evt);
            if (ret != ESP_OK) {
                ESP_LOGE(TAG, "post event to user fail!");
            }
        }
    }
}

s_event_queue队列中取出一个事件,若取出成功,则调用状态的默认处理esp_event_process_default,再将该状态转给用户的应用程序。

3.2.1.1.1对于默认处理esp_event_process_default,继续深入

esp_err_t esp_event_process_default(system_event_t *event)
{
    if (event == NULL) {
        ESP_LOGE(TAG, "Error: event is null!");
        return ESP_FAIL;
    }

    esp_system_event_debug(event);
    if ((event->event_id < SYSTEM_EVENT_MAX)) {
        if (default_event_handlers[event->event_id] != NULL) {
            ESP_LOGV(TAG, "enter default callback");
            default_event_handlers[event->event_id](event);
            ESP_LOGV(TAG, "exit default callback");
        }
    } else {
        ESP_LOGE(TAG, "mismatch or invalid event, id=%d", event->event_id);
        return ESP_FAIL;
    }
    return ESP_OK;
}

检查状态是否在系统所列的状态列表内,若在,则进入defualt_event_handlers[event->event_id]。

3.2.1.1.1.1深入查看defualt_event_handlers[event->event_id],包括哪些处理

/* Default event handler functions

   Any entry in this table which is disabled by config will have a NULL handler.
*/
static system_event_handler_t default_event_handlers[SYSTEM_EVENT_MAX] = { 0 };

这是一个system_event_handler_t的函数指针数组,看看指针数组大概有哪些内容。即最终是调用一个默认的处理函数,根据状态类型的不同而不同。由于default_event_handlers数组默认为0,还需调用下面两个函数。

void esp_event_set_default_wifi_handlers()
{
     default_event_handlers[SYSTEM_EVENT_STA_START]        = system_event_sta_start_handle_default;
     default_event_handlers[SYSTEM_EVENT_STA_STOP]         = system_event_sta_stop_handle_default;
     default_event_handlers[SYSTEM_EVENT_STA_CONNECTED]    = system_event_sta_connected_handle_default;
     default_event_handlers[SYSTEM_EVENT_STA_DISCONNECTED] = system_event_sta_disconnected_handle_default;
     default_event_handlers[SYSTEM_EVENT_STA_GOT_IP]       = system_event_sta_got_ip_default;
     default_event_handlers[SYSTEM_EVENT_STA_LOST_IP]      = system_event_sta_lost_ip_default;
     default_event_handlers[SYSTEM_EVENT_AP_START]         = system_event_ap_start_handle_default;
     default_event_handlers[SYSTEM_EVENT_AP_STOP]          = system_event_ap_stop_handle_default;

     esp_register_shutdown_handler((shutdown_handler_t)esp_wifi_stop);
}

void esp_event_set_default_eth_handlers()
{
     default_event_handlers[SYSTEM_EVENT_ETH_START]           = system_event_eth_start_handle_default;
     default_event_handlers[SYSTEM_EVENT_ETH_STOP]            = system_event_eth_stop_handle_default;
     default_event_handlers[SYSTEM_EVENT_ETH_CONNECTED]       = system_event_eth_connected_handle_default;
     default_event_handlers[SYSTEM_EVENT_ETH_DISCONNECTED]    = system_event_eth_disconnected_handle_default;
     default_event_handlers[SYSTEM_EVENT_ETH_GOT_IP]          = system_event_eth_got_ip_default;
}

3.2.1.2再看状态转给用户函数的处理

static esp_err_t esp_event_post_to_user(system_event_t *event)
{
    if (s_event_handler_cb) {
        return (*s_event_handler_cb)(s_event_ctx, event);
    }
    return ESP_OK;
}

该函数在s_event_handler_cb这个全局不为NULL,调用用户的回调函数。回调函数的参数是全部变量s_event_ctx,以及状态event。

默认的暂时先不管,对于用户的回调函数,这里就是我们要让设备在状态机状态改变后要自动完成的工作

3.3 esp_err_t esp_wifi_init(const wifi_init_config_t *config);

该函数初始化WIFI,包括为WIFI驱动分配资源,如WIFI控制结构、接收/发送的缓冲、WIFI的NVS结构,这个函数还启动WIFI任务。

这个函数的注意点:

1、这个函数必须在其他所有WIFI API之前调用,否则就会报ESP_ERR_WIFI_NOT_INIT;

2、用WIFI_INIT_CONFIG_DEFAULT宏初始化配置为默认值,

返回值: ESP_OK /ESP_ERR_NO_MEM/others

esp_err_t esp_wifi_init(const wifi_init_config_t *config)
{
#ifdef CONFIG_PM_ENABLE
    if (s_wifi_modem_sleep_lock == NULL) {
        esp_err_t err = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "wifi",
                &s_wifi_modem_sleep_lock);
        if (err != ESP_OK) {
            return err;
        }
    }
#endif
    esp_event_set_default_wifi_handlers();
    esp_err_t result = esp_wifi_init_internal(config);
    esp_wifi_set_debug_log();

    return result;
}

调用esp_event_set_default_wifi_handlers()以设置默认的事件处理函数的数组指针。

调用esp_wifi_init_internal(),这才是真正的初始化WIFI驱动;只有在不适用网络堆栈才直接调用这个函数

调用esp_wifi-set_debug_log(),设置wifi的debug 标志开启

3.4 esp_err_t esp_wifi_set_mode(wifi_mode_t mode);

设置WIFI工作模式,默认为soft-ap模式(俗称的软AP模式)。可以设置为station、soft-ap、station+soft-AP。

typedef enum {
    WIFI_MODE_NULL = 0,  /**< null mode */
    WIFI_MODE_STA,       /**< WiFi station mode */
    WIFI_MODE_AP,        /**< WiFi soft-AP mode */
    WIFI_MODE_APSTA,     /**< WiFi station + soft-AP mode */
    WIFI_MODE_MAX
} wifi_mode_t;

WIFI_MODE_NULL h和WIFI_MODE_MAX用来判断参数mode 是否在需要的范围内。

3.5 esp_err_t esp_wifi_set_config(wifi_interface_t interface, wifi_config_t *conf);

该函数作用配置ESP32 STA或AP

注意点:1、只有在指定的接口使能的基础上,才能被成功调用,否则,就调用失败。

2、对于station的配置,bssid_set必须设为0,只有在应用程序需要检查热点的MAC地址才设置为1.

3、ESP32 一次只能在一个channel,因此当soft-AP+station模式时,soft-AP会自动跟随station的channel变化。

3.6 esp_err_t esp_wifi_start(void);

根据当前的配置,启动IWIFI;

如果ESP工作在STA,它创建一个Station control block 并启动staton。

如果ESP工作在soft-AP,它创建一个soft-AP control block 并启动soft-AP。

如果ESP工作在soft-AP+STA,它创建一个Station control block 和soft-AP control block并启动staton和soft-AP。

因此启动后状态机第一个状态是SYSTEM_EVENT_STA_START

3.7 esp_err_t esp_wifi_connect(void);

连接ESP32 WIFI Sataion 到热点。

注意点: 该函数只在STA或soft-AP+STA工作模式下起作用。如果已将连上一个热点,请调用esp_wifi_disconnnect 断开连接再连接。

由es_wifi_start_scan()触发的扫描活动直到连接AP才起作用。如果ESP32同时扫描和连接,ESP32会中止扫描工作,并返回错误和错误数ESP_ERR_WIFI_STATE。

如果你想在ESP32收到disconnect 事件时重连,请要加最大尝试次数,否则调用的扫描会不工作。特别是在找不到AP的情况下,还尝试连接就会出现上述异常。

3.8 esp_err_t esp_wifi_stop(void);

关闭 esp_wifi_start() 打开的资源。

4.出现wifi: Haven't to connect to a suitable AP now 异常

暂时解决:

去除以下代码,恢复正常。

wifiBits = xEventGroupWaitBits(wifi_event_group, ST_ALLBITS,
            false, false, portMAX_DELAY);
    if((wifiBits & CONNECTED_BIT)==CONNECTED_BIT)
    {

    }

有必要对FreeRTOS对EventGroup进行学习。

FreeRTOS事件标志组的学习 

5、WIFI流程中任务分析

这里分析一下,WIFI流程中,默认启动了哪些任务

app_mian在起始的main_task

esp_event_loop_init()启动了一个事件任务esp_event_loop_task和事件队列, 任务主要是完成WIFI的状态机。

esp_wifi_init() 启动了一个WIFI任务,负责WIFI底层的控制。

我的疑问在于:

我在main_task中启动了下面两个任务,并启动了一个one shoot的定时器。对于定时器的归属问题,以及在定时器中使用事件标志组的问题,需要了解一下

猜你喜欢

转载自blog.csdn.net/zhejfl/article/details/85042368