【快速上手ESP32(基于ESP-IDF&VSCode)】10-事件循环&&WiFi

发布于:2024-04-30 ⋅ 阅读:(29) ⋅ 点赞:(0)

事件循环

本来这篇文章是只写WiFi的,但是写的时候才发现离不开事件循环,因此再多添一点内容在WiFi前面。

事件循环简单来说就是一个(循)环,我们可以在这个环上绑上一些事件,我们也可以监听这个环,当环上发生了事件,那么监听了对应事件的的处理函数就会执行,可以参考FreeRTOS的事件组。

使用事件循环

#include "esp_event.h"

创建&删除事件循环

esp_err_t esp_event_loop_create(const esp_event_loop_args_t *event_loop_args, esp_event_loop_handle_t *event_loop)

参数二是传出参数,把事件循环的句柄传出来,我们主要看看第一个参数的结构体。

事件循环队列的大小,也就是这个事件循环可以绑多少个事件。

事件名字随意。

优先级和栈大小根据实际情况而定。

最后一个ID实际上BaseType_t就是给一个整型。

esp_err_t esp_event_loop_delete_default(void)

删除只需要传入句柄。

注册&注销事件

esp_err_t esp_event_handler_register_with(esp_event_loop_handle_ t event_loop, esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler, void *event_handler_arg)

参数一传入事件循环句柄。

参数二是事件基ID,听起来像是给一个整数,但它实际上是个字符串。

剩下的参数就是事件ID,处理函数和给处理函数的参数。

然后处理函数是固定的格式,无返回值和固定的参数类型。每个参数是什么意思看名字应该就都能懂。

void fun(void* handler_arg, esp_event_base_t base, int32_t id, void* event_data){
    // 事件处理程序逻辑
}

注销事件使用下面的函数。

esp_err_t esp_event_handler_unregister_with( esp_event_loop_handle_t event_loop、 esp_event_base_t event_base、 int32_t event_id、 esp_event_handler_t event_handler)

两个ID都需要对应上之前注册过的事件。

发布事件

esp_err_t esp_event_post_to ( esp_event_loop_handle_t event_loop , esp_event_base_t event_base , int32_t event_id , const void * event_data , size_t event_data_size , TickType_t ticks_to_wait )

参数一传入事件循环句柄。

参数二是字符串的那个ID。

参数三是整数的ID,要两个ID都符合才会触发对应的事件处理函数。

参数四是传递给处理程序的参数,参数五是传递的参数的大小。

最后一个是阻塞时间。

发布事件时候,之前绑定了的对应的事件处理函数就会触发。

测试

#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "esp_event.h"
#include "esp_wifi.h"
#include "nvs_flash.h"

void fun(void *handler_arg, esp_event_base_t base, int32_t id, void *event_data){
    // 事件处理程序逻辑
    printf("fun is run\r\n");
}

void app_main(void){
    esp_event_loop_args_t eela = {
        .queue_size = 10,
        .task_core_id = 1,
        .task_name = "test",
        .task_priority = 1,
        .task_stack_size = 1024};
    esp_event_loop_handle_t eelh;
    esp_event_loop_create(&eela, &eelh); // 创建事件循环

    esp_event_handler_register_with(eelh, "test", 1, fun, NULL); // 绑定事件

    while (1)
    {
        esp_event_post_to(eelh, "test", 1, NULL, 1024, 100 / portTICK_PERIOD_MS); // 发送事件
        vTaskDelay(1000 / portTICK_PERIOD_MS);
        printf("过了1秒钟\r\n");
    }
}

可以看到上面的代码使得“test”的1号事件每隔一秒触发了一次,事件循环正常地运行着。

那么除了上面介绍的函数,还有一些其他的函数。

我们上面介绍的是左边一列的函数,而右边一列的其实差不多,函数名字也都很像。

差别就在于默认事件循环为用户提供了一套简单的事件处理机制,适用于大多数常规应用。而用户事件循环则为用户提供了更多的控制权和定制化能力,适用于需要精细管理事件或处理特殊事件的场景。

而我们接下来的WiFI需要用到的是默认事件循环,因为WiFI事件已经放到默认事件循环里了。

ESP32中的WiFi

官方手册里介绍的WiFi,专业名词看不太懂,不过问题不大,我们能用就行。

连接WiFi

#include "esp_wifi.h"

通常我们就是连接WiFI也就是STA模式,AP模式是自己开一个热点,不过这个用的比较少,因此我们看看STA的流程。

我们按照顺序来,首先是初始化阶段。

不过在这之前有一个问题在这里没有提到,那就是要先对nvs初始化,否则是使用不了WiFI的。

    nvs_flash_init();

初始化底层TCP/IP堆栈

esp_err_t esp_netif_init(void)

我们就调用一次,不用参数。

创建默认事件循环

esp_err_t esp_event_loop_create_default(void)

虽然上面编程指南中用的是创建用户事件循环,但是我找了不少资料都说创建默认事件循环。

那么我们就使用默认事件循环,也比较省事,没有需要配置的参数。

既然我们创建了事件循环,那么还需要的就是绑定事件了。

我们创建的默认事件循环,那么绑定事件的函数也需要和默认事件循环是配套的,可以参考上面的一个表格。

绑定WiFI事件

esp_err_t esp_event_handler_register ( esp_event_base_t event_base , int32_t event_id , esp_event_handler_t event_handler , void * event_handler_arg ) 

那么我们应该如何绑定WiFI事件呢。

参考我下面的代码。

esp_event_handler_register(WIFI_EVENT,ESP_EVENT_ANY_ID,fun,NULL);

在文章的开始就介绍了事件循环,那么上面这个例子大家应该能懂什么意思,要解释的就是前两个参数了,WIFI_EVENTESP_EVENT_ANY_ID,这两个宏的含义分别是WiFI事件和任意ID。

所以上面例子的意思就是我们绑定了WIFI的所有事件,也就是WiFi有任何风吹草动都会触发我们绑定的处理函数fun(这边名字随意,符合要求即可)。

创建STA

esp_netif_t *esp_netif_create_default_wifi_sta(void)

也是不用参数,但是会返回一个esp_netif_t类型的指针。

初始化WiFI

esp_err_t esp_wifi_init(const wifi_init_config_t *config)

需要传入一个参数,这个结构体相当复杂。

如果一个个去配置的话会相当麻烦,因此我们有一个宏定义可以帮我们都配置一个默认项,那就是WIFI_INIT_CONFIG_DEFAULT()

所以我们只需要像下面这样使用就可以啦。

    wifi_init_config_t wct = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&wct);

接下来我们进入到第二部分,WiFI配置。

设置WiFI模式

esp_err_t esp_wifi_set_mode(wifi_mode_t mode)

可以选择的模式有下面这些。

我们常用的就是AP和SAT以及APSTA,APSTA就是即可以是AP也可以是STA,跟我们手机一样,即可以连接WiFi也可以开热点。

配置STA

esp_err_t esp_wifi_set_config(wifi_interface_t interface, wifi_config_t *conf)

需要两个参数,第一个是WiFI接口,我们可以直接使用宏定义WIFI_IF_STA

第二个参数是union,也就是我们配置AP模式和配置STA模式还不一样。

我们直接看看STA怎么配置。

光看上面的表格我们就知道要怎么配置了,一般情况我们就配置要连接的WiFi名称和密码就够了,其他都用默认的就行。

配置完就可以启动了。

启动WiFi

esp_err_t esp_wifi_start(void)

连接WiFi

esp_err_t esp_wifi_connect(void)

现在有个问题,那就是我们需要把握住连接的时机,因为我们调用一次只会尝试连接WiFI一次,而有可能我们在调用的时候上面启动WiFI的流程还没走完。

因此我们可以在WiFi事件的处理函数里尝试连接。

如果触发事件的是WiFI事件,并且ID号和WIFI_EVENT_STA_START这个宏一致,那么我们就可以尝试连接了,因为这意味着STA已经启动了。

一切准备就绪,我们就可以开始连接了。

仅仅连接WIFI的完整示例代码

#include <stdio.h>
#include <string.h>
#include "esp_event.h"
#include "esp_wifi.h"
#include "nvs_flash.h"

void  fun(void* handler_arg,esp_event_base_t event_base,int32_t event_id,void* event_data){
    printf("%s,%ld\r\n",event_base,event_id);
    if(event_id==WIFI_EVENT_STA_START){         //如果是STA开启了,那么尝试连接
        esp_wifi_connect();
    }
}

void app_main(void){
    nvs_flash_init();                           //初始化nvs

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

    esp_event_loop_create_default();            //创建默认事件循环
    esp_event_handler_register(WIFI_EVENT,ESP_EVENT_ANY_ID,fun,NULL);       //绑定事件处理函数

    esp_netif_create_default_wifi_sta();        //创建STA,但是返回值没有用上,所以就不管它的返回值了

    wifi_init_config_t wict = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&wict);                       //初始化WiFI

    esp_wifi_set_mode(WIFI_MODE_STA);           //设为STA模式
    
    wifi_config_t wct = {
        .sta = {
            .ssid="zhetu",
            .password="zhetu123"
        }
    };
    esp_wifi_set_config(WIFI_IF_STA,&wct);      //设置WiFi

    esp_wifi_start();                           //启动WiFi

    while (1){
        
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

我手机的热点确实也被连接上了。

我们在上面的截图可以看到,事件处理函数中收到了来自WIFI_EVENT的两个事件,ID分别是2和4,我们可以去看看这分别代表什么含义。

看得出来2就是我们设定的连接条件,也就是STA开启了。

而4是我们连接上了WIFI。

为了使我们的程序更加健全,我们可以根据上面这一堆WIFI事件的事件ID来完善我们的事件处理函数。例如在WiFI断开连接的时候我们再次尝试连接。

连接完之后接下来我们进入下一个阶段。

获取IP

ID号是IP_EVENT_STA_GOT_IP,从名字我们也看得出来,这是IP事件里的,因此我们要获取IP的话需要独立于WiFi事件再单独绑定一个IP事件。

接下来再来一段示例代码,我在上面的连接WiFi的代码里再加一段,主要看我加的代码即可。

#include <stdio.h>
#include <string.h>
#include "esp_event.h"
#include "esp_wifi.h"
#include "nvs_flash.h"

void  fun(void* handler_arg,esp_event_base_t event_base,int32_t event_id,void* event_data){
    printf("%s,%ld\r\n",event_base,event_id);
    if(event_id==WIFI_EVENT_STA_START){         //如果是STA开启了,那么尝试连接
        esp_wifi_connect();
    }
}


//加了下面这段代码
void  fun1(void* handler_arg,esp_event_base_t event_base,int32_t event_id,void* event_data){
    printf("%s,%ld\r\n",event_base,event_id);
    if(event_id==IP_EVENT_STA_GOT_IP){       
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        printf("ip is %ld",event->ip_info.ip.addr);
    }
}
//加了上面这段代码




void app_main(void){
    nvs_flash_init();                           //初始化nvs

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

    esp_event_loop_create_default();            //创建默认事件循环
    esp_event_handler_register(WIFI_EVENT,ESP_EVENT_ANY_ID,fun,NULL);       //绑定事件处理函数


    //加了下面这段代码
    esp_event_handler_register(IP_EVENT,IP_EVENT_STA_GOT_IP,fun1,NULL);
    //加了上面这段代码



    esp_netif_create_default_wifi_sta();        

    wifi_init_config_t wict = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&wict);                       //初始化WiFI

    esp_wifi_set_mode(WIFI_MODE_STA);           //设为STA模式
    
    wifi_config_t wct = {
        .sta = {
            .ssid="zhetu",
            .password="zhetu123"
        }
    };
    esp_wifi_set_config(WIFI_IF_STA,&wct);      //设置WiFi

    esp_wifi_start();                           //启动WiFi

    while (1){
        
        vTaskDelay(1000 / portTICK_PERIOD_MS);
    }
}

由于打印的时候是按照长整型的打印的,换算之后是上面的这段16进制数,我们再按照每两个16进制转换为10进制,那就变成了102.168.168.192 ,再反过来192.168.168.102,这个就是我们的IP地址了。

剩下的阶段我们合一起,介绍一下函数就不演示了,因为也比较简单。

esp_err_t esp_wifi_disconnect(void)

esp_err_t esp_wifi_stop(void)

esp_err_t esp_wifi_deinit(void)