3、ESP32 Wi-Fi Station模式:Wi-Fi初始化、连接路由器、事件处理回调、重连机制

好,咱们进入第三章。这一章要啃的,是ESP32作为IoT设备最基础也最关键的一步——连上Wi-Fi。

你想想看,MQTT再牛,底层也得靠TCP/IP。TCP/IP再牛,也得先有个Wi-Fi连接。所以,Wi-Fi Station模式就是一切通信的基石。我见过不少新手,MQTT代码写得飞起,结果卡在Wi-Fi连不上,折腾一整天。嗯,咱们今天就把这个坑填平。

3.1 Wi-Fi Station模式概述

ESP32可以工作在几种Wi-Fi模式下:

  • Station模式(STA):作为客户端,连接到你家里的路由器。这是最常用的模式。
  • AP模式:ESP32自己当热点,让手机连它。常用于配网或临时控制。
  • AP+STA模式:一边连路由器,一边开热点。这种场景相对少一些。

咱们这门课,主要用Station模式。说白了,就是让ESP32像个手机一样,去连你家的Wi-Fi。

核心要点:ESP32的Wi-Fi驱动是基于FreeRTOS的事件驱动架构。你不需要轮询检查连接状态,而是注册回调函数,让系统在连接成功、断开、获取IP等事件发生时通知你。

3.2 Wi-Fi初始化流程

初始化Wi-Fi,我习惯分四步走。每一步都有它的道理,咱们一步步拆解。

  1. 初始化网络接口:调用 esp_netif_init()。这是ESP-IDF的网络抽象层,Wi-Fi和以太网都走它。
  2. 创建事件循环:调用 esp_event_loop_create_default()。这是事件处理的核心,后面所有的回调都挂在这个循环上。
  3. 创建Wi-Fi Station:调用 esp_netif_create_default_wifi_sta()。这会创建一个默认的Wi-Fi Station网络接口。
  4. 初始化Wi-Fi驱动:调用 esp_wifi_init(&cfg),传入配置结构体。这一步会启动Wi-Fi底层驱动。

代码看起来是这样的:

void wifi_init_sta(void)
{
    // 1. 初始化网络接口
    esp_netif_init();

    // 2. 创建默认事件循环
    esp_event_loop_create_default();

    // 3. 创建Wi-Fi Station
    esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
    assert(sta_netif);

    // 4. 初始化Wi-Fi驱动
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    esp_wifi_init(&cfg);
}

我的习惯:我会在 esp_netif_create_default_wifi_sta() 之后,立即检查返回值。虽然它很少失败,但嵌入式开发嘛,多一份检查就少一份隐患。

3.3 连接路由器

初始化完成后,就该配置SSID和密码,然后发起连接了。这一步看似简单,但配置结构体里的细节值得注意。

// 配置Wi-Fi
wifi_config_t wifi_config = {
    .sta = {
        .ssid = "YOUR_SSID",
        .password = "YOUR_PASSWORD",
        .threshold.authmode = WIFI_AUTH_WPA2_PSK, // 最低安全等级
    },
};

// 设置为Station模式
esp_wifi_set_mode(WIFI_MODE_STA);
// 配置参数
esp_wifi_set_config(WIFI_IF_STA, &wifi_config);
// 启动Wi-Fi
esp_wifi_start();
// 发起连接
esp_wifi_connect();

这里有个细节:threshold.authmode 字段。它指定了ESP32能接受的最低安全等级。如果你家路由器是WPA2,但你设成WPA3,那可能连不上。我建议设成 WIFI_AUTH_WPA2_PSK,兼容性最好。

我曾经踩过的坑:SSID和密码不要硬编码在代码里,尤其是密码。我见过有人把代码传到GitHub上,结果家里的Wi-Fi密码全网公开。建议用NVS(非易失性存储)或配网功能来管理。

3.4 事件处理回调

这是Wi-Fi驱动的灵魂。ESP32的Wi-Fi事件分为两类:

  • Wi-Fi事件:以 WIFI_EVENT_ 开头,比如连接成功、断开连接。
  • IP事件:以 IP_EVENT_ 开头,比如获取到IP地址、IP冲突。

我们需要注册一个事件处理函数,来处理这些事件。我个人习惯把Wi-Fi和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:
                // Wi-Fi启动后,发起连接
                esp_wifi_connect();
                break;
            case WIFI_EVENT_STA_DISCONNECTED:
                // 断开连接,准备重连
                ESP_LOGI(TAG, "Wi-Fi disconnected, trying to reconnect...");
                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(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
            // 连接成功,可以开始MQTT了
            xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
        }
    }
}

你可能会问:为什么要用 xEventGroupSetBits?这是FreeRTOS的事件组,用来同步任务。当Wi-Fi连接成功并获取到IP后,主任务才能继续执行MQTT初始化。这是一种常见的多任务同步方式。

3.5 重连机制

Wi-Fi连接不是一劳永逸的。路由器重启、信号干扰、设备移动,都可能导致断开。没有重连机制的设备,就是个砖头。

ESP-IDF默认的重连行为是:断开后自动重连一次。但一次不够,我们需要更健壮的策略。

我常用的重连策略是这样的:

重连次数 等待时间 说明
第1次 1秒 快速重连,应对瞬时断开
第2次 2秒 稍微等待
第3次 4秒 指数退避
第4次 8秒 继续退避
第5次及以上 16秒 最大间隔,避免频繁重连

实现起来也不复杂:

#define MAX_RETRY_ATTEMPTS 10

static int s_retry_num = 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 (s_retry_num < MAX_RETRY_ATTEMPTS) {
            // 指数退避,最大16秒
            int delay = (1 << s_retry_num);
            if (delay > 16) delay = 16;
            
            ESP_LOGI(TAG, "Retry %d in %d seconds", s_retry_num + 1, delay);
            vTaskDelay(pdMS_TO_TICKS(delay * 1000));
            
            esp_wifi_connect();
            s_retry_num++;
        } else {
            ESP_LOGE(TAG, "Max retry reached, giving up");
            // 这里可以进入深度睡眠或重启
        }
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        // 连接成功,重置重连计数
        s_retry_num = 0;
    }
}

我的经验:重连次数不要设得太大。10次是个合理的上限。如果10次都连不上,说明环境有问题,不如让设备休眠一段时间再试,或者通过其他方式(比如蓝牙)通知用户。

3.6 完整流程与状态机

把上面所有步骤串起来,Wi-Fi Station的完整生命周期就是一个状态机。我画了一张图,帮你理清思路。

ESP32 Wi-Fi Station 状态机 ESP32 Wi-Fi Station 状态机 INIT(初始化) START(启动) CONNECTING(连接中) GOT_IP(已连接) DISCONNECTED 关键事件说明: • WIFI_EVENT_STA_START → 自动调用 esp_wifi_connect() • IP_EVENT_STA_GOT_IP → 重置重连计数,通知MQTT任务 • WIFI_EVENT_STA_DISCONNECTED → 指数退避重连,最多10次

这张图展示了Wi-Fi连接的完整生命周期。从初始化开始,经过启动、连接、获取IP,最终进入稳定连接状态。一旦断开,自动进入重连循环。

3.7 避坑指南

最后,分享几个我实际项目中遇到的坑,希望能帮你少走弯路。

我曾经遇到的坑:

  • 事件处理函数中调用阻塞函数:事件处理函数是在系统任务中运行的,不能调用 vTaskDelay() 或 printf() 等阻塞函数。否则会导致系统崩溃。正确的做法是发消息给其他任务处理。
  • 忘记设置 threshold.authmode:如果不设置,默认值是 WIFI_AUTH_OPEN,意味着ESP32会尝试连接任何开放网络。这既不安全,也可能导致连接失败。
  • 重连时没有重置计数:如果连接成功后不重置 s_retry_num,下次断开时重连次数会累积,很快达到上限,导致设备彻底放弃连接。

嗯,这一章的内容就到这里。Wi-Fi连接是IoT设备的基础,看似简单,但细节决定成败。把这些基础打牢,后面的MQTT通信才能稳如泰山。