4. Wi-Fi基础与Station模式:Wi-Fi协议栈简介、ESP32 Station模式配置、Wi-Fi连接状态管理、Wi-Fi事件处理
好,咱们进入第四章。这一章讲的是Wi-Fi基础,特别是Station模式。说实话,Wi-Fi这块内容,很多做嵌入式的人觉得简单——不就是调个API连上路由器吗?但我在实际项目中踩过的坑告诉我,这里面的门道还真不少。
你想想看,一个Mesh网络里,每个节点都要靠Wi-Fi来通信。如果连Wi-Fi连接都搞不稳固,那整个Mesh网络就是空中楼阁。所以这一章,咱们把Wi-Fi的底裤扒干净。
4.1 Wi-Fi协议栈简介
Wi-Fi协议栈,说白了就是一套通信规则。ESP32用的是802.11 b/g/n标准,工作在2.4GHz频段。我刚开始做Wi-Fi项目时,总觉得协议栈这东西离应用层很远,直到有一次排查丢包问题,才发现是MAC层的重传机制没搞明白。
ESP32的Wi-Fi协议栈分三层:
- 物理层(PHY):负责信号的调制解调,说白了就是把0和1变成无线电波。ESP32支持802.11n,最高速率150Mbps,但Mesh场景下我们一般用不到那么高。
- MAC层:负责帧的封装、解封装,还有CSMA/CA(载波监听多点接入/冲突避免)。嗯,这里要注意,Wi-Fi的冲突避免机制和以太网不一样,它是先听后说,不是边听边说。
- 网络层:IP协议、ARP协议这些。ESP32的Wi-Fi驱动已经帮你处理好了,你只需要关心IP地址怎么来。
核心要点:ESP32的Wi-Fi协议栈是乐鑫自己封装的,底层基于FreeRTOS的任务调度。每个Wi-Fi事件都会触发一个回调,你可以在回调里做状态管理。我个人习惯把Wi-Fi事件处理单独放在一个任务里,避免阻塞主循环。
4.2 ESP32 Station模式配置
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());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
// 3. 初始化Wi-Fi
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// 4. 配置Station模式
wifi_config_t wifi_config = {
.sta = {
.ssid = "YOUR_SSID",
.password = "YOUR_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());
printf("Wi-Fi Station模式已启动,正在连接...\n");
}
这段代码看起来简单,但我曾经犯过一个低级错误——忘记初始化NVS。结果Wi-Fi配置死活存不住,每次重启都要重新配。你想想看,一个Mesh节点如果每次断电都要重新配网,那用户体验得多糟糕。
我的经验:配置Wi-Fi时,建议把SSID和密码放在结构体里统一管理。我在一个项目里遇到过SSID和密码长度超过32字节的情况,结果Wi-Fi连接失败,查了半天才发现是缓冲区溢出了。ESP32的SSID最大32字节,密码最大64字节,别超了。
4.3 Wi-Fi连接状态管理
Wi-Fi连接不是一锤子买卖。你连上路由器之后,信号可能会变差,路由器可能会重启,甚至有人会把路由器关掉。所以状态管理很重要。
ESP32的Wi-Fi状态机大概有这几个状态:
| 状态 | 含义 | 常见原因 |
|---|---|---|
| WIFI_STATE_INIT | Wi-Fi初始化完成 | 刚调用esp_wifi_init() |
| WIFI_STATE_START | Wi-Fi已启动 | 调用esp_wifi_start()后 |
| WIFI_STATE_CONNECTING | 正在连接路由器 | 调用esp_wifi_connect()后 |
| WIFI_STATE_CONNECTED | 已连接,但还没拿到IP | 关联成功,DHCP进行中 |
| WIFI_STATE_GOT_IP | 已获取IP地址 | DHCP成功 |
| WIFI_STATE_DISCONNECTED | 连接断开 | 信号弱、路由器重启等 |
我个人习惯用一个全局变量来记录当前状态:
typedef enum {
WIFI_STATE_IDLE,
WIFI_STATE_CONNECTING,
WIFI_STATE_CONNECTED,
WIFI_STATE_GOT_IP,
WIFI_STATE_DISCONNECTED
} wifi_state_t;
wifi_state_t g_wifi_state = WIFI_STATE_IDLE;
为什么要这么做?因为Mesh网络里,每个节点需要知道自己的网络状态,才能决定要不要转发数据。如果Wi-Fi断了,节点应该进入本地缓存模式,等网络恢复后再同步数据。
避坑指南:我曾经在一个项目中,Wi-Fi断开后没有及时更新状态,结果节点还在拼命往外发数据,全部丢掉了。后来我加了一个定时器,每5秒检查一次Wi-Fi状态,如果断开就停止数据发送。嗯,这个习惯我一直保留到现在。
4.4 Wi-Fi事件处理
ESP32的事件处理机制,说白了就是注册回调函数。当Wi-Fi状态发生变化时,系统会触发相应的事件,你在回调里做处理。
常用的事件有这些:
- WIFI_EVENT_STA_START:Station模式启动完成。这时候可以调用esp_wifi_connect()去连接路由器。
- WIFI_EVENT_STA_CONNECTED:ESP32与路由器建立了Wi-Fi连接。注意,这时候还没拿到IP地址。
- WIFI_EVENT_STA_DISCONNECTED:连接断开。你需要在这里做重连处理。
- IP_EVENT_STA_GOT_IP:拿到了IP地址。这时候网络才算真正可用。
看代码:
static void 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已启动,开始连接...\n");
esp_wifi_connect();
g_wifi_state = WIFI_STATE_CONNECTING;
break;
case WIFI_EVENT_STA_CONNECTED:
printf("已连接到路由器,等待IP...\n");
g_wifi_state = WIFI_STATE_CONNECTED;
break;
case WIFI_EVENT_STA_DISCONNECTED:
printf("Wi-Fi连接断开,5秒后重连...\n");
g_wifi_state = WIFI_STATE_DISCONNECTED;
vTaskDelay(pdMS_TO_TICKS(5000));
esp_wifi_connect();
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;
printf("获取到IP地址: " IPSTR "\n", IP2STR(&event->ip_info.ip));
g_wifi_state = WIFI_STATE_GOT_IP;
}
}
}
这里有个关键点:WIFI_EVENT_STA_DISCONNECTED事件里,我加了5秒的延时再重连。为什么?因为如果路由器只是短暂重启,你立刻重连只会加重网络负担。5秒的间隔,既不会让用户等太久,也不会把路由器搞崩溃。
核心要点:事件处理里不要做耗时操作。如果你需要在断开时保存数据,建议用队列把任务发给其他任务处理。我在一个项目里直接在事件回调里写Flash,结果导致Wi-Fi驱动卡死,整个系统都崩了。从那以后,我所有的事件回调都只做状态更新和日志打印,真正的业务逻辑放到任务里去做。
4.5 实战中的坑与经验
最后,分享几个我在项目中遇到的坑:
- Wi-Fi重连风暴:如果路由器信号不好,ESP32会频繁触发DISCONNECTED事件,然后你又在回调里不断重连,形成恶性循环。我的解决方案是加一个重连计数器,连续失败5次后就进入深度睡眠,等一段时间再试。
- DHCP超时:有些老旧路由器DHCP响应很慢,ESP32默认的超时时间可能不够。你可以通过esp_netif_set_hostname()设置主机名,或者手动配置静态IP。我个人建议Mesh节点用静态IP,省去DHCP的麻烦。
- 信道切换:如果路由器开启了自动信道选择,ESP32可能会在连接过程中遇到信道不匹配的问题。解决办法是在配置里指定信道,或者让ESP32做信道扫描。
嗯,这一章的内容就到这里。Wi-Fi基础打牢了,后面Mesh网络的通信才能稳如老狗。下一章咱们讲AP模式,到时候你会看到ESP32怎么从一个「客户端」变成「热点」。