文档
ESP-IDF 开发:从环境搭建到 WiFi 应用
本章目标
搭建 ESP-IDF 开发环境,理解项目结构和组件化架构,开发第一个 WiFi + FreeRTOS 应用。
1. ESP-IDF 与 Arduino ESP32 的区别
| 维度 | Arduino ESP32 | ESP-IDF |
|---|---|---|
| 底层 | 封装 ESP-IDF | 直接 FreeRTOS |
| 控制粒度 | 粗(简单 API) | 细(完整控制) |
| 双核利用 | 有限 | 手动指定核心 |
| 功耗优化 | 基本 | deep-sleep + ULP |
| 量产 | 不建议 | 官方推荐 |
2. 项目结构
my_app/
├── CMakeLists.txt ← 顶层 CMake
├── sdkconfig ← idf.py menuconfig 生成
├── main/
│ ├── CMakeLists.txt ← idf_component_register
│ └── main.c
├── components/
│ └── my_driver/
│ ├── CMakeLists.txt ← idf_component_register(SRCS ... REQUIRES ...)
│ ├── my_driver.c
│ └── include/my_driver.h
└── partitions.csv ← 分区表
3. 第一个 FreeRTOS 任务
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
void my_task(void *pvParameters) {
int *param = (int *)pvParameters;
while (1) {
printf("Task %d running on Core %d\n", *param, xPortGetCoreID());
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void app_main(void) {
static int id1 = 1, id2 = 2;
xTaskCreate(my_task, "task1", 2048, &id1, 2, NULL);
xTaskCreate(my_task, "task2", 2048, &id2, 2, NULL);
// app_main 返回后,其栈空间被回收
}
栈大小经验值
| 任务类型 | 推荐栈 (words) |
|---|---|
| 简单打印 | 2048 |
| WiFi 通信 | 4096-6144 |
| HTTP 客户端 | 8192 |
| JSON 解析 | 6144+ |
4. WiFi 连接(事件驱动)
#include "esp_wifi.h"
#include "esp_event.h"
static void wifi_event_handler(void *arg, esp_event_base_t base,
int32_t id, void *event_data) {
if (id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (id == WIFI_EVENT_STA_DISCONNECTED) {
esp_wifi_connect(); // 自动重连
}
}
static void ip_event_handler(void *arg, esp_event_base_t base,
int32_t id, void *event_data) {
if (id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *ev = (ip_event_got_ip_t *)event_data;
ESP_LOGI("WiFi", "Got IP: " IPSTR, IP2STR(&ev->ip_info.ip));
}
}
void wifi_init_sta(const char *ssid, const char *pass) {
// 初始化网络栈 + 事件循环
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
// 注册事件
esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
&wifi_event_handler, NULL);
esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
&ip_event_handler, NULL);
// 配置并启动 WiFi
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
wifi_config_t wifi_cfg = {0};
strcpy((char *)wifi_cfg.sta.ssid, ssid);
strcpy((char *)wifi_cfg.sta.password, pass);
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg));
ESP_ERROR_CHECK(esp_wifi_start());
}
5. 组件化开发
# components/my_sensor/CMakeLists.txt
idf_component_register(
SRCS "my_sensor.c"
INCLUDE_DIRS "include"
REQUIRES driver freertos esp_timer
)
// components/my_sensor/include/my_sensor.h
#pragma once
#include "esp_err.h"
typedef struct {
float temperature;
float humidity;
} sensor_reading_t;
esp_err_t sensor_init(void);
esp_err_t sensor_read(sensor_reading_t *out);
组件之间通过 REQUIRES 声明依赖,构建系统自动处理 include 路径和链接顺序。
6. 功耗优化
// 进入 Deep Sleep(仅 RTC + ULP 运行,~5µA)
esp_sleep_enable_timer_wakeup(60 * 1000000); // 60 秒后唤醒
esp_deep_sleep_start();
// 唤醒后从 app_main 重新执行(类似复位)
思考题
- 为什么 ESP-IDF 使用事件驱动而非阻塞等待 WiFi 连接?
- 组件化架构相比把所有代码放
main/有什么优势? vTaskDelayvsvTaskDelayUntil的区别是什么?