Wi-Fi 工作模式
ESP32 的 Wi-Fi 驱动支持三种工作模式,可以根据项目需求灵活切换。理解这三种模式是设计 IoT 产品网络架构的基础。
STA(Station)
站点模式:ESP32 作为客户端连接到现有 Wi-Fi 路由器,获取 IP 地址后可访问局域网和互联网。最常见的 IoT 使用场景,如传感器上报、云端通信。
AP(Access Point)
热点模式:ESP32 自己作为无线接入点,其他设备(手机、平板)可连接到它。常用于设备初始配置(Provisioning),让用户通过手机浏览器输入家庭 Wi-Fi 密码。
APSTA(混合)
同时作为 STA 和 AP。ESP32 一方面连接上级路由器,另一方面对下游设备提供 Wi-Fi 接入,可充当简单的 Wi-Fi 中继或家庭网关。
Wi-Fi 事件系统
ESP-IDF 使用事件循环(event loop)异步处理 Wi-Fi 状态变化:WIFI_EVENT_STA_CONNECTED、IP_EVENT_STA_GOT_IP、WIFI_EVENT_STA_DISCONNECTED 等,程序通过注册回调函数响应。
lwIP
轻量级 IP 协议栈(lightweight IP),ESP-IDF 内置,实现了 TCP/UDP/DNS/DHCP/IPv4/IPv6 等协议,上层的 HTTP、MQTT 等组件都基于它工作。
Wi-Fi STA 连接流程
app_main()
│
▼
esp_netif_init() ← 初始化网络接口(必须最先调用)
│
▼
esp_event_loop_create_default() ← 创建默认事件循环
│
▼
esp_netif_create_default_wifi_sta() ← 创建 STA 网络接口
│
▼
esp_wifi_init(cfg) ← 初始化 Wi-Fi 驱动,传入配置
│
▼
注册事件处理函数 ← WIFI_EVENT / IP_EVENT
│
▼
esp_wifi_set_mode(WIFI_MODE_STA)
esp_wifi_set_config(SSID/Password)
esp_wifi_start() ← 触发 WIFI_EVENT_STA_START
│
▼
[事件: WIFI_EVENT_STA_START]
│
▼
esp_wifi_connect() ← 开始扫描并连接
│
▼
[事件: WIFI_EVENT_STA_CONNECTED]
│
▼
DHCP 获取 IP ← lwIP 自动处理
│
▼
[事件: IP_EVENT_STA_GOT_IP] ← ★ 此时可以进行网络通信
│
▼
业务代码(HTTP/MQTT/WebSocket...)
STA 模式完整代码
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "freertos/event_groups.h"
#include "nvs_flash.h"
#define WIFI_SSID "YourSSID"
#define WIFI_PASSWORD "YourPassword"
#define WIFI_RETRY_MAX 5
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static const char *TAG = "WIFI";
static EventGroupHandle_t s_wifi_event_group;
static int s_retry_num = 0;
static void event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < WIFI_RETRY_MAX) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGW(TAG, "重连中... 第 %d 次", s_retry_num);
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
} 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);
}
}
void wifi_init_sta(void)
{
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
&event_handler, NULL, NULL);
esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
&event_handler, NULL, NULL);
wifi_config_t wifi_config = {
.sta = {
.ssid = WIFI_SSID,
.password = WIFI_PASSWORD,
},
};
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());
/* 等待连接成功或彻底失败 */
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "Wi-Fi 已连接!");
} else {
ESP_LOGE(TAG, "Wi-Fi 连接失败");
}
}
void app_main(void)
{
nvs_flash_init(); // Wi-Fi 驱动需要 NVS 存储校准数据
wifi_init_sta();
/* 此后可开始网络通信 */
}
HTTP 客户端
ESP-IDF 内置 esp_http_client 组件,支持 HTTP 和 HTTPS、GET/POST/PUT 等方法、分块传输等功能,是调用 REST API 和云端服务的标准方式。
#include "esp_http_client.h"
#include "esp_log.h"
static const char *TAG = "HTTP";
/* 事件处理回调(可选) */
static esp_err_t http_event_handler(esp_http_client_event_t *evt)
{
switch(evt->event_id) {
case HTTP_EVENT_ON_DATA:
/* 接收到数据时回调,evt->data 和 evt->data_len */
ESP_LOGI(TAG, "收到 %d 字节", evt->data_len);
break;
default: break;
}
return ESP_OK;
}
void http_get_example(void)
{
esp_http_client_config_t config = {
.url = "http://httpbin.org/get",
.event_handler = http_event_handler,
.buffer_size = 1024,
};
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_err_t err = esp_http_client_perform(client);
if (err == ESP_OK) {
ESP_LOGI(TAG, "HTTP GET 状态码: %d,内容长度: %lld",
esp_http_client_get_status_code(client),
esp_http_client_get_content_length(client));
}
esp_http_client_cleanup(client);
}
void http_post_json(void)
{
const char *post_data = "{\"temperature\":25.6,\"humidity\":65.3}";
esp_http_client_config_t config = {
.url = "http://your-server.com/api/data",
.method = HTTP_METHOD_POST,
};
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_http_client_set_header(client, "Content-Type", "application/json");
esp_http_client_set_post_field(client, post_data, strlen(post_data));
esp_http_client_perform(client);
esp_http_client_cleanup(client);
}
HTTPS 与证书验证
生产环境中必须使用 HTTPS 来保护数据传输。ESP-IDF 使用 mbedTLS 实现 TLS,证书验证是防止中间人攻击的关键。
不要跳过证书验证!
有些教程为图方便设置 .skip_cert_common_name_check = true 或不提供 CA 证书,这相当于不做 HTTPS 验证,与 HTTP 一样不安全。IoT 设备在生产中必须正确配置服务器 CA 证书。
/* 将服务器的 CA 证书嵌入固件(CMakeLists.txt 中添加 target_add_binary_data) */
extern const char server_cert_pem_start[] asm("_binary_server_cert_pem_start");
extern const char server_cert_pem_end[] asm("_binary_server_cert_pem_end");
void https_get(void)
{
esp_http_client_config_t config = {
.url = "https://api.example.com/sensors",
.cert_pem = server_cert_pem_start, // 服务器 CA 证书
.transport_type = HTTP_TRANSPORT_OVER_SSL,
};
esp_http_client_handle_t client = esp_http_client_init(&config);
esp_http_client_perform(client);
esp_http_client_cleanup(client);
}
OTA 空中升级原理
OTA(Over-The-Air)升级让 ESP32 无需物理连接即可更新固件,是 IoT 产品运维的核心功能。ESP-IDF 通过分区表实现:Flash 中划分两个 OTA 分区(ota_0 和 ota_1),轮流烧录新固件,验证成功后切换启动。
Flash 分区表(OTA 典型布局):
┌────────────────────────┐ 0x000000
│ bootloader (32KB) │ 引导加载程序,检查 OTA 状态
├────────────────────────┤ 0x008000
│ nvs (24KB) │ 非易失性存储(Wi-Fi/配置)
├────────────────────────┤ 0x00e000
│ otadata (8KB) │ 记录当前活动分区(ota_0/ota_1)
├────────────────────────┤ 0x010000
│ ota_0 (1.9MB) │ ← 当前运行固件
├────────────────────────┤ 0x1f0000
│ ota_1 (1.9MB) │ ← OTA 下载目标分区
├────────────────────────┤ 0x3d0000
│ spiffs (192KB) │ 文件系统
└────────────────────────┘ 0x400000
OTA 升级流程:
1. 连接 HTTP 服务器,下载新固件
2. 边下载边写入 ota_1 分区
3. 校验完整性(SHA256)
4. 更新 otadata,标记 ota_1 为下一次启动
5. 重启,bootloader 从 ota_1 启动
6. 启动成功后调用 esp_ota_mark_app_valid_cancel_rollback()
7. 若启动失败,bootloader 自动回滚到 ota_0
#include "esp_ota_ops.h"
#include "esp_http_client.h"
#define OTA_URL "https://your-server.com/firmware.bin"
#define OTA_BUF_SIZE 4096
void ota_task(void *arg)
{
esp_ota_handle_t update_handle;
const esp_partition_t *update_partition = esp_ota_get_next_update_partition(NULL);
esp_ota_begin(update_partition, OTA_WITH_SEQUENTIAL_WRITES, &update_handle);
/* 下载并写入固件... */
char buf[OTA_BUF_SIZE];
int data_read;
/* ... HTTP 读取循环 ... */
esp_ota_write(update_handle, buf, data_read);
esp_ota_end(update_handle);
esp_ota_set_boot_partition(update_partition);
esp_restart();
vTaskDelete(NULL);
}
使用 esp-idf-lib 的 OTA 示例更完整
ESP-IDF 官方提供了 examples/system/ota/native_ota_example,包含完整的 HTTPS OTA 流程,建议直接参考该示例,它处理了分块下载、错误回滚等边界情况。