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协议,那才是真正的数据通信核心。