本节目标:让 ESP32 以 STA 模式连上家里的路由器,并通过事件回调分清"连上 AP"和"拿到 IP"两个阶段,顺便掌握断线重连的写法
一、本节要解决的问题
前面十几节讲的全是 ESP32 的 MCU 能力 :GPIO、PWM、ADC、NVS、文件系统......这些能力让 ESP32 可以替换很多普通单片机。但 ESP32 真正的主业是 IOT(物联网):一块 ESP32 上集成了 2.4GHz WiFi + BT(LE) 4.2,而 ESP-IDF 对 WiFi 驱动的封装已经很完善,我们不需要研究协议栈底层,把精力放在应用逻辑上就行。
要让 ESP32 上网,第一个要回答的问题是:它怎么知道我家的 WiFi 叫什么、密码是多少?
本节先用最直接的办法------把 SSID 和密码写死在代码里 ,以 STA 模式连上路由器。这个做法在真实产品里不能用(出厂时根本不知道用户家的 WiFi),但它是后面一切网络应用(MQTT、HTTP、OTA)的地基,也是下一节 SmartConfig 配网的前置知识。
二、硬件基础
本节没有新增外围电路,用的是 ESP32 板载的 PCB 天线,需要准备的是:
- 一台 2.4GHz 家用路由器(ESP32 只支持 2.4GHz,连不上 5GHz 热点);
- 一台手机或电脑,用来确认路由器 SSID 和密码。
ESP32 的 WiFi 有三种工作模式:
| 模式 | 说明 |
|---|---|
| STA | 作为客户端去连接已存在的 WiFi 网络,像一台普通的上网设备 |
| AP | 自己创建热点,变成一个小型路由器,接受其它设备连接,多用于配网 |
| STA+AP | 同时工作在两种模式:既连热点又开热点,可以做桥接/中继,让别的设备经由 ESP32 上网 |
本节只讲最常用的 STA 模式。
三、知识点
3.1 三个"主角":esp_netif、esp_event、esp_wifi
ESP-IDF 把 WiFi 的使用拆成了三层,初始化代码里那几行"看起来差不多"的调用,其实各管一摊:
- esp_wifi:WiFi 驱动本体,负责扫描、认证、连接、加解密;
- esp_netif :网络接口(网卡)抽象层。
esp_netif_create_default_wifi_sta()会创建一个 STA 网卡对象,并顺手挂上 DHCP 客户端------没有它,就算连上了路由器也拿不到 IP; - esp_event :事件循环。
esp_event_loop_create_default()建了一个默认事件循环(内部是一个 FreeRTOS 任务 + 事件队列),WiFi 驱动把"启动完成""连上了""断开了"这些状态变化打包成事件投递进去,我们注册的回调就在这个任务里被调用。
注意 :回调函数运行在事件循环任务的上下文里,不要在回调里做阻塞操作(长延时、死循环、等待信号量),否则整个系统的事件分发都会被拖住。
3.2 连接是一场"事件接力"
调用顺序和事件顺序是分开的两条线,初学者最容易在这里迷糊:
调用线:esp_wifi_init() → esp_wifi_set_mode() → esp_wifi_set_config() → esp_wifi_start()
事件线: WIFI_EVENT_STA_START → esp_wifi_connect()
→ WIFI_EVENT_STA_CONNECTED (已连上 AP,但还没有 IP)
→ IP_EVENT_STA_GOT_IP (拿到路由器分配的地址,才算真正上网)
掉线时: WIFI_EVENT_STA_DISCONNECTED → 再 esp_wifi_connect() 重新开始
两个关键区别要记牢(教程原话的意思):
WIFI_EVENT_STA_CONNECTED只表示 已经挂了 AP 热点,链路层通了,但还没有网络层地址;- 只有
IP_EVENT_STA_GOT_IP到手,才说明路由器给你分配了 IP,此时 DHCP 完成,才能真正访问网络。
3.3 为什么必须先初始化 NVS
WiFi 驱动会把"最近一次成功连接的热点信息"存进 NVS(命名空间 nvs.net80211),所以 esp_wifi_init() 之前必须已经 nvs_flash_init(),否则会因 NVS 未初始化而报错。教程里那句"WiFi 底层驱动使用了 NVS 来保存一份默认可连接的 SSID 和密码"说的就是这件事。
3.4 断线重连思路
最简单的做法(本例程用的):在 WIFI_EVENT_STA_DISCONNECTED 分支里无条件再调一次 esp_wifi_connect(),形成"断了就重连"的闭环。
这个写法能跑,但产品里要改进:
- 密码填错时也会疯狂重试,串口刷屏、耗电、发热;
- 更稳的做法是按断开原因 (
event_data里的wifi_event_sta_disconnected_t.reason)分类处理:密码错(认证失败)就别重试,提示用户重新配网;路由器暂时不在才重试,并且加延时/退避 和最大重试次数。
四、代码分析
4.1 工程结构
wifi/
├── CMakeLists.txt # 标准模板,project(main)
├── sdkconfig # CONFIG_IDF_TARGET="esp32"
├── README.md
└── main/
├── CMakeLists.txt # SRCS "simple_wifi_sta.c" "main.c"
├── main.c # app_main():NVS 初始化 + 启动 STA
├── simple_wifi_sta.c # 本节全部实现:初始化函数 + 事件回调
└── simple_wifi_sta.h # 对外只暴露 wifi_sta_init()
4.2 主函数:两件事
c
void app_main(void)
{
//NVS初始化(WIFI底层驱动有用到NVS,所以这里要初始化)
nvs_flash_init();
//wifi STA工作模式初始化
wifi_sta_init();
while(1)
{
vTaskDelay(pdMS_TO_TICKS(500));
}
}
app_main() 本身不做 WiFi 逻辑,只负责"NVS 先初始化、再去启动 WiFi"。最后的 while(1) 是为了让 app_main 任务不退出------WiFi 的连接过程是异步的,连接结果稍后由事件回调通知。
4.3 网络层初始化:三行代码各自管什么
c
esp_err_t wifi_sta_init(void)
{
ESP_ERROR_CHECK(esp_netif_init()); //用于初始化tcpip协议栈
ESP_ERROR_CHECK(esp_event_loop_create_default()); //创建一个默认系统事件调度循环,之后可以注册回调函数来处理系统的一些事件
esp_netif_create_default_wifi_sta(); //使用默认配置创建STA对象
esp_netif_init():初始化 TCP/IP 协议栈(lwIP)和网络接口层;esp_event_loop_create_default():建默认事件循环,后面注册的回调都有地方"排队执行";esp_netif_create_default_wifi_sta():创建默认 STA 网卡对象(含 DHCP 客户端)。
esp_netif_create_default_wifi_sta()的返回值是一个esp_netif_t *网卡句柄,本例程没有接收它(用默认 STA 时通常也不需要)。另外simple_wifi_sta.c并没有显式#include "esp_netif.h",这里靠头文件间接包含;换 IDF 版本编译如果报esp_netif_init未声明,把该头文件补上即可。
4.4 初始化 WiFi 驱动并注册事件
c
//初始化WIFI
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
//注册事件
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT,ESP_EVENT_ANY_ID,&event_handler,NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT,IP_EVENT_STA_GOT_IP,&event_handler,NULL));
WIFI_INIT_CONFIG_DEFAULT()给出官方默认的一整套 WiFi 初始化参数(收发缓冲区、任务优先级、软 AP 支持等),入门阶段不用动它;- 两个注册的意义不同:
WIFI_EVENT+ESP_EVENT_ANY_ID表示所有 WiFi 事件 都进回调,由回调内部再按event_id分流;IP_EVENT只注册了IP_EVENT_STA_GOT_IP这一个,其它 IP 事件(如丢失 IP)不会进回调。
4.5 连接参数与启动顺序
c
//WIFI配置
wifi_config_t wifi_config =
{
.sta =
{
.ssid = DEFAULT_WIFI_SSID, //WIFI的SSID
.password = DEFAULT_WIFI_PASSWORD, //WIFI密码
.threshold.authmode = WIFI_AUTH_WPA2_PSK, //加密方式
.pmf_cfg =
{
.capable = true,
.required = false
},
},
};
//启动WIFI
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) ); //设置工作模式为STA
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) ); //设置wifi配置
ESP_ERROR_CHECK(esp_wifi_start() ); //启动WIFI
这里有几个参数值得解释:
threshold.authmode = WIFI_AUTH_WPA2_PSK:最低要求的加密方式,目前家用路由器基本都是 WPA2-PSK;pmf_cfg.capable = true:设备支持保护管理帧(PMF),required = false表示"不强制对方也支持"。教程特别说明照官方例程这么填就行,初学不必深究;- 三步顺序不能反:先
set_mode定角色 → 再set_config填参数 → 最后start开跑 。start成功后驱动才会抛出WIFI_EVENT_STA_START。
与教程不一致处 :教程 PDF 里写的 SSID 是
"wifitest",而仓库simple_wifi_sta.c里已经改成"testwifi",并且加了注释"需要把这两个修改成你家WIFI,测试是否连接成功"。以仓库代码为准------无论是哪个值,都要改成自己家 2.4G WiFi 的 SSID 和密码,否则现象就是一直重连失败。
4.6 事件回调:连接状态机的中心
c
static void 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: //WIFI以STA模式启动后触发此事件
esp_wifi_connect(); //启动WIFI连接
break;
case WIFI_EVENT_STA_CONNECTED: //WIFI连上路由器后,触发此事件
ESP_LOGI(TAG, "connected to AP");
break;
case WIFI_EVENT_STA_DISCONNECTED: //WIFI从路由器断开连接后触发此事件
esp_wifi_connect(); //继续重连
ESP_LOGI(TAG,"connect to the AP fail,retry now");
break;
default:
break;
}
}
if(event_base == IP_EVENT) //IP相关事件
{
switch(event_id)
{
case IP_EVENT_STA_GOT_IP: //只有获取到路由器分配的IP,才认为是连上了路由器
ESP_LOGI(TAG,"get ip address");
break;
}
}
}
回调的写法值得学习:先用 event_base 区分事件大类,再用 event_id 分流具体事件,这是 ESP-IDF 里处理事件的通用套路。
WIFI_EVENT_STA_START:驱动启动完成,此时才可以 发起连接,所以在这里调esp_wifi_connect();WIFI_EVENT_STA_DISCONNECTED:断开后立刻重连(见 3.4 的改进建议);IP_EVENT_STA_GOT_IP:拿到 IP,真正可以上网了。真实项目一般在这里置位一个事件组标志位,通知其它任务"网络就绪"(下一节的代码就是这么干的)。
esp_wifi_connect() 是异步非阻塞的:它只是告诉驱动"去连",结果通过上面这些事件回来,所以千万不要在它后面紧跟一句"连接成功"的打印。
五、关键 API 速查
| API | 作用 |
|---|---|
nvs_flash_init() |
初始化 NVS(必须早于 esp_wifi_init) |
esp_netif_init() |
初始化 TCP/IP 协议栈与网络接口层 |
esp_event_loop_create_default() |
创建默认系统事件循环 |
esp_netif_create_default_wifi_sta() |
创建默认 STA 网卡对象(含 DHCP 客户端) |
WIFI_INIT_CONFIG_DEFAULT() |
生成 WiFi 驱动默认初始化配置 |
esp_wifi_init(&cfg) |
初始化 WiFi 驱动 |
esp_event_handler_register(base, id, handler, arg) |
注册事件回调;ESP_EVENT_ANY_ID 表示该类的全部事件 |
esp_wifi_set_mode(WIFI_MODE_STA) |
设置 WiFi 工作模式 |
esp_wifi_set_config(WIFI_IF_STA, &cfg) |
写入连接参数(SSID/密码等) |
esp_wifi_start() |
启动 WiFi,触发 WIFI_EVENT_STA_START |
esp_wifi_connect() |
发起连接(异步,结果靠事件返回) |
esp_wifi_disconnect() |
主动断开当前连接 |
六、编译与现象验证
bash
cd esp32-board/wifi
idf.py build # sdkconfig 已指定 CONFIG_IDF_TARGET="esp32"
idf.py -p COM<端口> flash monitor
烧录前先把 simple_wifi_sta.c 顶部的 DEFAULT_WIFI_SSID、DEFAULT_WIFI_PASSWORD 改成自己家 2.4G WiFi 的信息。正常连接时串口大致是:
I (xxx) wifi: wifi_init_sta finished.
I (xxx) wifi: connected to AP
I (xxx) wifi: get ip address
- 只看到
connected to AP没看到get ip address:链路连上了但 DHCP 没成功,检查路由器 DHCP 是否开启; - 反复打印
connect to the AP fail,retry now:SSID/密码错了,或路由器是 5GHz、或信号太弱。