WiFi编程模型

image-20220821111031001

与FreeRTOS相关的概念

事件组

事件组是一种将事件传递给任务的方式

主要特征为:

  • 事件组允许任务进入阻塞态,等待多个事件之一的发生。
  • 当事件发生时,事件组使所有等待该事件的任务进入阻塞态。

事件组的特性

事件组,事件标志和事件比特

一个事件标志是一个布尔型变量,表示一个事件是否发生。

一个事件组是事件标志的集合。一个事件标志存储在一个位中,因此可以用一个类型为EventBits_t的变量中的不同位来表示不同事件,不同事件标志对应变量中的不同位。当该位为1时表示事件发生,为0时表示没发生。

EventBits_t的数据类型

该数据的位数与FreeRTOSConfig.h中的configUSE_16_BIT_TICKS有关,当configUSE_16_BIT_TICKS置为1时,每个事件组包含8个事件位,当置为0时,每个事件组包含24个事件位。

任务对于事件组的权限

所有直到该事件组存在的任务和中断都可获取该事件组,任一数量的任务都可对事件组进行读写操作。

esp32相关的概念

事件循环

esp32提供了esp_event库,用于替代freertos中的事件。并且Wi-Fi、以太网、IP等事件会被发送到此库提供的默认事件循环中。

来一个事件就执行一个事件处理函数,类似于单片机的中断。

传统的事件循环

在esp_event Library引入之前,wifi驱动,以太网,tcp/ip协议栈的事件都是通过传统的事件循环处理的,事件循环可以看作一个 while(1) ;循环。传统的事件循环只支持系统已经定义好的事件id和事件信息结构体,对于用户自定义的事件和蓝牙mesh事件是无法处理的。传统事件只支持一个事件处理函数,而且应用程序组件不能独自处理wifi和ip事件,需要应用程序将这些事件抛出。

esp事件库提供的事件循环

事件库用来取代传统的事件循环,并提供了一个默认的事件循环来处理wifi、以太网和ip事件。

事件库允许组件声明一个事件到其他组件注册的处理函数中,当wifi驱动等将事件发送给事件循环时,事件循环会调用相应的处理函数。

使用步骤:

  1. 创建事件循环。默认事件循环可以用 esp_event_loop_create_default()创建
  2. 把事件和事件处理函数注册到事件循环中 。注册函数为 esp_event_handler_register;事件处理函数的声明为 void event_handler(void *arg, esp_event_base_t event_base,int32_t event_id, void *event_data)
  3. 触发事件,执行事件处理函数;

WiFi和IP对应的event_base和event_id

esp默认事件循环会自动维护WiFi和IP的事件组,不需要程序额外控制

const char * WIFI_EVENT = "wifi_event";
const char * IP_EVENT = "ip_event";
typedef enum {  
    WIFI_EVENT_WIFI_READY = 0,           /**< ESP32 WiFi ready */
    WIFI_EVENT_SCAN_DONE,                /**< ESP32 finish scanning AP */
    WIFI_EVENT_STA_START,                /**< ESP32 station start */
    WIFI_EVENT_STA_STOP,                 /**< ESP32 station stop */
    WIFI_EVENT_STA_CONNECTED,            /**< ESP32 station connected to AP */
    WIFI_EVENT_STA_DISCONNECTED,         /**< ESP32 station disconnected from AP */
    WIFI_EVENT_STA_AUTHMODE_CHANGE,      /**< the auth mode of AP connected by ESP32 station changed */
    WIFI_EVENT_STA_WPS_ER_SUCCESS,       /**< ESP32 station wps succeeds in enrollee mode */
    WIFI_EVENT_STA_WPS_ER_FAILED,        /**< ESP32 station wps fails in enrollee mode */
    WIFI_EVENT_STA_WPS_ER_TIMEOUT,       /**< ESP32 station wps timeout in enrollee mode */
    WIFI_EVENT_STA_WPS_ER_PIN,           /**< ESP32 station wps pin code in enrollee mode */
    WIFI_EVENT_AP_START,                 /**< ESP32 soft-AP start */
    WIFI_EVENT_AP_STOP,                  /**< ESP32 soft-AP stop */
    WIFI_EVENT_AP_STACONNECTED,          /**< a station connected to ESP32 soft-AP */
    WIFI_EVENT_AP_STADISCONNECTED,       /**< a station disconnected from ESP32 soft-AP */
    WIFI_EVENT_AP_PROBEREQRECVED,        /**< Receive probe request packet in soft-AP interface */
} wifi_event_t;
typedef enum {  
    IP_EVENT_STA_GOT_IP,               /*!< ESP32 station got IP from connected AP */
    IP_EVENT_STA_LOST_IP,              /*!< ESP32 station lost IP and the IP is reset to 0 */
    IP_EVENT_AP_STAIPASSIGNED,         /*!< ESP32 soft-AP assign an IP to a connected station */
    IP_EVENT_GOT_IP6,                  /*!< ESP32 station or ap or ethernet interface v6IP addr is preferred */
    IP_EVENT_ETH_GOT_IP,               /*!< ESP32 ethernet got IP from connected AP */
} ip_event_t;

wifi_config_t结构体

wifi_config_t结构体用于wifi连接前的配置信息

typedef struct {  
    uint8_t ssid[32];//SSID
    uint8_t password[64];//密码
    uint8_t ssid_len;//SSID长度,若设为0则会自动查找到终止字符;否则会在规定长度处截断
    uint8_t channel;//AP的信道
    wifi_auth_mode_t authmode;//授权模式
    uint8_t ssid_hidden;//是否广播SSID,默认为0-广播;设为1则不广播
    uint8_t max_connection;//能连接的最大节点数量,默认为4,最大为4
    uint16_t beacon_interval;//信标间隔,默认100ms,应设置在100-60000ms内
} wifi_ap_config_t;

使用到的库函数

错误处理

ESP_ERROR_CHECK//用来检查返回值是否为ESP_OK。

ESP_LOGI//espressif的格式化输出信息
ESP_LOGE//(错误信息)
ESP_LOGD//(调试信息)。

事件组相关

xEventGroupCreate创建一个事件组

xEventGroupSetBits用于将一个事件组中一个或多个事件位置位,它表明相关事件的发生。本函数不能在中断中调用。中断函数中有专门函数

xEventGroupClearBits清除标志位。不能在中断中调用

xEventGroupWaitBits在先前创建的事件组中锁定并等待一个或多个位被置位。不能在中断中调用

事件循环相关

esp_event_loop_create_default创建默认事件循环

esp_event_handler_register在系统事件循环中注册事件处理程序。

WiFi相关

初始化与设置

esp_wifi_initWiFi功能初始化,config为初始化结构体指针
esp_wifi_set_config设置WiFi的配置
esp_wifi_set_mode模式设置
esp_wifi_get_mode获取当前模式
esp_wifi_get_config获取当前配置

关闭wifi

esp_wifi_stop关闭wifi并释放资源
esp_wifi_deinit释放所有初始化时分配的资源,并停止wifi进程,不需要wifi功能时可以使用

连接/断开wifi

用于STA或STA+AP
esp_wifi_connect连接WiFi
esp_wifi_disconnect断开WiFi
用于AP或STA+AP模式
esp_wifi_deauth_sta停止对所有等于aid的设备进行授权
esp_wifi_ap_get_sta_aid获取接入设备的AID
esp_wifi_ap_get_sta_list获取当前接入的设备列表

例程分析

/* WiFi station Example
   This example code is in the Public Domain (or CC0 licensed, at your option.)
   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
/* The examples use WiFi configuration that you can set via project configuration menu
   If you'd rather not, just change the below entries to strings with
   the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
*/
#define EXAMPLE_ESP_WIFI_SSID "name"//要连接的wifi名字
#define EXAMPLE_ESP_WIFI_PASS "password"//对应的wifi密码
#define EXAMPLE_ESP_MAXIMUM_RETRY 3//最大重连次数
/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;//声明一个事件组,用来表示wifi的连接状态。此事件组和默认事件循环的事件组不是一个东西,前者需要我们自己维护,后者不需要
/* The event group allows multiple bits for each event, but we only care about two events:
 * - we are connected to the AP with an IP
 * - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0 //定义的事件组的0位作为连接成功的标志
#define WIFI_FAIL_BIT BIT1      //定义的事件组的1位作为连接失败的标志

static const char *TAG = "wifi station";//标签
static int s_retry_num = 0;

//事件处理函数
static void event_handler(void *arg, esp_event_base_t event_base,int32_t event_id, void *event_data)
{  
    //WIFI_EVENT_STA_START为初始事件状态
    //连接成功后,事件状态位会变为WIFI_EVENT_STA_CONNECTED,之后会调用DHCP进行请求IP,
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START)
    {    
        esp_wifi_connect();
    }
    //如果连接失败,事件状态位会变为WIFI_EVENT_STA_DISCONNECTED,继续连接直到连接次数超过了最大允许值
    else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
    {    
        if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY)
        {        
            esp_wifi_connect();
            s_retry_num++;
            ESP_LOGI(TAG, "retry to connect to the AP");
        }
        else
        {        
            //超过了最大连接次数还连不上,则置位事件组的WIFI_FAIL_BIT标志
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        }
        ESP_LOGI(TAG, "connect to the AP fail");
    }
    //在拿到IP之后事件状态会变更为IP_EVENT_STA_GOT_IP。在这里我们使用了xEventGroupSetBits,设置事件组标志,
    else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP)
    {    
        //将传递过来的数据格式化为ip格式
        ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
        //将IP格式华为字符串打印出来
        ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
        //重置重试次数
        s_retry_num = 0;
        //成功获取了IP,置位事件组中的WIFI_CONNECTED_BIT标志
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

//初始化wifi,并设置工作模式为sta模式
void wifi_init_sta(void)
{
    s_wifi_event_group = xEventGroupCreate();//FreeRTOS创建事件组
    ESP_ERROR_CHECK(esp_netif_init());//初始化底层TCP/IP堆栈
    ESP_ERROR_CHECK(esp_event_loop_create_default());//创建默认事件循环
    esp_netif_create_default_wifi_sta();//创建默认的 WIFI STA. 如果出现任何初始化错误,此API将中止。
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();//创建初始化wifi的默认信息
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));//用上述配置信息来初始化WiFi硬件。

    //在默认事件循环中注册事件和事件处理函数
    //注册一个事件句柄到WIFI_EVENT事件,如果发生任何事件(ESP_EVENT_ANY_ID),则调用event_handler,无额外参数传递
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
    //注册一个事件句柄到IP_EVENT事件,如果发生获取了IP事件(IP_EVENT_STA_GOT_IP),则调用event_handler,无额外参数传递
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));

    //创建wifi连接时的配置信息,这里只配置了ssid和password
    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},
        },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));//设置wifi模式为sta
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));//设置wifi的配置参数
    ESP_ERROR_CHECK(esp_wifi_start());//启动wifi工作
    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) */
    //执行下面的函数时,程序会等待wifi连接完成(WIFI_CONNECTED_BIT置位)或者超过最大尝试次数后连接失败(WIFI_FAIL_BIT置位)
    //两种状态的置位都是在上边的event_handler()回调函数中执行的
    //因此,可以在调用此函数后再执行需要先获取IP地址才能执行的函数
    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");
    }
    //注销一个事件句柄到WIFI_EVENT事件,如果发生任何事件(IP_EVENT_STA_GOT_IP),不再执行回调函数event_handler,无参数传递
    ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler));
    //注销一个事件句柄到WIFI_EVENT事件,如果发生任何事件(ESP_EVENT_ANY_ID),不再执行回调函数event_handler,无参数传递
    ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler));
    //FreeRTOS销毁wifi事件组
    vEventGroupDelete(s_wifi_event_group);
}

void app_main(void)
{
    //Initialize NVS
    esp_err_t ret = nvs_flash_init();//初始化NVS存储(非易失性flash)
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
    {    
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);
    ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
    wifi_init_sta();//按照sta模式初始化wifi
}
最后修改:2023 年 05 月 07 日
如果觉得我的文章对你有用,请随意赞赏