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 设为 1000Hz
sdkconfig 里的CONFIG_FREERTOS_HZ=1000
echo '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    // 小憩
  }
}