EPS32 WIFI的基本使用

主要函数解析

函数esp_netif_init()

esp_err_t esp_netif_init(void)

​ ESP32的WIFI连接是基于TCP服务的。这个函数的作用就是初始化底层的TCP/IP堆栈。可以说ESP32的WIFI模块依赖于这个函数。在使用WIFI之前需要先利用这个函数初始化TCP/IP。

函数esp_event_loop_create_default()

esp_err_t esp_event_loop_create_default(void)

​ 在例程中ESP32的使用是基于esp自带的一个esp-event事件组的。这个函数的作用就是创建一个默认的esp事件组并且不断的循环它。

函数esp_netif_create_default_wifi_ap()

esp_netif_t *esp_netif_create_default_wifi_ap(void)

​ 这个函数的作用就是创建默认的WIIF-AP,就是说创建一个默认的AP模式的WIFI。

函数esp_event_handler_instance_register()

esp_err_t esp_event_handler_instance_register(esp_event_base_t event_base, 
                                              int32_t event_id,
                                              esp_event_handler_t event_handler, 
                                              void *event_handler_arg, 
                                              esp_event_handler_instance_t *instance)

​ 这个函数就是注册一个事件到默认的esp-event循环中。这个event循环很有讲究,可以算的上是魔改版本的FreeRTOS中的事件了。在ESP32就很少调用原FreeRTOS中的事件标志组了。

  • 参数
    • event_base: 这个参数的意思就相当于是要向哪个事件标志组创建一个什么标志,这个参数就相当于是目标事件标志组
    • event_id: 这个参数指代的就是要创建的事件的ID
    • event_handler: 此ID对应的回调函数
    • event_handler_arg: 要向回调函数中传入的参数在回调函数中会以void* arg的形式来接收
    • instance: 与已注册事件处理程序和数据相关的事件处理程序实例对象,可以为NULL。 (暂时还不太理解,没怎么学过C++)

宏定义WIFI_INIT_CONFIG_DEFAULT()

​ 这个宏定义就是用来初始化默认WIFI参数的,配合着WIFI参数结构体来使用比如这样

wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();

结构体struct wifi_init_config_t

​ 这里面有许多关于WIFI设置的参数详细参考

https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32c3/api-reference/network/esp_wifi.html?highlight=wifi_init_config_t#_CPPv418wifi_init_config_t

函数esp_wifi_init()

esp_err_t esp_wifi_init(const wifi_init_config_t *config)

​ 这个就不用多说了,用来配合着之前配置好的WIFI结构体来初始化WIFI底层。

联合体union wifi_config_t

​ 这里面有两个成员

在AP模式下就配置结构体 wifi_ap_config_t ,在station模式下就配置 wifi_sta_config_t 就好

结构体wifi_ap_config_twifi_sta_config_t

​ 其中参数具体参考https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32c3/api-reference/network/esp_wifi.html?highlight=wifi_config_t#_CPPv413wifi_config_t

函数esp_wifi_set_mode()

esp_err_t esp_wifi_set_mode(wifi_mode_t mode)

​ 这个函数就是设置WIFI的工作模式的。可以设置为AP模式,STA模式,也可以同时设置为STA+AP模式。

函数esp_wifi_set_config()

esp_err_t esp_wifi_set_config(wifi_interface_t interface, wifi_config_t *conf)

​ 这个函数就是将之前写好的WIFI参数配置好

​ 需要注意的是,这个函数只能在接口已经开启的状态下调用,否则将会失败(配合着之前的esp_wifi_set_mode())函数吗?

函数esp_wifi_start()

esp_err_t esp_wifi_start(void)

​ 将之前配置好的WIFI启动(AP或者STA或者AT+STA)

WIFI AP模式下的使用流程

​ 程序参考自乐鑫官方的例程

WIFI AP模式的初始化流程

void wifi_init_softap(void)
{
    ESP_ERROR_CHECK(esp_netif_init());//开启TCP/IP堆栈
    ESP_ERROR_CHECK(esp_event_loop_create_default());//开启ESP-EVENT默认事件循环
    esp_netif_create_default_wifi_ap();//创建一个默认的AP WIIF

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();//默认的WIFI配置
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));//初始化WIIF底层

    //向循环事件标志组中创建一个WIIF事件
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                        ESP_EVENT_ANY_ID,
                                                        &wifi_event_handler,
                                                        NULL,
                                                        NULL));

    //配置WIIF连接(账号,密码等)
    wifi_config_t wifi_config = {
        .ap = {
            .ssid = EXAMPLE_ESP_WIFI_SSID,
            .ssid_len = strlen(EXAMPLE_ESP_WIFI_SSID),
            .channel = EXAMPLE_ESP_WIFI_CHANNEL,
            .password = EXAMPLE_ESP_WIFI_PASS,
            .max_connection = EXAMPLE_MAX_STA_CONN,
            .authmode = WIFI_AUTH_WPA_WPA2_PSK
        },
    };
    if (strlen(EXAMPLE_ESP_WIFI_PASS) == 0) {
        wifi_config.ap.authmode = WIFI_AUTH_OPEN;
    }

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));//设置WIFI为AP模式
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));//配置AP模式的WIIF
    ESP_ERROR_CHECK(esp_wifi_start());//开启WIIF

    ESP_LOGI(TAG, "wifi_init_softap finished. SSID:%s password:%s channel:%d",
             EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS, EXAMPLE_ESP_WIFI_CHANNEL);
}

WIFI事件的回调函数

static void wifi_event_handler(void* arg, esp_event_base_t event_base,
                                    int32_t event_id, void* event_data)
{
    //判断事件的ID并作出相应的操作
    if (event_id == WIFI_EVENT_AP_STACONNECTED) {
        wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
        ESP_LOGI(TAG, "station "MACSTR" join, AID=%d",
                 MAC2STR(event->mac), event->aid);
    } else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) {
        wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
        ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d",
                 MAC2STR(event->mac), event->aid);
    }
}

Wi-Fi驱动程序会将事件发送到默认事件循环。应用程序可以在使用进行注册的回调中处理这些事件esp_event_handler_register()。esp_netif组件还处理Wi-Fi事件,以提供一组默认行为。

例如,当Wi-Fi站连接到AP时,esp_netif将自动启动DHCP客户端(默认情况下)。
也就是说WIFI的使用基本离不开esp的事件循环组

WIFI Station模式下的使用流程

WIIF Station模式的初始化流程

void wifi_init_sta(void)
{
    s_wifi_event_group = xEventGroupCreate();//创建一个事件标志组

    ESP_ERROR_CHECK(esp_netif_init());//初始化TCP堆栈

    ESP_ERROR_CHECK(esp_event_loop_create_default());//创建一个默认的事件循环
    esp_netif_create_default_wifi_sta();//创建一个默认的WIFI STA模式

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();//将WIIF底层参数设置为默认值
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));//初始化WIFI底层驱动

    esp_event_handler_instance_t instance_any_id;//创建一个实例
    esp_event_handler_instance_t instance_got_ip;//创建一个获取ip的实例

    //创建一个WIFI_EVENT事件标志组,并且为这个组中任何事件ID都设置一个名为event_handler的回调函数,并且绑定一个任何ID的实例
    ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
                                                        ESP_EVENT_ANY_ID,
                                                        &event_handler,
                                                        NULL,
                                                        &instance_any_id));

    //创建一个IP_EVENT的事件标志组,并未IP_EVENT_STA_GOT_IP这个事件ID注册一个名为event_handler的回调函数(在esp-event中可以将多个事件位绑定为一个回调函数)并且绑定一个获取IP的实例
    ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
                                                        IP_EVENT_STA_GOT_IP,
                                                        &event_handler,
                                                        NULL,
                                                        &instance_got_ip));

    //用来配置WIFI STA模式下的参数(需要连接的WIFI账号以及密码)
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = EXAMPLE_ESP_WIFI_SSID,
            .password = EXAMPLE_ESP_WIFI_PASS,
            /* Setting a password implies station will connect to all security modes including WEP/WPA.
             * However these modes are deprecated and not advisable to be used. Incase your Access point
             * doesn't support WPA2, these mode can be enabled by commenting below line */
         .threshold.authmode = WIFI_AUTH_WPA2_PSK,

            .pmf_cfg = {
                .capable = true,
                .required = false
            },
        },
    };

    //设置WIFI模式为STA模式
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );

    //将之前设置好的WIFI参数配置
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );

    //开启WIIF(如果是STA模式的话开启WIFI会产生一个名为WIFI_EVENT_STA_START的事件标志,在事件任务中,它将启动DHCP客户端,该客户端最终将触发DHCP进程。)
    ESP_ERROR_CHECK(esp_wifi_start() );

    ESP_LOGI(TAG, "wifi_init_sta finished.");

    /* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
     * number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
            WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
            pdFALSE,
            pdFALSE,
            portMAX_DELAY);

    /* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
     * happened. */
    if (bits & WIFI_CONNECTED_BIT) {
        ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
                 EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
    } else if (bits & WIFI_FAIL_BIT) {
        ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
                 EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
    } else {
        ESP_LOGE(TAG, "UNEXPECTED EVENT");
    }

    /* The event will not be processed after unregister */
    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, instance_got_ip));
    ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, instance_any_id));
    vEventGroupDelete(s_wifi_event_group);
}

WIFI事件的回调函数

static void event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    //当事件标志组是WIFI_EVENT并且事件ID是WIFI_EVENT_STA_START时,就执行esp_wifi_connect()这个函数
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) &#123;
            esp_wifi_connect();
            s_retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP");
        &#125; else &#123;
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        &#125;
        ESP_LOGI(TAG,"connect to the AP fail");
    &#125; else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) &#123;
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    &#125;
&#125;

​ 一旦esp_wifi_connect()被调用,Wi-Fi驱动程序将开始内部扫描/连接过程。

​ 如果内部扫描/连接过程成功,将生成WIFI_EVENT_STA_CONNECTED。在事件任务中,它将启动DHCP客户端,该客户端最终将触发DHCP进程。

​ 由于例如密码错误,找不到AP等原因,Wi-Fi连接可能会失败。在这种情况下,会出现WIFI_EVENT_STA_DISCONNECTED并提供这种失败的原因。


​ 初始化DHCP客户端后,将开始IP阶段。如果从DHCP服务器成功接收到IP地址,则将出现IP_EVENT_STA_GOT_IP,并且事件任务将执行常规处理。

​ 在应用程序事件回调中,IP_EVENT_STA_GOT_IP被中继到应用程序任务。对于基于LwIP的应用程序,此事件非常特殊,这意味着该应用程序已准备就绪,可以开始其任务,例如创建TCP / UDP套接字等。一个非常常见的错误是在收到IP_EVENT_STA_GOT_IP之前初始化套接字。接收IP之前,请勿开始与套接字相关的工作。

后记

​ ESP32的WIFI驱动库是很全的并且配合着事件循环组以及ESP32的双核特性,可以在另一个核上跑WIFI 蓝牙 等等进程。这是很方便的。

​ 然而我对WIFI的相关协议了解的并不多…再接再厉吧

​ 后续再学习下WIFI相关协议以及esp-event事件组的相关操作,这个魔改板的FreeRTOS给人感觉很惊喜,有许多方便的功能,但是用起来还是有些不顺手。