3、ESP32 Wi-Fi Station模式:Wi-Fi初始化、连接路由器、事件处理回调、重连机制
好,咱们进入第三章。这一章要啃的,是ESP32作为IoT设备最基础也最关键的一步——连上Wi-Fi。
你想想看,MQTT再牛,底层也得靠TCP/IP。TCP/IP再牛,也得先有个Wi-Fi连接。所以,Wi-Fi Station模式就是一切通信的基石。我见过不少新手,MQTT代码写得飞起,结果卡在Wi-Fi连不上,折腾一整天。嗯,咱们今天就把这个坑填平。
3.1 Wi-Fi Station模式概述
ESP32可以工作在几种Wi-Fi模式下:
- Station模式(STA):作为客户端,连接到你家里的路由器。这是最常用的模式。
- AP模式:ESP32自己当热点,让手机连它。常用于配网或临时控制。
- AP+STA模式:一边连路由器,一边开热点。这种场景相对少一些。
咱们这门课,主要用Station模式。说白了,就是让ESP32像个手机一样,去连你家的Wi-Fi。
核心要点:ESP32的Wi-Fi驱动是基于FreeRTOS的事件驱动架构。你不需要轮询检查连接状态,而是注册回调函数,让系统在连接成功、断开、获取IP等事件发生时通知你。
3.2 Wi-Fi初始化流程
初始化Wi-Fi,我习惯分四步走。每一步都有它的道理,咱们一步步拆解。
- 初始化网络接口:调用
esp_netif_init()。这是ESP-IDF的网络抽象层,Wi-Fi和以太网都走它。 - 创建事件循环:调用
esp_event_loop_create_default()。这是事件处理的核心,后面所有的回调都挂在这个循环上。 - 创建Wi-Fi Station:调用
esp_netif_create_default_wifi_sta()。这会创建一个默认的Wi-Fi Station网络接口。 - 初始化Wi-Fi驱动:调用
esp_wifi_init(&cfg),传入配置结构体。这一步会启动Wi-Fi底层驱动。
代码看起来是这样的:
void wifi_init_sta(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();
assert(sta_netif);
// 4. 初始化Wi-Fi驱动
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
esp_wifi_init(&cfg);
}
我的习惯:我会在 esp_netif_create_default_wifi_sta() 之后,立即检查返回值。虽然它很少失败,但嵌入式开发嘛,多一份检查就少一份隐患。
3.3 连接路由器
初始化完成后,就该配置SSID和密码,然后发起连接了。这一步看似简单,但配置结构体里的细节值得注意。
// 配置Wi-Fi
wifi_config_t wifi_config = {
.sta = {
.ssid = "YOUR_SSID",
.password = "YOUR_PASSWORD",
.threshold.authmode = WIFI_AUTH_WPA2_PSK, // 最低安全等级
},
};
// 设置为Station模式
esp_wifi_set_mode(WIFI_MODE_STA);
// 配置参数
esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
// 启动Wi-Fi
esp_wifi_start();
// 发起连接
esp_wifi_connect();
这里有个细节:threshold.authmode 字段。它指定了ESP32能接受的最低安全等级。如果你家路由器是WPA2,但你设成WPA3,那可能连不上。我建议设成 WIFI_AUTH_WPA2_PSK,兼容性最好。
我曾经踩过的坑:SSID和密码不要硬编码在代码里,尤其是密码。我见过有人把代码传到GitHub上,结果家里的Wi-Fi密码全网公开。建议用NVS(非易失性存储)或配网功能来管理。
3.4 事件处理回调
这是Wi-Fi驱动的灵魂。ESP32的Wi-Fi事件分为两类:
- Wi-Fi事件:以
WIFI_EVENT_开头,比如连接成功、断开连接。 - IP事件:以
IP_EVENT_开头,比如获取到IP地址、IP冲突。
我们需要注册一个事件处理函数,来处理这些事件。我个人习惯把Wi-Fi和IP事件分开处理,逻辑更清晰。
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:
// Wi-Fi启动后,发起连接
esp_wifi_connect();
break;
case WIFI_EVENT_STA_DISCONNECTED:
// 断开连接,准备重连
ESP_LOGI(TAG, "Wi-Fi disconnected, trying to reconnect...");
esp_wifi_connect();
break;
default:
break;
}
} else if (event_base == IP_EVENT) {
if (event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
// 连接成功,可以开始MQTT了
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
}
你可能会问:为什么要用 xEventGroupSetBits?这是FreeRTOS的事件组,用来同步任务。当Wi-Fi连接成功并获取到IP后,主任务才能继续执行MQTT初始化。这是一种常见的多任务同步方式。
3.5 重连机制
Wi-Fi连接不是一劳永逸的。路由器重启、信号干扰、设备移动,都可能导致断开。没有重连机制的设备,就是个砖头。
ESP-IDF默认的重连行为是:断开后自动重连一次。但一次不够,我们需要更健壮的策略。
我常用的重连策略是这样的:
| 重连次数 | 等待时间 | 说明 |
|---|---|---|
| 第1次 | 1秒 | 快速重连,应对瞬时断开 |
| 第2次 | 2秒 | 稍微等待 |
| 第3次 | 4秒 | 指数退避 |
| 第4次 | 8秒 | 继续退避 |
| 第5次及以上 | 16秒 | 最大间隔,避免频繁重连 |
实现起来也不复杂:
#define MAX_RETRY_ATTEMPTS 10
static int s_retry_num = 0;
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 && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < MAX_RETRY_ATTEMPTS) {
// 指数退避,最大16秒
int delay = (1 << s_retry_num);
if (delay > 16) delay = 16;
ESP_LOGI(TAG, "Retry %d in %d seconds", s_retry_num + 1, delay);
vTaskDelay(pdMS_TO_TICKS(delay * 1000));
esp_wifi_connect();
s_retry_num++;
} else {
ESP_LOGE(TAG, "Max retry reached, giving up");
// 这里可以进入深度睡眠或重启
}
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
// 连接成功,重置重连计数
s_retry_num = 0;
}
}
我的经验:重连次数不要设得太大。10次是个合理的上限。如果10次都连不上,说明环境有问题,不如让设备休眠一段时间再试,或者通过其他方式(比如蓝牙)通知用户。
3.6 完整流程与状态机
把上面所有步骤串起来,Wi-Fi Station的完整生命周期就是一个状态机。我画了一张图,帮你理清思路。
这张图展示了Wi-Fi连接的完整生命周期。从初始化开始,经过启动、连接、获取IP,最终进入稳定连接状态。一旦断开,自动进入重连循环。
3.7 避坑指南
最后,分享几个我实际项目中遇到的坑,希望能帮你少走弯路。
我曾经遇到的坑:
- 事件处理函数中调用阻塞函数:事件处理函数是在系统任务中运行的,不能调用
vTaskDelay()或printf()等阻塞函数。否则会导致系统崩溃。正确的做法是发消息给其他任务处理。 - 忘记设置
threshold.authmode:如果不设置,默认值是WIFI_AUTH_OPEN,意味着ESP32会尝试连接任何开放网络。这既不安全,也可能导致连接失败。 - 重连时没有重置计数:如果连接成功后不重置
s_retry_num,下次断开时重连次数会累积,很快达到上限,导致设备彻底放弃连接。
嗯,这一章的内容就到这里。Wi-Fi连接是IoT设备的基础,看似简单,但细节决定成败。把这些基础打牢,后面的MQTT通信才能稳如泰山。