事件循环
本来这篇文章是只写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_EVENT和ESP_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)