在 ESP-IDF 中,Wi-Fi 连接是一个异步的、事件驱动的过程。本文将带你捋清逻辑,并手写一段基于 FreeRTOS 事件组(Event Group)的标准 Wi-Fi 连接代码。
Wi-Fi 的连接过程(扫描、验证、DHCP 获取 IP)需要耗费数秒的时间。如果使用“死等”(阻塞式)的代码结构,会严重浪费 CPU 资源。因此,ESP-IDF 采用了事件循环 (Event Loop) 的架构:
系统后台默默处理 Wi-Fi 连接。
当状态发生改变(如:连接断开、成功获取 IP)时,系统向事件循环抛出一个“事件”。
事件回调函数 (Event Handler) 捕获到这些事件,并执行相应的逻辑(比如重试连接、通知主线程连接成功)。
在这个过程中,为了让我们的主任务知道 Wi-Fi 到底连上了没有,我们会借用 FreeRTOS 的事件组 (Event Group) 来进行任务间的同步。
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
// ------------------- 配置区 -------------------
// 根据实际情况修改 Wifi 的 SSID 和 PASSWORD
#define EXAMPLE_ESP_WIFI_SSID "Your_WIFI_SSID"
#define EXAMPLE_ESP_WIFI_PASS "Your_WIFI_PASSWORD"
#define EXAMPLE_ESP_MAXIMUM_RETRY 5
// ------------------- 状态定义 -------------------
/* FreeRTOS 事件组句柄,用于同步 Wi-Fi 连接状态 */
static EventGroupHandle_t s_wifi_event_group;
/* 定义事件组中的位 (Bits) */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static const char *TAG = "WIFI_APP";
static int s_retry_num = 0;
// ------------------- 事件回调函数 -------------------
/* 处理 Wi-Fi 和 IP 状态变化的异步事件 */
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
// 1. Wi-Fi 硬件启动成功,开始尝试连接
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
}
// 2. Wi-Fi 连接断开(可能密码错误或无信号)
else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
esp_wifi_connect(); // 触发重连
s_retry_num++;
ESP_LOGW(TAG, "连接断开,正在进行第 %d 次重试...", s_retry_num);
} else {
// 重试次数用完,通知主任务连接失败
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
ESP_LOGE(TAG,"连接 AP 失败");
}
// 3. 成功获取到 IP 地址
else 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;
ESP_LOGI(TAG, "成功获取到 IP 地址: " IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0; // 重置重试计数器
// 通知主任务连接成功
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
// ------------------- Wi-Fi 初始化逻辑 -------------------
void wifi_init_sta(void)
{
// 创建 FreeRTOS 事件组
s_wifi_event_group = xEventGroupCreate();
// 1. 初始化 TCP/IP 堆栈
ESP_ERROR_CHECK(esp_netif_init());
// 2. 创建默认的事件循环并初始化基础网络接口
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
// 3. 使用默认配置初始化 Wi-Fi 驱动
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// 4. 注册事件监听器 (将特定事件绑定到我们刚才写的 event_handler 上)
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,
&event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip));
// 5. 配置 Wi-Fi 工作模式并启动
wifi_config_t wifi_config = {
.sta = {
.ssid = EXAMPLE_ESP_WIFI_SSID,
.password = EXAMPLE_ESP_WIFI_PASS,
/* 如果配置了密码,将阈值设为 WPA2,以提高安全性 */
.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() );
ESP_LOGI(TAG, "Wi-Fi 驱动已启动,等待连接结果...");
// 6. 阻塞主任务,等待事件组中的标志位被设置
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE, // 不在返回前清空位
pdFALSE, // 满足任意一个位即可唤醒
portMAX_DELAY); // 死等,直到标志位出现
// 7. 根据唤醒时的标志位判断结果
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "成功连接到 SSID: %s", EXAMPLE_ESP_WIFI_SSID);
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGE(TAG, "无法连接到 SSID: %s", EXAMPLE_ESP_WIFI_SSID);
} else {
ESP_LOGE(TAG, "发生未知错误");
}
}
// ------------------- 主函数 -------------------
void app_main(void)
{
// 初始化 NVS 闪存 (Wi-Fi 驱动底层需要用它来存储网络配置等信息)
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_LOGI(TAG, "ESP32-S3 Station 模式启动");
wifi_init_sta();
// 连接成功后,可以在这里启动你的其他任务(比如 HTTP/MQTT 等)
} NVS (Non-volatile Storage) 不可省略: 在 app_main 中,最先执行的是 nvs_flash_init()。因为 ESP-IDF 的 Wi-Fi 底层驱动依赖 NVS 来持久化保存 MAC 地址、校准数据等。如果不初始化它,Wi-Fi 将直接启动失败。
异步等待: xEventGroupWaitBits 是 FreeRTOS 提供的神器。调用它后,当前的初始化任务会被完全挂起(不占用任何 CPU 轮询资源),直到事件循环拿到 IP 或彻底失败后,任务才会被瞬间唤醒继续往下走。xEventGroupWaitBits 函数通常和 xEventGroupSetBits 函数配对使用,xEventGroupSetBits 用于设置位(相当于发送通知),xEventGroupWaitBits 用于等待位被设置(相当于等待通知)。
代码的健壮性: 在 event_handler 中加入了重试机制 (s_retry_num)。在实际的物联网部署中,路由器重启或信号波动是常态,一个优秀的固件必须具备断线重连的能力。
#ESP32-S3# #ESP-IDF# #FreeRTOS# #WiFi# #C语言# #物联网#
(完)