专栏:《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_STARTSTA 模式启动完成
WIFI_EVENT_STA_CONNECTED802.11 链路关联成功(拿到 BSSID,尚未获取 IP)
WIFI_EVENT_STA_DISCONNECTEDWiFi 链路断开,携带断开原因码
IP_EVENT_STA_GOT_IPDHCP 成功获取 IP 地址,真正网络就绪
IP_EVENT_STA_LOST_IPIP 地址丢失

⚠️工程重点: 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 故障分为两层,需要同时检测:

  1. 802.11 无线链路层:由系统 WiFi 事件回调检测,收到WIFI_EVENT_STA_DISCONNECTED立刻触发重连。
  2. 网络应用层假死(无线链路正常,但网络不通) 现象: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 关键配置项

  1. Component config → WiFi → WiFi country code 设置CN;射频合规,信道、功率正确。
  2. Component config → WiFi → WiFi task stack size,默认一般够用,如果 WiFi 相关崩溃,可以适度调大。
  3. Component config → LWIP:增大 socket 缓冲区,长连接设备调大TCP send/receive buffer。
  4. 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 量产工程扩展建议

  1. WiFi 参数外部存储:不要写死在代码中,NVS 存储 SSID/PASSWORD,配网后动态写入 wifi_config_t。
  2. reason 断开原因码上报:产品日志 / 云平台上报 reason,便于现场远程分析掉线原因。
  3. RSSI 采集:业务周期调用esp_wifi_sta_get_rssi()读取信号强度,上报云端。
  4. 多 AP 择优:扫描周边 AP,根据 RSSI 选择信号最优路由器连接。
  5. 看门狗配合:WiFi 长时间完全无状态变化,任务喂狗异常,软件复位兜底。
  6. 重连策略优化:达到最大重试后,不要永久放弃;定时周期重新开启一轮重试。

获取当前 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 量产实战全套教程持续更新!

 

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐