先写清要迁移的行为,不先换一套闪灯
P03已经用一个按钮在500/150ms亮灭间隔之间切换,稳定30ms下降沿只计一次,长按不连续触发,卡顿后跳过错过边沿。本篇保留这些操作语义,再将初始化、硬件和状态拆成IDF组件。
Arduino不是没有任务:core把setup/loop放进loopTask;IDF由main task调用app_main。本例一个任务就能拥有全部状态,不为了“用了IDF”先增加共享变量和线程。10ms任务等待会改变采样量化,操作语义对齐不等于每个微秒延迟相同。
- 做到这里,你应该看到
- 先用P03列出启动、短按、长按、释放与卡顿后的预期,再决定代码放在哪层。
参考:[1] Arduino-ESP32 3.3.12 setup/loop所在的loopTask[2] ESP-IDF5.5.5 main task调用app_main并在返回后删除任务[5] ESP-IDF v5.5.5构建系统:组件注册、main特殊依赖、公开私有头、Kconfig与defaults[8] 本站P03完整Arduino程序与既定行为
同一回路只换软件,保留普通LED与上拉按钮
固定v1.1 N8R8,GPIO4/J1-4经330Ω接WP7113ID阳极,阴极回J1-22/GND;GPIO5/J1-5接常开按钮一组,另一组回地。板载GPIO38 RGB不参与,只用桥接USB供电与115200串口。
四脚按钮断电用通断挡确认两组,使用按下才导通的端点,不能把3V3与GND接到内部相通脚。先用P02/P03确认同一回路,再刷本例;若同时换电阻、引脚和供电,就无法把差异归因于框架迁移。
| 端点 | 连接/用途 |
|---|---|
| GPIO4 / J1-4 | 330Ω → WP7113ID阳极 |
| GND / J1-22 | LED阴极、按钮另一组 |
| GPIO5 / J1-5 | 内部上拉;按钮按下为LOW |
| USB-to-UART | 单路供电、烧录、桥接UART日志 |
- 做到这里,你应该看到
- Arduino与IDF使用同一块已确认的硬件回路,上电先松开按钮。
参考:[3] ESP-IDF5.5.5 GPIO配置与电平接口[6] 指定DevKitC-1 v1.1连接器、USB与板载RGB资料[7] WP7113ID规格:普通LED极性与条件[8] 本站P03完整Arduino程序与既定行为
把setup、loop和GPIO依赖分别映射
board_io组件初始化GPIO4为INPUT_OUTPUT,先设LOW,再将GPIO5设为输入上拉;数字读回不代表LED光学状态。button_blink组件只接收raw和uint32毫秒时间,公开头不含SDK类型,能单独检查。app_main依次采样、演算、输出、等待,是唯一状态所有者。
board_io公开头返回esp_err_t,因此公开REQUIRES esp_common;只在实现使用GPIO,属于PRIV_REQUIRES esp_driver_gpio。main私有依赖两个业务组件和esp_timer。不要把Arduino.h放进IDF工程当作“自动兼容层”,也不包含别人的.c来绕过组件结构。
| P03 Arduino | 本例IDF |
|---|---|
| setup / pinMode | board_io_init / gpio_config |
| loop状态更新 | button_blink_step纯C组件 |
| millis | esp_timer_get_time()/1000转uint32 |
| digitalRead / digitalWrite | board_io隔离GPIO SDK |
| core创建loopTask | SDK main task调用app_main |
| 常量 | Kconfig与defaults,入口检查跨项条件 |
- 做到这里,你应该看到
- 每个状态和依赖有明确所有者,能够单独解释一行初始化为何必要。
参考:[1] Arduino-ESP32 3.3.12 setup/loop所在的loopTask[2] ESP-IDF5.5.5 main task调用app_main并在返回后删除任务[3] ESP-IDF5.5.5 GPIO配置与电平接口[4] ESP-IDF5.5.5 esp_timer微秒计数接口[5] ESP-IDF v5.5.5构建系统:组件注册、main特殊依赖、公开私有头、Kconfig与defaults
保留稳定窗口和回绕逻辑,解释时序边界
时间使用uint32无符号差值,允许经过回绕;单次观察不能跨完整时钟周期。候选变化才重置changed_at,稳定下降沿切间隔并计数,释放不计数。上电按住只作为初始状态,松开后再按才有新press。
切换间隔不重置last_blink,已过去时间足够时本轮可能立即翻转;last_blink=now跳过错过边沿,不突发追赶。候选到接受的差值是采样消抖窗口,不含所有手指、电气、调度和光输出延迟。vTaskDelay是让任务等待,串口与系统调度仍会拉大间隙。
// SPDX-License-Identifier: MIT
#include "button_blink.h"
#include <stddef.h>
bool button_blink_init(button_blink_t *s,uint32_t now,bool raw,uint32_t slow,uint32_t fast,uint32_t debounce) {
if(!s || !fast || fast>=slow || !debounce || slow>=0x80000000u || debounce>=0x80000000u)return false;
*s=(button_blink_t){.candidate=raw,.stable=raw,.led=false,.changed_at=now,.last_blink=now,.interval=slow,.slow_ms=slow,.fast_ms=fast,.debounce_ms=debounce};return true;
}
unsigned button_blink_step(button_blink_t *s,uint32_t now,bool raw) {
if(!s)return 0;
unsigned event=0;
if(raw!=s->candidate){s->candidate=raw;s->changed_at=now;}
if(s->candidate!=s->stable && (uint32_t)(now-s->changed_at)>=s->debounce_ms){
s->stable=s->candidate;
if(!s->stable){++s->presses;s->interval=s->interval==s->slow_ms?s->fast_ms:s->slow_ms;event|=BUTTON_BLINK_PRESS;}
}
if((uint32_t)(now-s->last_blink)>=s->interval){s->last_blink=now;s->led=!s->led;event|=BUTTON_BLINK_EDGE;}
return event;
}
- 做到这里,你应该看到
- 长按只计一次;时间回绕与卡顿有明确策略,没有把无delay说成硬实时。
参考:[2] ESP-IDF5.5.5 main task调用app_main并在返回后删除任务[3] ESP-IDF5.5.5 GPIO配置与电平接口[4] ESP-IDF5.5.5 esp_timer微秒计数接口[8] 本站P03完整Arduino程序与既定行为
先构建同一默认行为,再干净改参
默认slow500/fast150/debounce30/poll10,入口检查0<fast<slow,非法配置报CONFIG_INVALID并保持初始化的LOW。Kconfig单项范围不能代替跨项关系检查。每五秒报告max_loop_gap,便于后续实板观察日志开销。
从另一份不含sdkconfig/build的新目录叠加alt.defaults,改为600/200/40。本站实际构建并核对生成header;只有刷到实板后启动字段与操作对应,才能验收硬件。defaults不会覆盖已有sdkconfig,清理前先保存配置,不从别人的build复制产物。
idf.py --version
idf.py build
# 另一份干净目录:600 / 200 / 40
idf.py -D 'SDKCONFIG_DEFAULTS=sdkconfig.defaults;sdkconfig.alt.defaults' build
idf.py -p PORT flash monitor- 做到这里,你应该看到
- 默认行为与P03可逐项对照,参数变化先在生成配置中确认。
参考:[2] ESP-IDF5.5.5 main task调用app_main并在返回后删除任务[3] ESP-IDF5.5.5 GPIO配置与电平接口[5] ESP-IDF v5.5.5构建系统:组件注册、main特殊依赖、公开私有头、Kconfig与defaults[6] 指定DevKitC-1 v1.1连接器、USB与板载RGB资料
入口把硬件、模型和等待串在一起
下方main.c与下载包一致,GPIO输出仅在模型报告新边沿时改变,接受按压才打印计数。main task进入循环后不会返回;非法配置返回时SDK会结束该main task,不是重新调用app_main。
任务10ms等待和串口打印会影响实际采样间隔;先减少日志再测量,不用提高优先级掩盖长等待。这里没有并发访问同一模型,因此不用先引入mutex或队列;真正分拆任务时再读E04。
// SPDX-License-Identifier: MIT
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "esp_system.h"
#include "sdkconfig.h"
#include "board_io.h"
#include "button_blink.h"
static uint32_t now_ms(void) { return (uint32_t)(esp_timer_get_time()/1000); }
void app_main(void) {
const char *tag="E03";
ESP_ERROR_CHECK(board_io_init());
button_blink_t state;
uint32_t now=now_ms(),last_loop=now,report_at=now,max_gap=0;
if(!button_blink_init(&state,now,board_button_high(),CONFIG_DEMO_SLOW_MS,CONFIG_DEMO_FAST_MS,CONFIG_DEMO_DEBOUNCE_MS)) {
ESP_LOGE(tag,"CONFIG_INVALID: require 0 < fast < slow, debounce > 0; LED stays LOW");return;
}
ESP_LOGI(tag,"IDF=%s GPIO4=ordinary LED GPIO5=active-low button; slow=%d fast=%d debounce=%d poll=%d",esp_get_idf_version(),CONFIG_DEMO_SLOW_MS,CONFIG_DEMO_FAST_MS,CONFIG_DEMO_DEBOUNCE_MS,CONFIG_DEMO_POLL_MS);
for(;;) {
now=now_ms();const uint32_t gap=now-last_loop;last_loop=now;if(gap>max_gap)max_gap=gap;
const unsigned event=button_blink_step(&state,now,board_button_high());
if(event&BUTTON_BLINK_EDGE)ESP_ERROR_CHECK(board_led_write(state.led));
if(event&BUTTON_BLINK_PRESS)ESP_LOGI(tag,"press=%" PRIu32 " accepted=%" PRIu32 " candidate=%" PRIu32 " debounce=%" PRIu32 " interval=%" PRIu32 " gpio_level=%d",state.presses,now,state.changed_at,now-state.changed_at,state.interval,board_led_level());
if((uint32_t)(now-report_at)>=5000){report_at=now;ESP_LOGI(tag,"max_loop_gap_ms=%" PRIu32 " interval=%" PRIu32,max_gap,state.interval);max_gap=0;}
// app_main itself runs in an IDF task; one state owner, no extra threads or queue needed here.
vTaskDelay(pdMS_TO_TICKS(CONFIG_DEMO_POLL_MS));
}
}
参考:[1] Arduino-ESP32 3.3.12 setup/loop所在的loopTask[2] ESP-IDF5.5.5 main task调用app_main并在返回后删除任务[3] ESP-IDF5.5.5 GPIO配置与电平接口[4] ESP-IDF5.5.5 esp_timer微秒计数接口
用同一组合成输入比较两份实现
包内reference/p03-reference.ino是原样P03,host/Arduino.h仅提供确定时间与电平输入桩,不进入IDF固件。迁移对照实际运行2组包含抖动、长按、释放、回绕和停顿的序列、共25个采样点,逐步比较LED状态、press数与interval;另有非法配置与延迟追赶检查。
这些主机结果证明所给输入下的逻辑一致,不证明GPIO电平、任务调度或实体灯。本站未进行上传与十次物理按压,观察表为空。实板先同回路保存Arduino日志,再刷IDF比较;异常先回P02核对端点、上拉与原始输入,再判断新增代码。
clang -std=c11 -I components/button_blink/include -c components/button_blink/button_blink.c -o /tmp/e03-model.o
clang++ -std=c++17 -Wall -Wextra -Werror -I host -I components/button_blink/include host/migration_compare.cpp /tmp/e03-model.o -o /tmp/e03-compare
/tmp/e03-compare- 做到这里,你应该看到
- 逻辑、调度、电气、实体四种观察各有边界;没有用主机PASS填实板CSV。
参考:[1] Arduino-ESP32 3.3.12 setup/loop所在的loopTask[2] ESP-IDF5.5.5 main task调用app_main并在返回后删除任务[3] ESP-IDF5.5.5 GPIO配置与电平接口[4] ESP-IDF5.5.5 esp_timer微秒计数接口[8] 本站P03完整Arduino程序与既定行为
资料来源与版本说明
按固定SDK、组件源码与指定板级核对;实际构建和主机输入检查另有证据,没有本站烧录、GPIO或串口运行结果。资料核对日期:。资料核对、程序构建与实板测试分别记录,不互相替代。