4、WiFi基础与Station模式:WiFi协议栈简介、ESP32连接路由器、WiFi事件处理、自动重连机制
好,咱们进入WiFi部分。说实话,在智能家居网关里,WiFi是那个「承上启下」的角色——它负责跟你的手机、云服务器打交道。Zigbee和蓝牙搞定设备端,WiFi搞定网络端。这一章,我带你把ESP32的WiFi Station模式玩透。
4.1 WiFi协议栈,到底是个啥?
很多初学者一听到「协议栈」三个字就头大。其实说白了,它就是一套分层的通信规则。你想想看,你的手机和路由器之间要传数据,总得有人管「怎么连上」「数据包怎么拆」「出错怎么办」这些事吧?
ESP32的WiFi协议栈,底层是硬件MAC层,往上走是TCP/IP协议栈(LwIP),再往上才是我们写的应用代码。我个人习惯把这三层记成:物理连接 → 网络通信 → 业务逻辑。
核心要点:ESP32的WiFi驱动已经封装好了,我们不需要碰底层。你只需要调用API,告诉它「我要连哪个路由器,密码是多少」就行。但理解分层结构,对排查问题很有帮助。
我在项目中遇到过一个问题:设备连上WiFi但就是发不出数据。查了半天,发现是LwIP的内存池配小了。你看,不懂协议栈分层,这种问题根本无从下手。
4.2 Station模式:让ESP32连上路由器
Station模式,就是让ESP32扮演一个「客户端」角色,去连接你家里的无线路由器。这是最常用的模式,没有之一。
代码其实很简单,我直接给你看核心流程:
#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"
void wifi_init_sta(void)
{
// 1. 初始化NVS(非易失性存储)
esp_err_t ret = nvs_flash_init();
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);
// 2. 初始化网络接口
ESP_ERROR_CHECK(esp_netif_init());
// 3. 创建默认事件循环
ESP_ERROR_CHECK(esp_event_loop_create_default());
// 4. 创建WiFi Station网络接口
esp_netif_create_default_wifi_sta();
// 5. 初始化WiFi驱动
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// 6. 配置并启动
wifi_config_t wifi_config = {
.sta = {
.ssid = "你的路由器SSID",
.password = "你的路由器密码",
.threshold.authmode = WIFI_AUTH_WPA2_PSK,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_LOGI(TAG, "WiFi初始化完成,正在连接...");
}
嗯,这里要注意:NVS初始化不能省。WiFi的配置信息(比如MAC地址、校准数据)都存在NVS里。我第一次用ESP32时跳过了这步,结果WiFi死活起不来,折腾了半小时才发现。
4.3 WiFi事件处理:别傻等,要监听
你可能会想:「我调用完esp_wifi_start(),是不是就自动连上了?」不是的。WiFi连接是异步操作,你需要通过事件处理来知道「连上了没」「断开了没」。
ESP32的事件机制,说白了就是「你注册一个回调函数,系统有消息就通知你」。我个人觉得这个设计很优雅,比轮询舒服多了。
static void 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_START) {
// WiFi启动成功,开始连接
esp_wifi_connect();
ESP_LOGI(TAG, "WiFi启动,开始连接路由器");
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
// 断开了!需要重连
ESP_LOGI(TAG, "WiFi断开,5秒后重连...");
esp_wifi_connect();
}
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;
ESP_LOGI(TAG, "获取到IP: " IPSTR, IP2STR(&event->ip_info.ip));
}
}
// 注册事件处理器
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));
你看,三个关键事件:
- WIFI_EVENT_STA_START:WiFi驱动启动完毕,这时候才能调用
esp_wifi_connect() - WIFI_EVENT_STA_DISCONNECTED:连接断开,触发重连
- IP_EVENT_STA_GOT_IP:拿到IP地址,这才是真正「连上了」的标志
我的经验:千万不要在WIFI_EVENT_STA_START里做太多耗时操作。我曾经在里面加了个延时,结果导致事件循环卡死,WiFi连不上。事件回调里只做「轻量级」操作,重活交给任务队列。
4.4 自动重连机制:别让设备掉线
智能家居设备最怕什么?掉线。你想想看,半夜灯控失灵,用户肯定骂娘。所以自动重连是必须的。
ESP32的WiFi驱动其实自带了一个简单的重连机制——调用esp_wifi_set_auto_connect(true)后,断线时会自动尝试重连。但说实话,这个机制太「傻」了,它不会做退避,断了就猛连,反而可能把路由器搞崩。
我建议自己实现一个带退避的重连逻辑:
#define MAX_RETRY 10 // 最大重试次数
#define BASE_RETRY_MS 1000 // 初始重试间隔(1秒)
static int retry_count = 0;
static void 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 (retry_count < MAX_RETRY) {
// 指数退避:1s, 2s, 4s, 8s...
int delay_ms = BASE_RETRY_MS << retry_count;
if (delay_ms > 30000) delay_ms = 30000; // 最大30秒
ESP_LOGI(TAG, "第%d次重连,等待%dms", retry_count + 1, delay_ms);
vTaskDelay(pdMS_TO_TICKS(delay_ms));
esp_wifi_connect();
retry_count++;
} else {
ESP_LOGE(TAG, "重连次数耗尽,进入深度休眠");
// 这里可以触发报警或进入低功耗模式
}
}
else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
// 连上了,重置重试计数
retry_count = 0;
ESP_LOGI(TAG, "重连成功,重置计数器");
}
}
注意:指数退避不是越复杂越好。我曾经设计过一个「动态退避算法」,根据信号强度调整间隔,结果测试时发现逻辑太复杂,反而引入了bug。后来老老实实用固定指数退避,稳定运行两年没出过问题。有时候,简单就是可靠。
4.5 实战中的几个坑
最后,我把自己踩过的坑列出来,你遇到了直接对照:
| 问题 | 现象 | 解决办法 |
|---|---|---|
| WiFi初始化失败 | esp_wifi_init返回错误 | 检查NVS是否初始化,WiFi堆大小是否足够 |
| 连不上路由器 | 一直触发DISCONNECTED事件 | 检查SSID/密码,确认路由器是2.4GHz频段 |
| 拿到IP后很快断开 | DHCP租约问题 | 设置静态IP,或缩短DHCP请求超时 |
| 重连越来越慢 | 重试次数耗尽 | 检查退避逻辑,确认retry_count在成功时清零 |
嗯,这一章的内容就这些。WiFi Station模式是网关的基础,你把它吃透了,后面的TCP通信、MQTT连接就顺理成章。下一章我们聊ESP32的SoftAP模式——让设备自己变成热点,这在配网场景里特别有用。