ESP-IDF + arduino组件开发
ESP32 Arduino文档:https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/api-guides/tools/idf-component-manager.html
开发环境
Arduino组件目前仅支持到5.5版本(5.5.x不行)
在idf终端执行idf.py add-dependency "espressif/arduino-esp32^3.3.0" 安装组件
ESP-IDF 组件使用 Arduino 时,必须把 FreeRTOS Tick 设为 1000Hzsdkconfig 里的CONFIG_FREERTOS_HZ=1000echo 'CONFIG_FREERTOS_HZ=1000' >> sdkconfig.defaults 后续为乐固定创建一个默认值
两种使用Arduino的风格
完全模拟Arduino
vscode 搜索ESP-IDF: SDK Configuration Editor 然后在
Arduino Configuration>勾选Autostart Arduino setup and loop on boot
勾选Autostart 时不要实现 main();只写 setup()/loop()
#include "Arduino.h"
int LED_PIN = 1; // LED灯引脚
int BUTTON_PIN = 4; // 按钮引脚
// 初始化阶段,Arduino自动调用这两个函数
void setup() {
// 初始化操作
}
// 循环阶段,Arduino 会自动调用这个函数
void loop() {
// 循环
}
如果不自动运行需要手动运行:
extern "C" void app_main(void) { // ESP-IDF 的入口
// 初始化Arduino环境
initArduino();
// 调用setup函数
setup();
// 主循环,模拟Arduino的loop()
while (true) {
loop();
vTaskDelay(10 / portTICK_PERIOD_MS); // 让出CPU时间,避免看门狗超时
}
}
// 需手动调用初始化
void setup() {
// 初始化操作
}
// 手动调用循环
void loop() {
// 循环
}
注意事项
不要让 Arduino 和 IDF 同时操作同一个 GPIO(除非你非常确定顺序和互斥)
Arduino最小示范
点亮LED
接线:GPIO1 → LED → 电阻 → GND
#include "Arduino.h"
int LED_PIN = 1;
extern "C" void app_main(void) {
initArduino(); // 初始化Arduino
pinMode(LED_PIN, OUTPUT); // 设置引脚为输出
digitalWrite(LED_PIN, HIGH); // 给引脚赋一个高电平点亮
}
Arduino Autostart风格按键按下亮灯示范
#include "Arduino.h"
int LED_PIN = 1; // LED灯引脚
int BUTTON_PIN = 4; // 按钮引脚
// 初始化阶段(推荐把 pinMode 放在这里)
void setup() {
pinMode(LED_PIN, OUTPUT); // 设置 LED 为输出
pinMode(BUTTON_PIN, INPUT_PULLDOWN); // 若按钮接 3.3V,使用下拉输入;若按钮接地请改 INPUT_PULLUP
Serial.begin(115200); // 串口调试
Serial.println("串口启动成功"); // 启动提示
}
// 循环阶段,Arduino 会自动调用这个函数
void loop() {
if (digitalRead(BUTTON_PIN) == 1) {
Serial.println("检测到第一次按下"); // 打印日志
// 按键消抖
delay(200);
if (digitalRead(BUTTON_PIN) == 1) {
// 亮起LED
digitalWrite(LED_PIN, 1);
} else {
// 熄灭LED
digitalWrite(LED_PIN, 0);
}
} else {
// 熄灭LED
digitalWrite(LED_PIN, 0);
}
delay(1); // 让出时间片,喂狗,避免长时间占用导致 WDT 触发
}
IDF+Arduino组件共同使用,使用开关控制led灯
// 目标:用“IDF 驱动 WS2812 + Arduino 处理按钮/串口/计时”
// 功能:RGB灯 3秒亮 → 3秒灭 循环;SW1 切换运行/暂停;SW2 总开关(关闭/恢复)。
// 对比点:IDF 负责“硬件底层准确时序(RMT+led_strip)”;Arduino 负责“易用的GPIO/时间/串口”。
#include "Arduino.h" // 引入 Arduino API:pinMode/digitalRead/Serial/millis/delay // 使用Arduino简化GPIO与时间
#include "freertos/FreeRTOS.h" // 仍可与 FreeRTOS 并存(Arduino 在 ESP32 上本质也跑在 FreeRTOS) // RTOS基础
#include "freertos/task.h" // 可选:这里没直接用 vTaskDelay(演示用 Arduino 的 delay) // 任务API(备用)
#include "esp_log.h" // ESP-IDF 日志(和 Arduino Serial 对比用) // IDF日志
#include "esp_err.h" // 错误码与 ESP_ERROR_CHECK 宏 // 错误处理
#include "led_strip.h" // IDF 的 WS2812 抽象驱动 // 灯带驱动
#include "driver/rmt_tx.h" // RMT 发送端外设,用于精确时序驱动 WS2812 // RMT外设
// -------------------- 可按你的板修改的引脚定义 --------------------
static const int LED_GPIO = 48; // 板载RGB数据脚:常见 DevKitC-1 用 GPIO48;若不亮可改 38 // RGB灯数据脚
static const int BTN_RUN = 0; // SW1:建议接 BOOT(GPIO0),按下接地(低电平生效) // 运行/暂停键
static const int BTN_KILL = 13; // SW2:示例用 GPIO13(需有实际按键连此脚);无则改成你的“USER/KEY”脚 // 总开关键
// -------------------- 全局对象/状态 --------------------
static const char *TAG = "IDF+Arduino"; // IDF日志TAG,便于筛选 // 日志标签
led_strip_handle_t g_strip = nullptr; // IDF 灯带句柄,通过它 set_pixel/refresh // 灯带句柄
// 运行状态控制
volatile bool g_enabled = true; // 总开关:SW2 切换;false 时强制灭灯 // 总开关状态
volatile bool g_running = true; // 运行/暂停:SW1 切换;false 时暂停循环 // 运行状态
// 闪烁状态控制(非阻塞:用 millis 计时,随时响应按键)
bool led_on = false; // 当前LED是否点亮 // LED状态
unsigned long last_toggle_ms = 0; // 最近一次切换亮/灭的时间戳(毫秒) // 上次切换时刻
const unsigned long PHASE_MS = 3000; // 每一相 3 秒(亮 3s、灭 3s) // 相位时长
uint32_t loop_cnt = 0; // 完成“亮+灭”次数 // 计数器
// -------------------- 极简按键去抖与边沿检测(Arduino风格) --------------------
// 约定:按钮接法是“上拉输入”,按下=GND=LOW,因此“按下边沿”是 HIGH→LOW。
struct Debounce {
int pin; // 引脚号 // 按键引脚
bool stable; // 当前稳定电平 // 稳定电平
bool last_stable; // 上一次稳定电平 // 上次稳定
unsigned long last_change_ms; // 最近一次变化的时间 // 变化时刻
};
static Debounce db_run { BTN_RUN, true, true, 0 }; // 初值为 HIGH(未按下,因上拉) // SW1去抖
static Debounce db_kill{ BTN_KILL, true, true, 0 }; // SW2去抖 // SW2去抖
const unsigned long DEBOUNCE_MS = 30; // 去抖时间窗(30ms) // 去抖时间
// 读取一个按键,返回是否产生“按下/抬起”边沿;这里我们只用“按下”边沿
enum Edge { NO_EDGE, PRESS_EDGE, RELEASE_EDGE }; // 边沿类型枚举 // 枚举
static Edge read_button(Debounce &d) {
bool raw = digitalRead(d.pin); // 直接读电平(INPUT_PULLUP:未按=HIGH,按下=LOW) // 读引脚
unsigned long now = millis(); // 当前毫秒数 // 时间戳
if (raw != d.stable && (now - d.last_change_ms) >= DEBOUNCE_MS) {
d.last_stable = d.stable; // 记录上一次稳定值 // 记录旧值
d.stable = raw; // 更新稳定值 // 更新新值
d.last_change_ms = now; // 更新时间 // 更新时间
if (d.last_stable == HIGH && d.stable == LOW) return PRESS_EDGE; // 检测到按下边沿 // 按下
if (d.last_stable == LOW && d.stable == HIGH) return RELEASE_EDGE; // 检测到抬起边沿 // 抬起
}
return NO_EDGE; // 没有产生有效边沿 // 无边沿
}
// -------------------- IDF 驱动:封装点亮/熄灭(供主循环调用) --------------------
static inline void rgb_on_blue() {
ESP_ERROR_CHECK(led_strip_set_pixel(g_strip, 0, 0, 0, 255)); // 设为蓝色 (R,G,B) // 设色
ESP_ERROR_CHECK(led_strip_refresh(g_strip)); // 发送到灯 // 刷新
led_on = true; // 记状态 // 状态
}
static inline void rgb_off() {
ESP_ERROR_CHECK(led_strip_clear(g_strip)); // 清为全黑 // 清零
ESP_ERROR_CHECK(led_strip_refresh(g_strip)); // 发送到灯 // 刷新
led_on = false; // 记状态 // 状态
}
// -------------------- 应用入口:IDF世界里必须是 app_main --------------------
extern "C" void app_main(void) {
// 1) 初始化 Arduino 子系统(非常关键):之后才能用 pinMode/digitalRead/Serial/delay/millis
initArduino(); // 初始化 Arduino 运行时 // 初始化Arduino
Serial.begin(115200); // Arduino 串口(对比 ESP_LOGI);也可省略 // 打开串口
Serial.println("[Arduino] 运行"); // 打印启动信息 // 串口打印
ESP_LOGI(TAG, "ESP-IDF 运行"); // IDF 日志打印 // IDF日志
// 2) 配置按钮为上拉输入:未按=HIGH,按下=LOW(用导线把按钮另一端接 GND)
pinMode(BTN_RUN, INPUT_PULLUP); // SW1:运行/暂停键(建议用 BOOT/IO0) // SW1上拉
pinMode(BTN_KILL, INPUT_PULLUP); // SW2:总开关键(请改成你实际“USER/KEY”引脚) // SW2上拉
// 3) 创建 IDF 的 led_strip(RMT)设备:负责把 WS2812 所需的精确波形打出去
led_strip_config_t cfg{}; // 零初始化 // 配置体
cfg.strip_gpio_num = LED_GPIO; // WS2812 数据脚 // 数据脚
cfg.max_leds = 1; // 一颗灯 // 像素数
cfg.led_model = LED_MODEL_WS2812; // 指定型号 // 型号
cfg.flags.invert_out = false; // 不反相 // 反相
led_strip_rmt_config_t rmt{}; // RMT配置 // RMT体
rmt.clk_src = RMT_CLK_SRC_DEFAULT; // 默认时钟源 // 时钟
rmt.resolution_hz = 10 * 1000 * 1000; // 10MHz 分辨率(100ns) // 分辨率
rmt.mem_block_symbols = 64; // 缓冲深度 // 缓冲
rmt.flags.with_dma = false; // 无需 DMA // DMA
ESP_ERROR_CHECK(led_strip_new_rmt_device(&cfg, &rmt, &g_strip)); // 创建设备 // 创建设备
rgb_off(); // 上电先灭 // 先灭
last_toggle_ms = millis(); // 初始化相位计时 // 起始时间
// 4) 非阻塞主循环:用 millis 定时,每次循环都先处理按钮,再决定灯的状态
while (true) { // 永久循环(任务常驻) // 主循环
// ---- 4.1 按钮扫描 ----
switch (read_button(db_run)) { // 读 SW1(RUN/PAUSE) // 读SW1
case PRESS_EDGE: // 检测到按下 // 按下
g_running = !g_running; // 切换运行/暂停 // 切换状态
if (!g_running) rgb_off(); // 暂停时立刻灭灯 // 立即灭
Serial.printf("[Arduino] RUN=%s\n", g_running ? "ON" : "PAUSE"); // 串口打印 // 打印
ESP_LOGI(TAG, "RUN=%s", g_running ? "ON" : "PAUSE"); // IDF 日志也打印 // 打印
break;
default: break; // 其他情况忽略 // 忽略
}
switch (read_button(db_kill)) { // 读 SW2(KILL 总开关) // 读SW2
case PRESS_EDGE: // 按下边沿 // 按下
g_enabled = !g_enabled; // 切换开关 // 切换
if (!g_enabled) rgb_off(); // 关闭时立刻灭灯 // 立即灭
Serial.printf("[Arduino] ENABLED=%s\n", g_enabled ? "YES" : "NO"); // 串口打印 // 打印
ESP_LOGI(TAG, "ENABLED=%s", g_enabled ? "YES" : "NO"); // IDF 日志也打印 // 打印
break;
default: break; // 忽略 // 忽略
}
// ---- 4.2 状态机:非阻塞 3s 亮 / 3s 灭 ----
if (!g_enabled || !g_running) {
// 任一为假:总开关关 或 已暂停 → 保持灭灯,不计数
// (这样按钮一按就生效,不用等 3 秒周期结束)
// 若希望“暂停时保持当前亮/灭状态”,可去掉 rgb_off()
// 这里只要处于禁用/暂停,保持灭更直观。
// 已在切换时 rgb_off(),此处无需重复。
} else {
unsigned long now = millis(); // 当前毫秒 // 时间
if ((now - last_toggle_ms) >= PHASE_MS) { // 到达 3s 相位 // 到点
last_toggle_ms = now; // 轮换相位 // 更新时间
if (led_on) {
rgb_off(); // 从亮 → 灭 // 熄灭
++loop_cnt; // 完成一次“亮+灭” // 计数
Serial.printf("[Arduino] loop=%lu\n", (unsigned long)loop_cnt); // 串口计数 // 打印
ESP_LOGI(TAG, "loop=%lu", (unsigned long)loop_cnt); // IDF 计数 // 打印
} else {
rgb_on_blue(); // 从灭 → 亮 // 点亮
}
}
}
delay(10); // 10ms 轮询周期:Arduino 的 delay 内部是 vTaskDelay // 小憩
}
}