2、Station模式基础:Wi-Fi初始化流程、事件循环机制、连接回调函数
好,咱们正式开始动手写代码了。这一章我带你走一遍 ESP32 作为 Wi-Fi Station(也就是客户端)的完整初始化流程。说白了,就是让 ESP32 像你的手机一样,去连一个路由器。
很多新手上来就抄例程,结果连不上也不知道问题出在哪。我个人习惯是先把底层机制理清楚,再动手。这样出了问题,你心里有数。
2.1 Wi-Fi 初始化流程:三步走
ESP32 的 Wi-Fi 初始化,其实就三个核心步骤。我把它总结成一张图,你先有个整体印象。
嗯,这张图你看明白了吗?下面我拆开细说。
2.1.1 初始化网络接口
第一步,调用 esp_netif_init()。这个函数干什么?它创建了 TCP/IP 协议栈的底层环境。没有它,你的 Wi-Fi 连上了也收发不了数据。
我踩过的坑: 刚做项目时,我忘了调这个函数,结果 Wi-Fi 连接状态显示成功,但 ping 不通任何 IP。查了两天才发现是 netif 没初始化。从那以后,我写代码第一件事就是检查这三步有没有漏。
2.1.2 创建事件循环
第二步,esp_event_loop_create_default()。这个函数创建了一个默认的事件循环。事件循环是什么?说白了,就是 ESP32 内部的一个消息总线。Wi-Fi 状态变了、连接断了、拿到了 IP,这些消息都会通过这个总线发出来。你的代码只需要注册好“听众”(回调函数),就能及时响应。
2.1.3 初始化 Wi-Fi 驱动
第三步,esp_wifi_init(&cfg)。这里需要传入一个 wifi_init_config_t 结构体。我一般直接用 WIFI_INIT_CONFIG_DEFAULT() 这个宏,省事。
// 标准初始化代码片段
esp_netif_init(); // 1. 初始化网络接口
esp_event_loop_create_default(); // 2. 创建事件循环
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg); // 3. 初始化 Wi-Fi 驱动
2.2 事件循环机制:别再用轮询了
很多单片机开发者习惯用轮询——在主循环里不断查状态。但在 ESP32 上,我强烈建议你用事件驱动。为什么?因为 Wi-Fi 连接是个异步过程,轮询会浪费 CPU,而且容易漏掉短暂的状态变化。
ESP32 的事件循环机制,你可以理解成一个“订阅-发布”系统。你的代码订阅感兴趣的事件,当事件发生时,系统自动调用你的回调函数。
2.2.1 事件类型
Wi-Fi 相关的事件主要分两类:
| 事件基 | 典型事件 | 触发时机 |
|---|---|---|
WIFI_EVENT |
WIFI_EVENT_STA_START |
Station 模式启动完成 |
WIFI_EVENT |
WIFI_EVENT_STA_CONNECTED |
与 AP 建立 Wi-Fi 连接(链路层) |
WIFI_EVENT |
WIFI_EVENT_STA_DISCONNECTED |
与 AP 断开连接 |
IP_EVENT |
IP_EVENT_STA_GOT_IP |
DHCP 成功获取到 IP 地址 |
IP_EVENT |
IP_EVENT_STA_LOST_IP |
IP 地址丢失 |
我的经验: 很多初学者只关注 WIFI_EVENT_STA_CONNECTED,以为连上就完事了。其实真正能上网的标志是 IP_EVENT_STA_GOT_IP。你想想看,连上路由器但没拿到 IP,跟没连有什么区别?
2.2.2 注册事件回调
注册回调用 esp_event_handler_register() 函数。你需要指定事件基、具体事件 ID,以及你的回调函数指针。
// 注册 Wi-Fi 事件和 IP 事件的回调
esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL);
esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &ip_event_handler, NULL);
注意这里我用的是 ESP_EVENT_ANY_ID,意思是这个回调处理所有 Wi-Fi 事件。你也可以只注册特定事件,更精细控制。
2.3 连接回调函数:实战中的写法
好了,理论说完了,咱们直接看代码。下面是我在实际项目中用的回调函数模板。
// Wi-Fi 事件回调
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT) {
switch (event_id) {
case WIFI_EVENT_STA_START:
printf("Wi-Fi Station 已启动,开始连接...\n");
esp_wifi_connect(); // 触发连接
break;
case WIFI_EVENT_STA_CONNECTED:
printf("已连接到 AP,等待 IP...\n");
break;
case WIFI_EVENT_STA_DISCONNECTED:
printf("连接断开,5秒后重连...\n");
// 我曾经在这里忘记加延时,导致疯狂重连把 AP 搞崩了
vTaskDelay(pdMS_TO_TICKS(5000));
esp_wifi_connect();
break;
}
}
}
// IP 事件回调
static void ip_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
printf("获取到 IP: " IPSTR "\n", IP2STR(&event->ip_info.ip));
// 到这里,才真正可以开始网络通信
}
}
⚠️ 重要提醒: 回调函数是在事件循环的任务上下文中执行的。不要在回调里做耗时操作,比如延时、大块内存分配、阻塞式网络请求。否则会阻塞整个事件循环,导致其他事件无法处理。
2.4 完整的 Station 初始化与连接代码
把上面所有内容串起来,就是一个完整的 Station 初始化流程。我习惯把它封装成一个函数。
void wifi_sta_init(void)
{
// 1. 初始化网络接口
esp_netif_init();
// 2. 创建默认事件循环
esp_event_loop_create_default();
// 3. 创建默认的 Wi-Fi Station 网卡
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
// 4. 初始化 Wi-Fi 驱动
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg);
// 5. 注册事件回调
esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
&wifi_event_handler, NULL);
esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
&ip_event_handler, NULL);
// 6. 配置 Station 模式
wifi_config_t wifi_config = {
.sta = {
.ssid = "YOUR_SSID",
.password = "YOUR_PASSWORD",
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
},
};
esp_wifi_set_mode(WIFI_MODE_STA);
esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
// 7. 启动 Wi-Fi
esp_wifi_start();
printf("Wi-Fi Station 初始化完成,等待连接...\n");
}
注意: 第3步 esp_netif_create_default_wifi_sta() 很多人会漏掉。它创建了 Wi-Fi Station 的网络接口实例,并自动关联到 TCP/IP 协议栈。没有这一步,你的 Wi-Fi 连上了也拿不到 IP。
2.5 避坑指南:我踩过的三个坑
做 Wi-Fi 项目这么多年,有些坑我印象特别深。分享给你,希望你不用再走一遍。
-
回调里做阻塞操作
我曾经在
WIFI_EVENT_STA_GOT_IP回调里直接发起 HTTP 请求,结果事件循环被卡死,后续事件全部丢失。后来改成用队列把消息发给主任务处理,问题解决。 -
重连不加延时
有一次设备在信号弱的环境下,断开后立即重连,反复失败。我在
DISCONNECTED事件里加了 5 秒延时,重连成功率大幅提升。你想想看,路由器也需要时间释放资源。 -
忽略事件循环的优先级
默认事件循环运行在系统任务中,优先级较高。如果你的应用任务优先级设置不当,可能会导致事件处理延迟。我一般把应用任务优先级设得比事件循环低一级。
2.6 本章小结
这一章我们走通了 Station 模式的完整初始化流程。记住三个核心点:
- 初始化顺序:netif → 事件循环 → Wi-Fi 驱动
- 事件驱动代替轮询,注册回调处理连接状态
- 真正可用的标志是
IP_EVENT_STA_GOT_IP,不是CONNECTED