ESP-IDF+vscode开发ESP32 联网篇第二讲——WIFI STA

发布时间:2026/7/29 6:18:21
ESP-IDF+vscode开发ESP32 联网篇第二讲——WIFI STA 目录前言一、工程实现1.1 WIFI组件代码展览scan.cstation.cscan.hstation.hmian.c1.2 组件功能scan.c — 扫描模块实现station.c — STA 连接模块实现scan.h — 扫描模块头文件station.h — STA 模块头文件main.c — 应用入口1.3 代理逻辑说明1为什么要在事件循环之后创建默认 STA netif2为什么必须在 esp_wifi_start() 之前创建 STA netif3返回 netif 指针方便后续使用4station_init 中的主机名和 IP 事件回调二、结果展示总结前言本文在《ESP-IDFvscode开发ESP32 联网篇第一讲——WIFI扫描》的基础上继续完善 ESP32 的联网能力。 本篇重点是实现Station 终端功能让 ESP32 作为 Wi-Fi 客户端连接各类无线网络并通过事件回调和终端命令实现异常处理与自动重连。在这一讲中你将看到如何将扫描结果与 STA 连接逻辑结合起来构建一个可以在控制台中交互式选择 AP 并发起连接的完整示例。一、工程实现在 Wi-Fi 网络中STAStation指的是所有连接到无线网络的终端设备例如手机、电脑、智能家居等。 在本示例中ESP32 将以 STA 模式工作先扫描周围 AP再根据用户在控制台输入的编号和密码完成连接与断开操作。整个工程围绕一个“WiFi 组件”展开主要由以下几个源文件组成1.1 WIFI组件代码展览scan.c负责初始化 WiFi 子系统并完成首次全信道扫描同时注册scan控制台命令。/* * WiFi 扫描模块 — 实现文件 * * 功能 * 1. 初始化 NVS、TCP/IP 协议栈、事件循环、WiFi 子系统 * 2. 将 WiFi 设为 STA 模式并触发主动扫描 * 3. 注册 scan 控制台命令供用户手动重新扫描 * * 依赖 * - ESP-IDF: nvs_flash, esp_netif, esp_wifi, esp_event, esp_console, esp_log * - 本组件: scan.h函数声明、wifi_event.h事件处理回调 * */ /* ---------- 头文件 ---------- */ #include scan.h /* 本模块接口声明 */ #include wifi_event.h /* WiFi 事件处理回调 */ /* ---------- 宏定义 ---------- */ /** 模块日志标签 */ static const char *TAG wifi_scan; /* * 控制台命令 * */ /** * brief scan 命令回调 — 触发一次全信道扫描 */ static int scan_cmd_handler(int argc, char **argv) { ESP_LOGI(TAG, 触发重新扫描...); esp_err_t ret esp_wifi_scan_start(NULL, false); if (ret ! ESP_OK) { ESP_LOGE(TAG, 扫描启动失败: %s, esp_err_to_name(ret)); } return 0; } /** * brief 注册 scan 控制台命令 */ static void register_scan_cmd(void) { const esp_console_cmd_t cmd { .command scan, .help 触发一次 WiFi 全信道扫描结果通过日志输出, .hint NULL, .func scan_cmd_handler, }; ESP_ERROR_CHECK(esp_console_cmd_register(cmd)); } /* * 公开 API * */ /** * brief 初始化 WiFi 并触发一次全信道扫描 * * 本函数完成以下步骤 * 1. 初始化 NVSWiFi 校准数据存储依赖 * 2. 初始化 TCP/IP 协议栈esp_netif * 3. 创建默认事件循环 * 4. 创建默认 WiFi STA 网络接口必须在 WiFi 初始化之前 * 5. 初始化 WiFi 子系统默认配置 * 6. 注册 WiFi 事件回调 * 7. 设置为 STA 模式并启动 WiFi * 8. 发起主动全信道扫描 * 9. 注册 scan 控制台命令 */ esp_netif_t *wifi_scan_init(void) { esp_netif_t* sta_netif; /* ---- 1. 初始化 NVS ---- */ esp_err_t ret nvs_flash_init(); if (ret ESP_ERR_NVS_NO_FREE_PAGES || ret ESP_ERR_NVS_NEW_VERSION_FOUND) { /* NVS 分区损坏或版本不兼容擦除后重新初始化 */ ESP_ERROR_CHECK(nvs_flash_erase()); ret nvs_flash_init(); } ESP_ERROR_CHECK(ret); /* ---- 2. 初始化 TCP/IP 协议栈 ---- */ ESP_ERROR_CHECK(esp_netif_init()); /* ---- 3. 创建默认事件循环 ---- */ ESP_ERROR_CHECK(esp_event_loop_create_default()); /* ---- 4. 创建默认 WiFi STA 网络接口DHCP 依赖必须先于 WiFi 初始化 ---- */ sta_netif esp_netif_create_default_wifi_sta(); /* ---- 5. 初始化 WiFi 子系统默认配置 ---- */ wifi_init_config_t cfg WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(cfg)); /* ---- 6. 注册 WiFi 事件回调所有 WiFi 事件 ---- */ ESP_ERROR_CHECK( esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_event_handler, NULL, /* arg: 未使用 */ NULL)); /* instance: 未保存句柄 */ /* ---- 7. 设置为 STA 模式并启动 WiFi ---- */ ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_start()); /* ---- 8. 发起主动全信道扫描 ---- */ ESP_ERROR_CHECK(esp_wifi_scan_start(NULL, false)); ESP_LOGI(TAG, WiFi 初始化完成开始扫描 WiFi 网络...); /* ---- 9. 注册 scan 控制台命令 ---- */ register_scan_cmd(); return sta_netif; }station.c负责 STA 连接控制、自动重连逻辑以及connect/disconnect控制台命令。// WiFi STA 连接模块 — 提供 connect/disconnect 控制台命令 #include stdio.h #include stdlib.h #include string.h #include station.h #include wifi_event.h static const char *TAG wifi_station; void wifi_station_connect(char *ssid, char *password); // --- 控制台命令回调 --- // connect 编号 [密码] — 从 scan 列表中选编号连接 WiFi static int connect_cmd_handler(int argc, char **argv) { // 1. 检查扫描结果 if (wifi_event.ap_count 0) { printf(错误: 没有可用的扫描结果请先执行 scan 命令\r\n); return 1; } // 2. 解析编号 int index atoi(argv[1]); if (index 1 || index wifi_event.ap_count) { printf(错误: 编号 %d 超出有效范围 (1 ~ %d)\r\n, index, wifi_event.ap_count); return 1; } wifi_ap_record_t *ap wifi_event.ap_records[index - 1]; char *password (argc 3) ? argv[2] : ; if (ap-authmode WIFI_AUTH_OPEN strlen(password) 0){ printf(提示: 所选网络 \%s\ 为开放网络密码将被忽略\r\n, (char *)ap-ssid); } // 3. 重复连接判断 if (wifi_event.is_connected) { if (strcmp(wifi_event.last_ssid, (char *)ap-ssid) 0) { printf(提示: 已连接到 \%s\无需重复连接\r\n, wifi_event.last_ssid); return 0; } printf(当前已连接 \%s\断开后连接到新网络...\r\n, wifi_event.last_ssid); wifi_event.retry_enabled false; esp_wifi_disconnect(); vTaskDelay(pdMS_TO_TICKS(500)); } // 4. 发起连接 wifi_station_connect((char *)ap-ssid, password); printf(连接中请稍候... (该过程中不要重复连接)\r\n); return 0; } // disconnect — 断开当前 WiFi 并禁用自动重连 static int disconnect_cmd_handler(int argc, char **argv) { if (!wifi_event.is_connected) { printf(提示: 当前未连接任何网络\r\n); return 0; } wifi_event.retry_enabled false; esp_err_t ret esp_wifi_disconnect(); if (ret ! ESP_OK) { ESP_LOGE(TAG, 断开连接失败: %s, esp_err_to_name(ret)); return 1; } printf(已断开连接\r\n); return 0; } // --- 公开 API --- // 注册 IP 事件回调及 connect/disconnect 控制台命令须在 wifi_scan_init 之后调用 void wifi_station_init(esp_netif_t *esp_netif) { esp_netif_set_hostname(esp_netif, ESP32C6_HK); ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, wifi_event_handler, NULL, NULL)); const esp_console_cmd_t connect_cmd { .command connect, .help 连接指定 WiFi 网络需先执行 scan, .hint 编号 [密码], .func connect_cmd_handler, }; ESP_ERROR_CHECK(esp_console_cmd_register(connect_cmd)); const esp_console_cmd_t disconnect_cmd { .command disconnect, .help 断开当前 WiFi 连接, .hint NULL, .func disconnect_cmd_handler, }; ESP_ERROR_CHECK(esp_console_cmd_register(disconnect_cmd)); } // 保存凭据、配置 WiFi 并发起连接启用自动重连认证失败不重试 void wifi_station_connect(char *ssid, char *password) { strncpy(wifi_event.last_ssid, ssid, sizeof(wifi_event.last_ssid) - 1); wifi_event.last_ssid[sizeof(wifi_event.last_ssid) - 1] \0; strncpy(wifi_event.last_password, password, sizeof(wifi_event.last_password) - 1); wifi_event.last_password[sizeof(wifi_event.last_password) - 1] \0; wifi_event.retry_count 0; wifi_config_t wifi_config { .sta { .scan_method WIFI_ALL_CHANNEL_SCAN, .failure_retry_cnt 5, .threshold.authmode WIFI_AUTH_OPEN }, }; strncpy((char *)wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid) - 1); strncpy((char *)wifi_config.sta.password, password, sizeof(wifi_config.sta.password) - 1); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, wifi_config)); esp_err_t ret esp_wifi_connect(); if (ret ! ESP_OK) { ESP_LOGE(TAG, 连接启动失败: %s, esp_err_to_name(ret)); wifi_event.retry_enabled false; return; } ESP_LOGI(TAG, 连接请求已发送目标 SSID: %s, ssid); }scan.h对应的头文件声明对外暴露的初始化和连接接口。#ifndef SCAN_H #define SCAN_H #include stdint.h /* uint16_t */ #include esp_wifi.h /* wifi_ap_record_t */ /** * brief 初始化 WiFi 子系统并触发首次全信道扫描 * * 完成 NVS、TCP/IP 协议栈、事件循环、WiFi 驱动的初始化 * 设置 STA 模式并发起主动扫描。同时注册 scan 控制台命令。 * * note 必须在 wifi_station_init() 之前调用 */ esp_netif_t *wifi_scan_init(void); #endif /* SCAN_H */station.h对应的头文件声明对外暴露的初始化和连接接口。#ifndef STATION_H #define STATION_H /** * brief 配置并发起 WiFi STA 连接 * * 保存连接凭据并调用 esp_wifi_connect()。 * 内部启用自动重连最多 3 次认证失败时不重试。 * * param[in] ssid 目标 SSID以 \0 结尾 * param[in] password 密码开放网络可传空字符串 */ void wifi_station_connect(char *ssid, char *password); /** * brief 注册 IP 事件回调及控制台命令 * * 注册 IP_EVENT_STA_GOT_IP 回调以及 connect / disconnect 命令。 * * note 必须在 wifi_scan_init() 之后调用 */ void wifi_station_init(void); #endif /* STATION_H */mian.c应用入口串联 REPL 环境初始化、WiFi 扫描初始化和 STA 功能初始化。#include stdio.h #include basic.h #include scan.h #include station.h void app_main(void) { esp_log_level_set(wifi, ESP_LOG_WARN); // 设置 WiFi 模块日志级别为 ERROR减少日志输出 CONSOLE_REPL_INIT(); // 初始化控制台REPL环境 esp_netif_t *sta_netif wifi_scan_init(); // 初始化WiFi子系统(NVS/TCP/IP/事件循环/WiFi驱动)设为STA模式并触发首次全信道扫描注册scan命令 wifi_station_init(sta_netif); // 注册IP事件回调(GOT_IP)及connect/disconnect控制台命令 while(1) { vTaskDelay(pdMS_TO_TICKS(1000)); } }1.2 组件功能scan.c — 扫描模块实现scan.c模块负责完成 NVS、TCP/IP 协议栈、事件循环以及 WiFi 驱动的初始化并在 STA 模式下主动扫描周围的 AP。 同时它会为控制台注册一个scan命令方便用户在运行过程中随时重新触发扫描。代码的主要功能包括初始化 NVS用于存储 WiFi 校准数据和配置。初始化esp_netif网络栈并创建默认事件循环。创建默认 WiFi STA 网络接口为 DHCP 和 IP 分配提供基础。初始化 WiFi 驱动注册 WiFi 事件回调并设置为 STA 模式后启动 WiFi。发起一次主动全信道扫描并在控制台注册scan命令。通过该命令可以在后续随时重新扫描并查看日志输出结果。你可以在模块注释中清晰看到每一步初始化流程以及依赖关系对理解 ESP-IDF 下的 WiFi 启动过程非常有帮助。station.c — STA 连接模块实现station.c模块负责具体的连接与断开控制它通过注册connect和disconnect控制台命令让用户可以基于扫描结果选择要连接的 AP。模块的核心逻辑包括connect命令检查是否已有有效的扫描结果没有则提示先执行scan。根据用户输入的编号在扫描列表中找到对应 AP并处理开放网络与密码网络的差异。判断是否为重复连接如果当前已连接同一 SSID则直接提示无需重复连接如果要切换到不同网络则先断开当前连接。随后调用wifi_station_connect()发起连接请求。disconnect命令当用户主动断开连接时关闭自动重连机制并调用esp_wifi_disconnect()完成断开操作。wifi_station_connect()函数内部会保存当前连接使用的 SSID 和密码方便自动重连使用。启用自动重连机制初始化重连计数。认证失败时不会进行重试以免陷入错误密码循环。配置wifi_config_t结构体包括扫描方式、重试次数和认证阈值等再通过esp_wifi_set_config()应用配置并调用esp_wifi_connect()发起连接请求。通过丰富的日志和状态结构体wifi_event程序可以在事件回调中对不同异常情况连接失败、认证错误、意外断开等做出相应处理从而保证运行的稳定性。scan.h — 扫描模块头文件scan.h声明了wifi_scan_init()接口用于初始化 WiFi 子系统并触发首次全信道扫描。 该函数会完成 NVS、TCP/IP 协议栈、事件循环以及 WiFi 驱动的初始化并将 WiFi 设置为 STA 模式后发起主动扫描同时注册scan控制台命令。需要注意的是文档中明确说明必须在wifi_station_init()之前调用wifi_scan_init()否则后续 IP 事件回调和 STA 连接逻辑无法正常工作。station.h — STA 模块头文件station.h头文件中声明了两个对外函数wifi_station_connect(ssid, password)配置并发起 WiFi STA 连接保存连接凭据并启用自动重连机制。wifi_station_init()注册 IP 事件回调例如IP_EVENT_STA_GOT_IP以及connect/disconnect控制台命令。同样需要注意调用时序必须在wifi_scan_init()之后调用wifi_station_init()否则 IP 事件回调无法正确绑定到已存在的网络接口。main.c — 应用入口main.c中的app_main()函数串联了整个示例的启动流程。 主要步骤如下csdn设置 WiFi 模块的日志级别为ESP_LOG_ERROR减少普通信息日志方便聚焦异常和错误信息。初始化控制台 REPL 环境为后续的scan、connect、disconnect等命令提供交互界面。调用wifi_scan_init()初始化 WiFi 扫描功能完成基础网络栈和事件循环的搭建并发起首次扫描。调用wifi_station_init()注册 STA 相关的 IP 事件回调和控制台命令使得后续连接逻辑得以生效。进入一个简单的while(1)循环通过vTaskDelay()保持任务存在让整个 REPL 环境和事件系统持续运行。通过这几个步骤一个完整的“基于控制台命令操作 WiFi 扫描与连接”的示例就搭建完成了。1.3 代理逻辑说明本节主要解释wifi_scan_init()和wifi_station_init()中关键调用顺序背后的原因尤其是默认 STA 网络接口netif的创建时机。1为什么要在事件循环之后创建默认 STA netif在wifi_scan_init()中需要在创建事件循环之后、调用esp_wifi_start()之前执行esp_netif_create_default_wifi_sta()创建默认 WiFi STA 网络接口。原因是esp_netif_create_default_wifi_sta()内部会自动注册与 WiFi/IP 相关的事件处理器用于 DHCP、IP 分配等流程。事件处理器依赖已经存在的默认事件循环如果在事件循环创建之前调用会导致注册失败并触发ESP_ERROR_CHECK的断言报错。因此正确的顺序应该是初始化esp_netif。创建默认事件循环。调用esp_netif_create_default_wifi_sta()创建 STA netif。再初始化和启动 WiFi 驱动。2为什么必须在esp_wifi_start()之前创建 STA netifesp_wifi_start()启动 WiFi 硬件后固件会立即在事件循环中投递WIFI_EVENT_STA_START等事件。 此时必须已经存在 STA netif 的事件处理器来接收并处理这些事件否则后续 IP 分配流程会异常。如果在esp_wifi_start()之后才创建 netif现象表现为从 WiFi 的角度看连接过程似乎是成功的。但由于 IP 事件处理器没有及时就位DHCP 和 IP 分配无法顺利完成wifi_event_handler永远收不到IP_EVENT_STA_GOT_IP事件。最终体现为“能连上 AP但一直拿不到 IP 地址”。所以在设计本示例时esp_netif_create_default_wifi_sta()被明确地放在事件循环创建之后、esp_wifi_start()之前这一点对后续的站点连接和 IP 获取至关重要。3返回 netif 指针方便后续使用为了在后续模块中能够方便地使用已经创建好的 STA netif本示例将wifi_scan_init()的返回值设计为esp_netif_t *即返回默认 STA 网络接口的指针。虽然当前代码中主要通过esp_netif_get_handle_from_ifkey(WIFI_STA_DEF)等方式获取句柄但保留返回值可以让你在扩展工程时更灵活地传递和管理网络接口实例。4station_init 中的主机名和 IP 事件回调在wifi_station_init()函数中首先通过esp_netif_set_hostname()设置 STA 接口的主机名例如示例中使用的ESP32C6_HK这样在路由器的设备列表中更容易识别这块开发板。随后注册 IP 事件回调当 STA 成功获取 IP 地址时会触发IP_EVENT_STA_GOT_IP由统一的wifi_event_handler进行处理包括日志输出、状态更新以及后续自动重连逻辑等。最后注册connect和disconnect控制台命令为用户提供一个友好的交互入口让整个连接流程更易于调试和验证。二、结果展示总结本篇在上一讲 WiFi 扫描的基础上完成了 ESP32 作为 STA 终端的连接控制逻辑实现了从“发现网络”到“选择并连接”的完整流程。 通过合理安排事件循环、netif 创建和 WiFi 启动的时序示例保证了 DHCP 和 IP 分配过程的正确性。同时借助丰富的事件回调和控制台命令程序具备了基本的异常处理和自动重连能力非常适合作为后续扩展如 HTTP/MQTT 客户端、云端接入等的基础工程模板。你可以在此基础上继续扩展例如为每个 AP 记录信号强度排序、为不同网络配置不同的安全策略或在获得 IP 后自动启动上层协议任务使 ESP32 真正成为一个“可交互、可联网”的物联网节点。