目录
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.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进行学习。
最终解决方法:
1、除了在定时器中调用XEventGroupWaitBits()并且是还是永久等待,这是一个大错误。
2、第二个错误在于调用esp_wifi_stop()之后调用esp_wifi_deinit(). 因事件任务中调用NULL指针处理默认状态程序,从导致StoreProhibited。 这是时序问题,考虑在事件循环的SYSTEM_EVENT_STA_STOP中调用 esp_wifi_deinit()。多任务之间的时序配合竟然如此重要。
case SYSTEM_EVENT_STA_STOP:
if(esp_wifi_deinit()!=ESP_OK) //释放wifi所有资源和WIFI任务
{
ESP_LOGE(SM_WFC,"DeInit failed");
}
break;
3、在调用esp_wifi_start()的函数 之后尽量不要处理其他事情,等待5S或等待获得IP后进行其他网络操作。
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的定时器。对于定时器的归属问题,以及在定时器中使用事件标志组的问题,需要了解一下。