2、Station模式基础:Wi-Fi初始化流程、事件循环机制、连接回调函数

好,咱们正式开始动手写代码了。这一章我带你走一遍 ESP32 作为 Wi-Fi Station(也就是客户端)的完整初始化流程。说白了,就是让 ESP32 像你的手机一样,去连一个路由器。

很多新手上来就抄例程,结果连不上也不知道问题出在哪。我个人习惯是先把底层机制理清楚,再动手。这样出了问题,你心里有数。

2.1 Wi-Fi 初始化流程:三步走

ESP32 的 Wi-Fi 初始化,其实就三个核心步骤。我把它总结成一张图,你先有个整体印象。

ESP32 Wi-Fi Station 初始化流程 步骤1 初始化网络接口 esp_netif_init() 步骤2 创建事件循环 esp_event_loop_create_default() 步骤3 初始化 Wi-Fi 驱动 esp_wifi_init(&cfg) 💡 核心要点 顺序不能乱:先有网卡 → 再有事件通道 → 最后启动 Wi-Fi 驱动

嗯,这张图你看明白了吗?下面我拆开细说。

2.1.1 初始化网络接口

第一步,调用 esp_netif_init()。这个函数干什么?它创建了 TCP/IP 协议栈的底层环境。没有它,你的 Wi-Fi 连上了也收发不了数据。

我踩过的坑: 刚做项目时,我忘了调这个函数,结果 Wi-Fi 连接状态显示成功,但 ping 不通任何 IP。查了两天才发现是 netif 没初始化。从那以后,我写代码第一件事就是检查这三步有没有漏。

2.1.2 创建事件循环

第二步,esp_event_loop_create_default()。这个函数创建了一个默认的事件循环。事件循环是什么?说白了,就是 ESP32 内部的一个消息总线。Wi-Fi 状态变了、连接断了、拿到了 IP,这些消息都会通过这个总线发出来。你的代码只需要注册好“听众”(回调函数),就能及时响应。

2.1.3 初始化 Wi-Fi 驱动

第三步,esp_wifi_init(&cfg)。这里需要传入一个 wifi_init_config_t 结构体。我一般直接用 WIFI_INIT_CONFIG_DEFAULT() 这个宏,省事。

// 标准初始化代码片段
esp_netif_init();                              // 1. 初始化网络接口
esp_event_loop_create_default();               // 2. 创建事件循环

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

2.2 事件循环机制:别再用轮询了

很多单片机开发者习惯用轮询——在主循环里不断查状态。但在 ESP32 上,我强烈建议你用事件驱动。为什么?因为 Wi-Fi 连接是个异步过程,轮询会浪费 CPU,而且容易漏掉短暂的状态变化。

ESP32 的事件循环机制,你可以理解成一个“订阅-发布”系统。你的代码订阅感兴趣的事件,当事件发生时,系统自动调用你的回调函数。

2.2.1 事件类型

Wi-Fi 相关的事件主要分两类:

事件基 典型事件 触发时机
WIFI_EVENT WIFI_EVENT_STA_START Station 模式启动完成
WIFI_EVENT WIFI_EVENT_STA_CONNECTED 与 AP 建立 Wi-Fi 连接(链路层)
WIFI_EVENT WIFI_EVENT_STA_DISCONNECTED 与 AP 断开连接
IP_EVENT IP_EVENT_STA_GOT_IP DHCP 成功获取到 IP 地址
IP_EVENT IP_EVENT_STA_LOST_IP IP 地址丢失

我的经验: 很多初学者只关注 WIFI_EVENT_STA_CONNECTED,以为连上就完事了。其实真正能上网的标志是 IP_EVENT_STA_GOT_IP。你想想看,连上路由器但没拿到 IP,跟没连有什么区别?

2.2.2 注册事件回调

注册回调用 esp_event_handler_register() 函数。你需要指定事件基、具体事件 ID,以及你的回调函数指针。

// 注册 Wi-Fi 事件和 IP 事件的回调
esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL);
esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &ip_event_handler, NULL);

注意这里我用的是 ESP_EVENT_ANY_ID,意思是这个回调处理所有 Wi-Fi 事件。你也可以只注册特定事件,更精细控制。

2.3 连接回调函数:实战中的写法

好了,理论说完了,咱们直接看代码。下面是我在实际项目中用的回调函数模板。

// Wi-Fi 事件回调
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:
                printf("Wi-Fi Station 已启动,开始连接...\n");
                esp_wifi_connect();  // 触发连接
                break;

            case WIFI_EVENT_STA_CONNECTED:
                printf("已连接到 AP,等待 IP...\n");
                break;

            case WIFI_EVENT_STA_DISCONNECTED:
                printf("连接断开,5秒后重连...\n");
                // 我曾经在这里忘记加延时,导致疯狂重连把 AP 搞崩了
                vTaskDelay(pdMS_TO_TICKS(5000));
                esp_wifi_connect();
                break;
        }
    }
}

// IP 事件回调
static void ip_event_handler(void* arg, esp_event_base_t event_base,
                              int32_t event_id, void* event_data)
{
    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;
        printf("获取到 IP: " IPSTR "\n", IP2STR(&event->ip_info.ip));
        // 到这里,才真正可以开始网络通信
    }
}

⚠️ 重要提醒: 回调函数是在事件循环的任务上下文中执行的。不要在回调里做耗时操作,比如延时、大块内存分配、阻塞式网络请求。否则会阻塞整个事件循环,导致其他事件无法处理。

2.4 完整的 Station 初始化与连接代码

把上面所有内容串起来,就是一个完整的 Station 初始化流程。我习惯把它封装成一个函数。

void wifi_sta_init(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();

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

    // 5. 注册事件回调
    esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
                               &wifi_event_handler, NULL);
    esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
                               &ip_event_handler, NULL);

    // 6. 配置 Station 模式
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = "YOUR_SSID",
            .password = "YOUR_PASSWORD",
            .threshold.authmode = WIFI_AUTH_WPA2_PSK,
        },
    };
    esp_wifi_set_mode(WIFI_MODE_STA);
    esp_wifi_set_config(WIFI_IF_STA, &wifi_config);

    // 7. 启动 Wi-Fi
    esp_wifi_start();

    printf("Wi-Fi Station 初始化完成,等待连接...\n");
}

注意: 第3步 esp_netif_create_default_wifi_sta() 很多人会漏掉。它创建了 Wi-Fi Station 的网络接口实例,并自动关联到 TCP/IP 协议栈。没有这一步,你的 Wi-Fi 连上了也拿不到 IP。

2.5 避坑指南:我踩过的三个坑

做 Wi-Fi 项目这么多年,有些坑我印象特别深。分享给你,希望你不用再走一遍。

  1. 回调里做阻塞操作

    我曾经在 WIFI_EVENT_STA_GOT_IP 回调里直接发起 HTTP 请求,结果事件循环被卡死,后续事件全部丢失。后来改成用队列把消息发给主任务处理,问题解决。

  2. 重连不加延时

    有一次设备在信号弱的环境下,断开后立即重连,反复失败。我在 DISCONNECTED 事件里加了 5 秒延时,重连成功率大幅提升。你想想看,路由器也需要时间释放资源。

  3. 忽略事件循环的优先级

    默认事件循环运行在系统任务中,优先级较高。如果你的应用任务优先级设置不当,可能会导致事件处理延迟。我一般把应用任务优先级设得比事件循环低一级。

2.6 本章小结

这一章我们走通了 Station 模式的完整初始化流程。记住三个核心点:

  • 初始化顺序:netif → 事件循环 → Wi-Fi 驱动
  • 事件驱动代替轮询,注册回调处理连接状态
  • 真正可用的标志是 IP_EVENT_STA_GOT_IP,不是 CONNECTED