3. WiFi Station 模式:从零开始连接网络

这一章我们正式进入 ESP32 的 WiFi 编程。Station 模式,说白了就是让 ESP32 像你的手机一样,去连接一个已有的路由器。这是最基础、最常用的模式。我个人做过的物联网项目里,90% 以上都是用的这个模式。

核心要点:初始化 WiFi → 配置 SSID/密码 → 处理连接事件 → 获取 IP 地址。四个步骤,缺一不可。

3.1 初始化 WiFi:让协议栈准备好

写代码之前,得先让 ESP32 的 WiFi 硬件和协议栈「醒过来」。我刚开始学的时候,总忘记初始化,结果回调函数死活不触发。你想想看,硬件都没上电,软件怎么跑?

初始化分两步:

  1. 初始化 netif——网络接口层,负责 IP 协议栈
  2. 初始化 WiFi 驱动——让射频硬件进入工作状态
// 第一步:初始化 netif
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();

// 第二步:初始化 WiFi 驱动
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));

这里有个细节:nvs_flash_init() 是用来读取非易失性存储的。WiFi 的 MAC 地址、校准参数都存在里面。我曾经遇到过一上电就崩溃,查了半天发现是 NVS 分区没烧录。嗯,这个坑你们别踩。

3.2 配置 SSID 和密码:告诉 ESP32 连谁

初始化完了,就该告诉 ESP32 要连哪个路由器了。配置信息放在一个结构体里:

wifi_config_t wifi_config = {
    .sta = {
        .ssid = "MyHomeWiFi",
        .password = "password123",
        .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());
参数 说明 我的建议
ssid 路由器名称,最长 32 字节 注意中文字符串编码问题
password WiFi 密码,WPA2 至少 8 位 不要硬编码在正式产品里
threshold.authmode 最低认证模式 设成 WPA2_PSK 兼容性最好
注意:密码为空时,ESP32 会尝试连接开放网络。但我不建议这么做——我在客户现场见过有人把开放 WiFi 和 ESP32 放一起,结果被隔壁蹭网,设备频繁掉线。

3.3 连接事件处理:监听 WiFi 的「心跳」

WiFi 连接不是瞬间完成的。ESP32 会经历扫描、认证、关联、获取 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 && event_id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
        printf("WiFi 开始连接...\n");
    }
    else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) {
        printf("已连接到 AP,正在获取 IP...\n");
    }
    else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        printf("连接断开,5 秒后重试...\n");
        esp_wifi_connect();
    }
}

为什么要监听 WIFI_EVENT_STA_DISCONNECTED?因为 WiFi 随时可能掉线。我做过一个户外温湿度监测项目,设备挂在树上,信号不稳定。如果不做重连逻辑,设备断网后就成「孤儿」了。

小技巧:重连间隔不要太短。我一般设 3-5 秒,给路由器一点恢复时间。太频繁的重连反而会让 AP 把设备拉黑。

3.4 获取 IP 地址:真正「上网」的标志

连接上 AP 不等于能上网。只有拿到 IP 地址,才算真正接入网络。这个事件来自 IP 协议栈,不是 WiFi 驱动:

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));
    }
}

这里有个容易忽略的点:IP_EVENT_STA_GOT_IP 只触发一次。如果 DHCP 租约到期续约,不会再次触发。所以不要在事件处理函数里做一次性初始化,除非你确定只执行一次。

获取到的 IP 信息包含三个值:

  • ip —— 本机 IP 地址
  • netmask —— 子网掩码,通常是 255.255.255.0
  • gw —— 网关地址,也就是路由器的 IP

3.5 完整流程:把碎片拼起来

下面这张图是我自己画的工作流程,帮你把上面的步骤串起来:

ESP32 Station 模式连接流程 初始化 NVS & netif 初始化 WiFi 驱动 配置 SSID / 密码 启动 WiFi 连接 获取 IP 地址 连接成功,开始通信 ⚠ 常见问题 • NVS 未初始化 → 崩溃 • 事件循环未创建 → 收不到事件 • 密码错误 → 反复断连 💡 调试建议 • 用 log 打印每个事件 • 抓包看 DHCP 交互 🔁 重连策略 • 断线后等待 3-5 秒 • 最多重试 5 次后休眠

3.6 完整代码示例

把上面所有代码拼在一起,就是一个可运行的 Station 模式程序:

#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"

#define WIFI_SSID "MyHomeWiFi"
#define WIFI_PASS "password123"

static void wifi_event_handler(void* arg, esp_event_base_t base,
                                int32_t id, void* data)
{
    if (base == WIFI_EVENT && id == WIFI_EVENT_STA_START) {
        esp_wifi_connect();
    } else if (base == WIFI_EVENT && id == WIFI_EVENT_STA_DISCONNECTED) {
        esp_wifi_connect();
    } else if (base == IP_EVENT && id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) data;
        ESP_LOGI("wifi", "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
    }
}

void wifi_init_sta(void)
{
    ESP_ERROR_CHECK(nvs_flash_init());
    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));

    esp_event_handler_instance_t instance_any_id;
    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_id));
    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 = WIFI_SSID,
            .password = WIFI_PASS,
            .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());
}
我的习惯:正式产品里,SSID 和密码不要硬编码。我会把它们存在 NVS 里,或者通过蓝牙配网写入。这样客户换路由器不用重新烧录固件。

3.7 调试技巧:怎么知道连没连上?

代码写完了,怎么验证?我一般用三个方法:

  • 看串口日志——打开 ESP32 的串口输出,观察事件日志。看到 GOT_IP 就说明成功了。
  • ping 测试——在电脑上 ping ESP32 的 IP 地址,能通就说明网络层正常。
  • 路由器管理页面——登录路由器,查看已连接设备列表。看到 ESP32 的 MAC 地址就对了。

有一次我在客户现场调试,串口日志显示 GOT_IP,但设备就是不上报数据。后来发现是 DNS 解析有问题。所以拿到 IP 只是第一步,应用层通信还得单独验证。

避坑提醒:ESP32 的 WiFi 驱动默认开启了省电模式。如果你发现设备偶尔 ping 不通,很可能是休眠了。可以在初始化后调用 esp_wifi_set_ps(WIFI_PS_NONE) 关闭省电。我做过一个智能灯控项目,就是因为省电模式导致开关指令延迟,用户体验极差。

好了,这一章的内容就这些。Station 模式是 ESP32 WiFi 开发的基石,后面的 TCP、HTTP、MQTT 都建立在它之上。把初始化、事件处理、IP 获取这三个环节吃透,后面的路就好走了。