【ESP32笔记】WIFI连接—STA模式

本节目标:让 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、或信号太弱。
相关推荐
HRTOS2 小时前
HRTOS 4.0 实测:时间片模式与调度位模式的任务切换性能对比
经验分享·单片机·嵌入式硬件·51单片机
学电子她就能回来吗2 小时前
LeetCode Hot 100|滑动窗口专题零基础学习笔记
笔记·学习·leetcode
我先去打把游戏先2 小时前
Git-Rebase使用指南-从原理到实战
服务器·git·嵌入式硬件·物联网·ubuntu
一个风轻云淡3 小时前
扒开系统调用的三层皮:从 getpid 到手写 int 0x80
stm32·单片机·嵌入式硬件
Xudde.3 小时前
DC-1 靶机渗透测试(Vulnhub Writeup)
笔记·学习·安全·web安全
海绵宝宝转agent4 小时前
2026-10-14算法+面经总结
java·笔记·算法·缓存
HRTOS4 小时前
HRTOS 4.0 实测:8051 中断响应延迟与中断嵌套性能测试
单片机·嵌入式硬件·51单片机
skywalk81634 小时前
Monty / 千脑理论(Thousand Brains Project)学习笔记
笔记·学习·monty·千脑理论
NendoZen4 小时前
万用板焊接LM2940实现5V电源模块
单片机·嵌入式硬件