4、Wi-Fi连接与网络基础:ESP32的Station与AP模式,Wi-Fi事件处理与重连机制
说到ESP32的Wi-Fi,我其实挺感慨的。早年做嵌入式项目,Wi-Fi模块都是外挂的,AT指令一包一包地发,调试起来那叫一个痛苦。现在ESP32把Wi-Fi、蓝牙、MCU全集成在一块芯片上,说实话,开发体验好了不止一个档次。
但Wi-Fi这东西,看着简单,坑却不少。你想想看,一个设备如果连不上网,那后面所有的MQTT、数据上传、远程控制全白搭。所以这一章,咱们把ESP32的Wi-Fi基础打扎实了。
4.1 Station模式与AP模式:两种角色,两种场景
ESP32的Wi-Fi可以工作在两种模式下,说白了就是两种身份。
Station模式(客户端模式):ESP32去连接一个已有的Wi-Fi热点。比如你家路由器叫"TP-LINK_5G",ESP32连上去,这就是Station模式。我大部分物联网项目都用这个模式,设备通过路由器上网,跟云端通信。
AP模式(热点模式):ESP32自己创建一个Wi-Fi热点,其他设备可以连上来。比如手机直接连ESP32,进行配网或者查看数据。我在做智能家居网关时,经常用AP模式做初始配置——设备上电后先开热点,手机连上去设置Wi-Fi密码。
还有一种混合模式,叫Station + AP 共存模式。ESP32一边连着路由器上网,一边开着热点让别人连。嗯,这个模式挺实用,但要注意内存开销会大一些。
核心要点:
- Station模式:ESP32作为客户端,连接外部Wi-Fi
- AP模式:ESP32作为热点,最多支持4个客户端连接
- 共存模式:同时扮演两个角色,适合网关类设备
4.2 初始化Wi-Fi的代码套路
我个人习惯用ESP-IDF框架,它的Wi-Fi API设计得比较清晰。下面这段代码,是我项目里常用的初始化模板。
#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"
void wifi_init_station(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());
ESP_LOGI("WIFI", "Wi-Fi初始化完成,正在连接...");
}
这段代码看着长,其实就四步:初始化NVS、初始化网络接口、初始化Wi-Fi驱动、配置并启动。我刚开始用ESP-IDF时,老忘记初始化NVS,结果Wi-Fi死活起不来。后来养成了习惯,每次新建项目先把NVS初始化写上。
小技巧:SSID和密码建议放在单独的头文件里,或者用Kconfig配置。别硬编码在代码里,不然换网络就得重新编译。
4.3 Wi-Fi事件处理:别让程序瞎跑
Wi-Fi连接不是瞬间完成的。你调用esp_wifi_start()后,ESP32会异步地去扫描、认证、关联、获取IP。这个过程需要事件机制来跟踪。
ESP-IDF使用事件循环(event loop)来处理Wi-Fi状态变化。常用的Wi-Fi事件有这么几个:
| 事件 | 含义 | 典型处理 |
|---|---|---|
| WIFI_EVENT_STA_START | Station模式启动 | 开始连接AP |
| WIFI_EVENT_STA_CONNECTED | 成功连接到AP | 准备获取IP |
| WIFI_EVENT_STA_DISCONNECTED | 与AP断开连接 | 触发重连逻辑 |
| IP_EVENT_STA_GOT_IP | 获取到IP地址 | MQTT等上层协议启动 |
| IP_EVENT_STA_LOST_IP | IP地址丢失 | 重新获取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:
esp_wifi_connect();
break;
case WIFI_EVENT_STA_CONNECTED:
ESP_LOGI("WIFI", "已连接到AP");
break;
case WIFI_EVENT_STA_DISCONNECTED:
ESP_LOGI("WIFI", "连接断开,5秒后重连");
// 重连逻辑
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("WIFI", "获取到IP: " IPSTR, IP2STR(&event->ip_info.ip));
// 在这里启动MQTT客户端
}
}
}
// 注册事件处理
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
&wifi_event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
&wifi_event_handler, NULL));
为什么事件处理这么重要?我曾经有个项目,设备部署在工厂里,Wi-Fi信号不太稳定。一开始我没处理WIFI_EVENT_STA_DISCONNECTED事件,结果设备断网后就傻在那了,再也不连了。后来加上重连逻辑,问题才解决。
4.4 重连机制:别让设备变"孤儿"
Wi-Fi断连是常态,不是异常。你想想看,路由器重启、信号干扰、设备移动,都可能导致断连。所以重连机制是必须的。
ESP-IDF默认有简单的重连机制,但不够智能。我一般自己实现一套重连策略:
- 指数退避:第一次断连等1秒重试,第二次等2秒,第三次等4秒...最大间隔不超过60秒。这样既不会频繁重试浪费电,也不会错过恢复时机。
- 最大重试次数:连续重试10次还连不上,就进入深度休眠或者切换到AP模式等待配网。
- 状态上报:每次重连成功或失败,都通过日志或者LED指示灯反馈给用户。
下面是我常用的重连代码片段:
#define MAX_RETRY_COUNT 10
static int retry_count = 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 (retry_count < MAX_RETRY_COUNT) {
retry_count++;
int delay_ms = (1 << retry_count) * 1000; // 指数退避
if (delay_ms > 60000) delay_ms = 60000;
ESP_LOGI("WIFI", "第%d次重连,等待%dms", retry_count, delay_ms);
vTaskDelay(pdMS_TO_TICKS(delay_ms));
esp_wifi_connect();
} else {
ESP_LOGE("WIFI", "重连失败,进入配网模式");
// 切换到AP模式,等待用户重新配网
wifi_enter_config_mode();
}
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
retry_count = 0; // 连接成功,重置计数器
ESP_LOGI("WIFI", "重连成功");
}
}
注意:事件处理函数里不要做阻塞操作,比如延时、打印大量日志。否则会影响Wi-Fi协议栈的正常运行。上面的vTaskDelay在事件处理里其实不太合适,更好的做法是发一个消息给任务,让任务去处理重连。
4.5 实战中的避坑指南
做Wi-Fi开发这么多年,踩过的坑不少。分享几个典型的:
- 信道问题:ESP32默认扫描所有信道,但如果你知道AP在哪个信道,可以指定信道加快连接速度。我在一个项目中,设备每次上电要等10秒才连上,后来指定了信道,3秒就搞定了。
- MAC地址过滤:有些路由器开启了MAC地址过滤,ESP32连不上。调试时先关掉这个功能,确认代码没问题再开。
- 电源问题:Wi-Fi发射瞬间电流可达300mA以上。如果电源供电不足,ESP32会反复重启。我遇到过用USB转串口供电,结果Wi-Fi一开就重启,后来换了独立电源才解决。
- 共存模式的内存:同时开Station和AP模式,内存占用会增加不少。如果你的应用对内存敏感,建议只用一种模式。
我的习惯:调试Wi-Fi时,先用手机热点测试。手机热点环境可控,方便抓日志。等代码稳定了,再换到真实的路由器环境。
4.6 小结
Wi-Fi连接是ESP32物联网应用的基石。Station模式让设备上网,AP模式让设备被访问,事件处理让程序知道发生了什么,重连机制让设备更可靠。
说实话,这些基础内容看起来简单,但真正做好并不容易。我见过太多项目因为Wi-Fi不稳定而翻车。所以,花时间把这一块搞扎实,后面做MQTT、做数据汇聚才会顺风顺水。
下一章,咱们开始讲MQTT协议,那才是真正的数据通信核心。