《WiFi 嵌入式物联网开发全套实战》| 第 14 章 ESP32 STA 模式:连接路由、自动重连、掉线恢复完整源码
专栏:《WiFi 嵌入式物联网开发全套实战》 专栏定位:嵌入式 Linux/ESP32 WiFi 从原理→驱动→配网→协议→稳定性→抓包调试→量产优化全套工业实战 适配:物联网设备、智能家居、工控网关、无线透传设备、4G+WiFi 双模设备 💖 点赞 + 收藏 + 关注,嵌入式 WiFi 量产实战全套教程持续更新!
本章前言
ESP32 STA(站点)模式是物联网设备最基础的能力,设备作为无线终端接入家用 / 工业路由器,实现网络通信。官方简单 demo 只实现单次 WiFi 连接;在真实量产设备中会遇到大量现实问题:路由器重启、WiFi 信号变差、AP 更换信道、DHCP 失效、路由器 ARP 表老化、WiFi 底层链路断连但 LWIP 上层仍认为网络正常。
很多开发者直接复制示例代码,设备测试时正常,7×24 小时长时间运行就出现:WiFi 底层已经断开,程序不重连;反复重连风暴;NVS 保存旧配置干扰测试;WiFi 事件回调处理不当引发崩溃;IP 丢失不会触发重连等问题。
本章基于 ESP‑IDF 5.x,实现工业级 STA 代码:WiFi 初始化、事件驱动处理、NVS 读取保存 WiFi 参数、完整自动重连逻辑、掉线分层检测、IP 丢失处理、重连退避防风暴、状态机管理,提供可直接编译完整源码;同时拆解事件原理、关键参数、量产现象、坑点与调试手段。
前置条件:已经完成第 13 章 ESP‑IDF 环境,menuconfig 中 WiFi 国家码配置为
CN,开启 NVS 分区,使用默认或者 OTA 分区表。
14.1 ESP32‑STA 底层工作机制
14.1.1 WiFi 事件驱动模型
ESP‑IDF WiFi 完全基于事件组回调机制,WiFi 驱动、LWIP 协议栈发生状态变化,会抛出对应的系统事件,用户注册事件处理函数接收事件。 重要 STA 相关核心事件:
| 事件枚举 | 说明 |
|---|---|
| WIFI_EVENT_STA_START | STA 模式启动完成 |
| WIFI_EVENT_STA_CONNECTED | 802.11 链路关联成功(拿到 BSSID,尚未获取 IP) |
| WIFI_EVENT_STA_DISCONNECTED | WiFi 链路断开,携带断开原因码 |
| IP_EVENT_STA_GOT_IP | DHCP 成功获取 IP 地址,真正网络就绪 |
| IP_EVENT_STA_LOST_IP | IP 地址丢失 |
⚠️工程重点:
WIFI_EVENT_STA_CONNECTED≠ 网络可用,仅仅代表无线链路和路由器完成关联;必须收到IP_EVENT_STA_GOT_IP才代表设备可以正常收发网络数据包。 很多新手在这里踩坑,收到 CONNECTED 就直接发起 Socket/MQTT,导致网络报错。
14.1.2 断开原因码
wifi_event_sta_disconnected_t结构体携带reason断开原因码,是排查掉线第一依据。
- 2:鉴权失败(密码错误)
- 8:解除关联,路由器主动踢设备
- 15:4‑way 握手超时(干扰、密码错误)
- 20:由于信号弱,本地驱动主动断连
- 其他参考 esp_wifi_types.h 头文件。
14.1.3 重连退避机制必要性
如果路由器断电,不加延时直接循环重连,会产生重连风暴,CPU、WiFi 射频持续高负荷,功耗飙升,影响其他业务。 量产必须实现指数退避:1s →2s →4s →8s,最大间隔限制,避免无限快速重试。
14.2 完整可编译源码示例
app_main.c,基于 idf5.x,依赖 nvs_flash 组件。
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
/* ===================== 用户配置区 ===================== */
#define EXAMPLE_WIFI_SSID "Your_AP_SSID"
#define EXAMPLE_WIFI_PASSWORD "12345678"
/* WiFi事件组位定义 */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_GOT_IP_BIT BIT1
#define WIFI_FAIL_BIT BIT2
static const char *TAG = "STA_DEMO";
static EventGroupHandle_t g_wifi_event_group;
static uint8_t g_retry_cnt = 0;
static const uint8_t MAX_RETRY_CNT = 10;
/**
* @brief WiFi / IP 事件回调函数
* @note 回调运行在系统事件任务上下文,禁止阻塞、大延时、循环;只做标志置位、消息投递
*/
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_START)
{
ESP_LOGI(TAG, "STA模式已启动,开始连接AP");
esp_wifi_connect();
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED)
{
wifi_event_sta_connected_t *evt = (wifi_event_sta_connected_t *)event_data;
ESP_LOGI(TAG, "802.11链路已关联, bssid:"MACSTR", channel:%d",
MAC2STR(evt->bssid), evt->channel);
xEventGroupSetBits(g_wifi_event_group, WIFI_CONNECTED_BIT);
}
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED)
{
wifi_event_sta_disconnected_t *evt = (wifi_event_sta_disconnected_t *)event_data;
ESP_LOGE(TAG, "WiFi链路断开, reason:%d, ssid:%s", evt->reason, EXAMPLE_WIFI_SSID);
xEventGroupClearBits(g_wifi_event_group, WIFI_CONNECTED_BIT | WIFI_GOT_IP_BIT);
if(g_retry_cnt < MAX_RETRY_CNT)
{
g_retry_cnt ++;
/* 指数退避延时:1 2 4 8 ... 最大8秒 */
uint32_t delay_ms = (1 << (g_retry_cnt -1)) * 1000;
if(delay_ms > 8000) delay_ms = 8000;
ESP_LOGW(TAG, "准备重连,重试次数:%d, delay:%d ms", g_retry_cnt, delay_ms);
vTaskDelay(pdMS_TO_TICKS(delay_ms));
esp_wifi_connect();
}
else
{
ESP_LOGE(TAG, "达到最大重试次数,停止重连");
xEventGroupSetBits(g_wifi_event_group, WIFI_FAIL_BIT);
}
}
else 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;
ESP_LOGI(TAG, "获取IP成功:" IPSTR, IP2STR(&event->ip_info.ip));
g_retry_cnt = 0; // 连接成功,重置重试计数器
xEventGroupSetBits(g_wifi_event_group, WIFI_GOT_IP_BIT);
}
else if(event_base == IP_EVENT && event_id == IP_EVENT_STA_LOST_IP)
{
ESP_LOGW(TAG, "IP地址丢失!");
xEventGroupClearBits(g_wifi_event_group, WIFI_GOT_IP_BIT);
}
}
/**
* @brief STA模式初始化,启动WiFi
*/
void wifi_sta_init(void)
{
g_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
/* 注册WiFi、IP事件回调 */
esp_event_handler_instance_t instance_any_wifi;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&wifi_event_handler,
NULL,
&instance_any_wifi));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&wifi_event_handler,
NULL,
&instance_got_ip));
wifi_config_t wifi_config = {
.sta = {
.ssid = EXAMPLE_WIFI_SSID,
.password = EXAMPLE_WIFI_PASSWORD,
/* 设置阈值:信号低于该值驱动主动断开,单位dbm,-80为参考,根据产品调整 */
.threshold.rssi = -80,
/* 扫描认证方式 */
.pmf_cfg.capable = true,
.pmf_cfg.required = false,
},
};
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_sta_init 完成,等待连接...");
/* 等待事件:连接+获取IP 或者失败 */
EventBits_t bits = xEventGroupWaitBits(g_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_GOT_IP_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT && bits & WIFI_GOT_IP_BIT)
{
ESP_LOGI(TAG, "======== WiFi STA 联网就绪 ========");
}
else if (bits & WIFI_FAIL_BIT)
{
ESP_LOGE(TAG, "WiFi连接最终失败,请检查SSID/密码/路由器");
}
}
void app_main(void)
{
/* 初始化NVS存储,WiFi内部需要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);
ESP_LOGI(TAG, "ESP32 STA Demo 启动");
wifi_sta_init();
/* app_main任务完成,业务逻辑放到其他任务 */
return;
}
14.3 代码关键点解析
14.3.1 事件回调约束
wifi_event_handler运行在系统 event loop 任务,栈空间有限。 ❌禁止:vTaskDelay()、while 死循环、阻塞队列、大量 printf、复杂业务逻辑。 ✅推荐:只做事件标记、消息发送到自己业务任务,业务处理交给 app 自定义任务。
坑:在回调中写延时,直接造成 WiFi 事件卡死,WiFi 驱动异常。
14.3.2 RSSI 信号阈值
.threshold.rssi = -80
当路由器信号持续低于‑80dBm,WiFi 底层驱动主动断开连接,尝试重连更好 AP。
- 室内智能家居:‑75 ~‑80;
- 电池低功耗设备,可以放宽‑85;
- 不建议设置太高‑60,会出现频繁断开重连。
14.3.3 重连退避逻辑
g_retry_cnt记录重试次数,采用指数退避,最大延时 8 秒,防止重连风暴;达到MAX_RETRY_CNT停止重连。
量产产品:停止重连之后,可以做逻辑:周期性重新开启重试,例如每 30s 重新尝试一轮。
14.3.4 IP_EVENT_STA_LOST_IP
路由器 DHCP 租期到期,设备会收到 LOST_IP 事件,清除WIFI_GOT_IP_BIT,上层业务需要感知 IP 丢失,关闭旧 Socket 连接。
14.3.5 NVS 说明
ESP‑IDF WiFi 默认会把 WiFi 信息保存 NVS,重启后自动连接。测试阶段如果修改 SSID 密码,建议烧录前擦除 flash:idf.py erase_flash,避免 NVS 旧配置干扰测试现象。
14.4 STA 模式分层掉线检测逻辑(量产重点)
WiFi 故障分为两层,需要同时检测:
- 802.11 无线链路层:由系统 WiFi 事件回调检测,收到
WIFI_EVENT_STA_DISCONNECTED立刻触发重连。 - 网络应用层假死(无线链路正常,但网络不通) 现象:WiFi 显示已连接,IP 还在,但是路由器断外网、AP 隔离、网关死机,WiFi 底层没有抛出断开事件,上层 Socket 全部卡死。
解决方案:业务任务增加应用层心跳检测(ping 网关 / MQTT 心跳),如果长时间无响应,主动调用
esp_wifi_disconnect()触发完整重连流程。
示例主动断开 API:
esp_err_t esp_wifi_disconnect(void);
esp_err_t esp_wifi_stop(void);
esp_err_t esp_wifi_start(void);
当判定网络假死,调用esp_wifi_disconnect(),会收到 DISCONNECT 事件,进入我们写好的重连逻辑。
14.5 menuconfig 关键配置项
Component config → WiFi → WiFi country code设置CN;射频合规,信道、功率正确。Component config → WiFi → WiFi task stack size,默认一般够用,如果 WiFi 相关崩溃,可以适度调大。Component config → LWIP:增大 socket 缓冲区,长连接设备调大TCP send/receive buffer。NVS分区保证存在,WiFi 内部依赖 NVS 存储射频校准数据。
14.6 常见故障现象与定位
现象 1:WiFi 打印 CONNECTED,但是 Socket connect 失败
根因:收到 STA_CONNECTED 只是无线链路,还没 GOT_IP;业务代码等待WIFI_GOT_IP_BIT位再发起网络操作。
现象 2:路由器断电,设备不会自动重连
- 查看断开 reason 码;
- 确认事件回调是否收到
WIFI_EVENT_STA_DISCONNECTED; - 检查重连计数、退避延时逻辑;
- 硬件供电不稳,WiFi 驱动直接异常不抛出事件。
现象 3:WiFi 反复疯狂重连,CPU 占用高
没有做退避延时,断开事件立刻调用esp_wifi_connect(),形成风暴;代码必须加入指数延时。
现象 4:密码错误,不断重试连接
reason=2,鉴权失败;量产项目识别 reason=2,不要无限重试,上报业务层密码错误状态。
现象 5:长时间运行 WiFi 假死,无断开事件
无线链路完好,但是网络不通;必须上层业务心跳检测,主动触发 WiFi 重连。
现象 6:修改 SSID 密码,烧录后还是连旧热点
NVS 分区保存旧 WiFi 配置,执行idf.py erase_flash擦除。
14.7 量产工程扩展建议
- WiFi 参数外部存储:不要写死在代码中,NVS 存储 SSID/PASSWORD,配网后动态写入 wifi_config_t。
- reason 断开原因码上报:产品日志 / 云平台上报 reason,便于现场远程分析掉线原因。
- RSSI 采集:业务周期调用
esp_wifi_sta_get_rssi()读取信号强度,上报云端。 - 多 AP 择优:扫描周边 AP,根据 RSSI 选择信号最优路由器连接。
- 看门狗配合:WiFi 长时间完全无状态变化,任务喂狗异常,软件复位兜底。
- 重连策略优化:达到最大重试后,不要永久放弃;定时周期重新开启一轮重试。
获取当前 rssi 接口示例:
int8_t rssi = 0;
esp_wifi_sta_get_rssi(&rssi);
ESP_LOGI(TAG,"current rssi: %d", rssi);
14.8 本章小结
ESP32‑STA 开发不能简单依赖 demo 单次连接。本章掌握核心要点:WiFi 事件驱动模型、区分 CONNECTED 与 GOT_IP、断开 reason 码解读、指数退避防重连风暴、无线链路层 + 应用层双层掉线检测。 很多量产 WiFi 不稳定问题,不是硬件射频,而是事件处理错误、缺少退避、缺少上层假死检测逻辑。本章代码可以直接作为产品 STA 基础框架二次开发。
💖 点赞 + 收藏 + 关注,嵌入式 WiFi 量产实战全套教程持续更新!
更多推荐


所有评论(0)