【Zephyr|ESP32-S3】为ESP32配置WiFi-STA模式并连上WiFi网络

哈喽,我是余火,一个普通的牛马打工人,目前正在学如何使用Zephyr RTOS。

上篇用 UART 串口实现了电脑给板子发命令控制 WS2812 变色,有串口通信基础了。但到目前为止板子一直是离线状态——只能和电脑一对一有线通信。

ESP32-S3 最大的优势是自带 WiFi模块,所以这次直接在同一个工程上加 WiFi 功能,让板子连上路由器拿到 IP 地址。

💡 本篇学习目标
• net_mgmt(NET_REQUEST_WIFI_CONNECT, ...):发起 WiFi 连接请求
• net_mgmt 事件回调:监听连接结果和断开事件
• K_SEM 信号量桥接:将异步 WiFi 事件转换为同步阻塞等待

改了哪些东西

文件

改了什么

`src/main.c`

末尾追加 `#include "wifi.h"` + `wifi_init_and_connect()` 调用

`src/wifi.c`

新增:WiFi STA 连接管理模块(事件回调 + 信号量同步 + 重试)

`src/wifi.h`

新增:WiFi 模块接口声明

`prj.conf`

追加网络 Kconfig + WiFi 凭证 + 栈/堆调整

overlay

追加 `&wifi { status = "okay"; };`

UART 中断、命令解析线程、WS2812 灯效代码全部都可以保留不动。

WiFi STA 模式

WiFi 有两种基础模式:STA(Station,终端)和 AP(Access Point,热点)。手机连家里路由器就是 STA 模式,手机开热点让别的设备连就是 AP 模式。

嵌入式产品绝大多数用 STA 模式——板子作为终端连路由器,然后通过 TCP/UDP/MQTT 等协议通信。本篇就是让 ESP32-S3 以 STA 模式连上路由器。

启用网络子系统

相比 GPIO、UART 这些默认启用的驱动,Zephyr 的网络协议栈是可选组件,需要手动开启一堆 Kconfig。在 prj.conf 中追加:

/* ── 栈/堆调整(WiFi 协议栈需要更多内存)── */CONFIG_MAIN_STACK_SIZE=4096CONFIG_HEAP_MEM_POOL_SIZE=57344
/* ── 网络基础 ── */CONFIG_NETWORKING=yCONFIG_NET_IPV4=yCONFIG_NET_L2_WIFI_MGMT=yCONFIG_NET_L2_ETHERNET=yCONFIG_NET_MGMT=yCONFIG_NET_MGMT_EVENT=yCONFIG_NET_MGMT_EVENT_STACK_SIZE=2048CONFIG_NET_MGMT_EVENT_QUEUE_SIZE=16CONFIG_NET_DHCPV4=yCONFIG_NET_PKT_TX_COUNT=10CONFIG_NET_PKT_RX_COUNT=10CONFIG_NET_BUF_TX_COUNT=20CONFIG_NET_BUF_RX_COUNT=20CONFIG_NET_TX_STACK_SIZE=2048CONFIG_NET_RX_STACK_SIZE=2048
/* ── WiFi ── */CONFIG_WIFI=yCONFIG_WIFI_LOG_LEVEL_INF=y
/* ── WiFi 凭证(替换为你自己的) ── */CONFIG_WIFI_SSID="your_ssid"CONFIG_WIFI_PASSWORD="your_password"

💡 配置项看着多,其实就三组:网络基础(IP协议栈 + 缓冲区 + 收发线程)→ WiFi 驱动 → 凭证。Zephyr 的模块化设计就是不用网络就不编译进去,省 Flash 和 RAM。

overlay 追加一行显式确认 WiFi 外设(ESP32-S3 的 dtsi 默认已启用,但显式写出来更清晰):

&wifi {    status = "okay";};

WiFi 连接模块

WiFi 逻辑拆成了独立的 wifi.c / wifi.h,main 只需调一个函数。这是实际项目的常见做法——WiFi、BLE 等通信模块各管各的,main 只负责初始化和业务逻辑。

wifi.h:

#ifndef WIFI_H#define WIFI_H
/** * @brief 初始化 WiFi STA 并阻塞等待连接完成 (或超时) * * 内部通过 K_SEM 将异步 net_mgmt 事件转换为同步等待。 * 超时时间固定为 30 秒。 * 连接成功后 IP 地址通过 DHCP 自动获取,并通过 LOG 输出。 * * @return 0 成功,负 errno 失败 */int wifi_init_and_connect(void);
#endif /* WIFI_H */

wifi.c 核心代码:

#include <zephyr/kernel.h>#include <zephyr/net/net_if.h>#include <zephyr/net/net_mgmt.h>#include <zephyr/net/net_event.h>#include <zephyr/net/wifi_mgmt.h>#include <zephyr/logging/log.h>#include <string.h>LOG_MODULE_REGISTER(wifi, LOG_LEVEL_INF);#define WIFI_TIMEOUT_MS 30000/* * WiFi 连接完成信号量 (初始值 0,最大值 1) * * 用于将异步的 net_mgmt 事件转换为同步等待: *   wifi_init_and_connect() 中 k_sem_take() 阻塞 → *   回调中连接成功时 k_sem_give() 唤醒。 */static K_SEM_DEFINE(wifi_connected, 0, 1);/* 缓存的 WiFi STA 网络接口句柄 */static struct net_if *sta_iface;/* net_mgmt 事件回调结构体 */static struct net_mgmt_event_callback wifi_cb;/* * wifi_mgmt_handler — WiFi 网络管理事件回调 * * 这套 net_mgmt 事件机制和 UART 中断回调、GPIO 中断回调 * 本质上是同一套设计思想——硬件事件 → 驱动通知 → 用户回调, * 只是从单设备扩展到了网络子系统。 */static void wifi_mgmt_handler(struct net_mgmt_event_callback *cb,                              uint64_t mgmt_event, struct net_if *iface){    ARG_UNUSED(cb);    switch (mgmt_event) {    case NET_EVENT_WIFI_CONNECT_RESULT: {        const struct wifi_status *status =            (const struct wifi_status *)cb->info;        if (status->status == 0) {            /*             * 连接成功:读取并打印 IPv4 地址             *             * 由于 CONFIG_ESP32_WIFI_STA_AUTO_DHCPV4,             * DHCP 通常已完成,但仍检查 addr->s_addr != 0             * 以处理 IP 尚未分配的边界情况。             */            const struct net_in_addr *addr =                net_if_ipv4_get_global_addr(iface, NET_ADDR_PREFERRED);            if (addr && addr->s_addr != 0) {                char buf[NET_IPV4_ADDR_LEN];                net_addr_ntop(AF_INET, addr, buf, sizeof(buf));                LOG_INF("IPv4 address: %s", buf);            } else {                LOG_WRN("IPv4 address: (not assigned yet)");            }            LOG_INF("WiFi connected");            /* 释放信号量,唤醒 wifi_init_and_connect() 中的 k_sem_take() */            k_sem_give(&wifi_connected);        } else {            /*             * 连接失败(密码错误、AP 不在范围内等)             * 仅记录日志,不释放信号量,调用者将在超时后返回 -ETIMEDOUT。             */            LOG_ERR("WiFi connect failed: %d", status->status);        }        break;    }    case NET_EVENT_WIFI_DISCONNECT_RESULT: {        const struct wifi_status *status =            (const struct wifi_status *)cb->info;        LOG_WRN("WiFi disconnected: reason=%d", status->status);        break;    }    default:        break;    }}int wifi_init_and_connect(void){    /*     * 获取 WiFi STA 接口     *     * 优先使用 net_if_get_wifi_sta() 精确获取 WiFi STA 接口。     * 如果返回 NULL,则回退到 net_if_get_default() 获取默认接口。     */    struct net_if *iface = net_if_get_wifi_sta();    if (!iface) {        iface = net_if_get_default();    }    sta_iface = iface;    if (!iface) {        LOG_ERR("No network interface found");        return -ENODEV;    }    /* 注册 net_mgmt 事件回调 */    net_mgmt_init_event_callback(&wifi_cb, wifi_mgmt_handler,                                 NET_EVENT_WIFI_CONNECT_RESULT |                                 NET_EVENT_WIFI_DISCONNECT_RESULT);    net_mgmt_add_event_callback(&wifi_cb);    /*     * 显式启用网络接口 + 等待硬件就绪     *     * net_if_up() 激活网络接口,触发底层驱动初始化 WiFi 硬件。     * k_msleep(1000) 给驱动 1 秒时间完成硬件初始化,     * 实测 500ms 偶尔不够稳定,1 秒后连接成功率显著提高。     */    net_if_up(iface);    k_msleep(1000);    /*     * 构造 WiFi 连接请求参数     *     * - SSID 和密码从 Kconfig (CONFIG_WIFI_SSID / CONFIG_WIFI_PASSWORD) 获取     * - 安全类型: WPA2-PSK     * - 频段: 2.4GHz (ESP32-S3 不支持 5GHz)     * - 信道: 任意 (由 AP 决定)     */    struct wifi_connect_req_params params = {        .ssid        = (const uint8_t *)CONFIG_WIFI_SSID,        .ssid_length = strlen(CONFIG_WIFI_SSID),        .psk         = (const uint8_t *)CONFIG_WIFI_PASSWORD,        .psk_length  = strlen(CONFIG_WIFI_PASSWORD),        .security    = WIFI_SECURITY_TYPE_PSK,        .channel     = WIFI_CHANNEL_ANY,        .band        = WIFI_FREQ_BAND_2_4_GHZ,    };    LOG_INF("Connecting to SSID \"%s\" ...", CONFIG_WIFI_SSID);    /*     * 连接 + 重试机制     *     * ESP32-S3 的 WiFi 驱动偶发首次连接时返回 reason=-1(驱动未完全就绪)。     * 重试最多 3 次,每次间隔 2 秒,覆盖驱动初始化时序不稳定的情况。     */    int ret;    for (int attempt = 1; attempt <= 3; attempt++) {        LOG_INF("WiFi connect attempt %d/3", attempt);        /*         * net_mgmt() 是非阻塞的,仅将请求投递到网络管理栈。         * 实际连接在底层驱动和网络栈的上下文中异步执行。         */        ret = net_mgmt(NET_REQUEST_WIFI_CONNECT, iface,                       &params, sizeof(params));        if (ret) {            LOG_WRN("WiFi connect request failed: %d", ret);            k_msleep(2000);            continue;        }        /*         * 阻塞等待连接完成信号量(最多 30 秒)         *         * 由于 CONFIG_ESP32_WIFI_STA_AUTO_DHCPV4,驱动仅在 DHCP BOUND 后         * 才触发 CONNECT_RESULT 事件,所以唤醒时 IP 地址已分配完成。         */        ret = k_sem_take(&wifi_connected, K_MSEC(WIFI_TIMEOUT_MS));        if (ret == 0) {            return 0;        }        LOG_WRN("WiFi connection timeout, retrying...");        k_msleep(2000);    }    LOG_ERR("WiFi connect failed after 3 attempts");    return -ETIMEDOUT;}

💡 整个 WiFi 连接流程的设计和 UART 中断一脉相承:UART 是"硬件收字节 → ISR 回调 → 信号量通知线程",WiFi 是"驱动连接 → net_mgmt 回调 → 信号量唤醒等待方"。本质都是异步事件 + 信号量同步这套范式。

main 函数改动

在原有 main.c 的末尾追加 WiFi 初始化调用,UART 和 WS2812 的初始化代码保留不动:

    /* 以下是 main.c 末尾追加的内容 */
    /*     * 初始化 WiFi STA 并连接路由器     *     * wifi_init_and_connect() 内部会阻塞等待连接完成(含 DHCP 获取 IP),     * 最长等待 30 秒。连接成功后 IP 地址在 wifi 模块的回调中通过 LOG 输出。     */    int wifi_ret = wifi_init_and_connect();    if (wifi_ret == 0) {        LOG_INF("WiFi ready, IP obtained");    } else {        LOG_ERR("WiFi init failed: %d", wifi_ret);    }
    return 0;}

编译烧录后打开串口助手,可以看到完整的 WiFi 连接过程:SSID → 扫描认证 → DHCP 获取 IP → 打印 IPv4 地址。同时串口命令控灯功能正常工作,发 red、green 等命令依然可以控制 WS2812。

路由器上查询,能看到ESP32-S3确实连接到WiFi上了。

常见问题

Q1:编译报 undefined reference to net_if_get_wifi_sta?

• 原因:prj.conf 缺少网络 Kconfig

• 解决:确认 CONFIG_NETWORKING=y、CONFIG_NET_L2_WIFI_MGMT=y、CONFIG_WIFI=y 都已添加

Q2:LOG 输出 WiFi connect failed: -1?

• 原因:WiFi 驱动未完全就绪就发起了连接

• 解决:代码中已有 net_if_up() 后 k_msleep(1000) 等待 + 3 次重试机制,如果仍然失败检查 CONFIG_WIFI_SSID 和 CONFIG_WIFI_PASSWORD 是否正确

Q3:连接成功但没看到 IP 地址?

• 原因:DHCP 尚未完成分配

•解决:ESP32-S3的 WiFi 驱动在DHCP BOUND后才触发 NET_EVENT_WIFI_CONNECT_RESULT,正常情况下唤醒时 IP 已分配。如果看到 (not assigned yet) 说明 DHCP 超时,检查路由器 DHCP 服务

总结

本篇在第 9 篇工程基础上,通过 WiFi STA 模式 + net_mgmt 事件回调 + 信号量同步等待 实现了 ESP32-S3 连上路由器并获取 IP,UART 串口和 WS2812 功能完全保留。

顺带提一嘴,现在这种硬编码 SSID 和密码的方式其实只是开发调试用的,真正的 IoT 产品一般是设备先自建 AP 热点让用户填 WiFi 密码配网,等有空了我也准备去研究研究 SoftAP 配网的实现。


希望我的笔记能对你有一点点点的帮助!欢迎关注一起学习👇

发表评论