# armor (ESP32-C3) — ESP-IDF 工程 本目录原为一个 PlatformIO / Arduino 工程,现已改造为 ESP-IDF 工程。 原始 PlatformIO 工程曾备份在同级的 `../armor-platformio-backup/`——**那份备份没有进仓库**, 这里的 `reference/armor/` 只保留改造后的 ESP-IDF 工程。 - 目标芯片:**ESP32-C3**(实测设备 `ESP32-C3 (QFN32) rev v0.4`,USB-Serial/JTAG,MAC `98:a3:16:4c:67:c8`) - ESP-IDF 版本:**v6.1.0** - 状态:**已构建并烧录成功,实测稳定运行** ## 目录结构 ``` armor/ ├── CMakeLists.txt # ESP-IDF 顶层工程文件 ├── sdkconfig.defaults # 默认配置(替代 platformio.ini 的 board/build_flags) │ # 由 idf.py save-defconfig 生成:IDF_TARGET / 4MB flash / 自定义分区表 ├── partitions.csv # 自定义分区表(2 MB factory 应用分区) ├── idf.ps1 # 便捷脚本:自动激活 ESP-IDF 后转发给 idf.py ├── .gitignore # 忽略 build/、sdkconfig、IDE 与本机生成物 ├── .gitattributes # 行尾策略:仓库统一 LF,.ps1/.bat/.cmd 在工作区用 CRLF └── main/ ├── CMakeLists.txt ├── armor_main.cpp # app_main:NVS / 串口 / 按键 / 业务任务(原 main.cpp) ├── air.hpp / air.cpp # ESP-NOW 对拷链路 ├── hit.hpp / hit.cpp # 压电片击打检测 + WS2812 ├── button.hpp/.cpp # OneButton 2.6.1 状态机移植 ├── storage.hpp/.cpp # NVS 封装(替代 Arduino Preferences) ├── armor_time.hpp # millis() └── led_strip_encoder.* # 官方 WS2812 RMT 编码器(取自 IDF 示例) ``` ## 与 PlatformIO 工程的对应关系 | PlatformIO / Arduino | ESP-IDF | | --- | --- | | `platform = espressif32`, `framework = arduino` | ESP-IDF v6.1 原生工程 | | `board = esp32-c3-devkitm-1` | `CONFIG_IDF_TARGET="esp32c3"` | | `lib_deps = Adafruit NeoPixel` | `led_strip_encoder.c` + RMT TX 驱动 | | `lib_deps = OneButton` | `main/button.cpp`(逐行移植其状态机) | | `Preferences` | `storage.cpp`(NVS) | | `ARDUINO_USB_CDC_ON_BOOT=1`(`Serial`) | USB Serial/JTAG 控制台 + `usb_serial_jtag_read_bytes()` | | `Serial0`(GPIO20/21) | `uart_driver_install(UART_NUM_0)`,TX=21 / RX=20 | | `setup()` / `loop()` / `loop2()` | `app_main()` / `hit_task` / `loop2_task` | | `gen_build.cmd`, `upload.cmd` | `idf.ps1`(见下) | ## 移植要点 ### 1. ADC:旧驱动在新版 IDF 中已被移除 原代码使用 `driver/adc.h` 的 legacy DMA 接口(`adc_digi_initialize` / `adc_digi_controller_configure` / `adc_digi_read_bytes`),该驱动在 ESP-IDF v6 中已删除。现改用 `esp_adc/adc_continuous.h`,采样参数保持一致: 总采样率 40 kHz、4 通道、12 bit、`ADC_ATTEN_DB_0`,每帧 10 ms(1600 字节)。 解析方式仍是直接读取 `adc_digi_output_data_t::type2`(C3 的 12 bit 布局), 因此击打判定阈值 `HIT_THRESHOLD = 2000` 的语义不变。 ### 2. WS2812:软件位翻转 → RMT 硬件驱动 Adafruit_NeoPixel 是软件位翻转驱动,这里换成 RMT + 官方 `led_strip_encoder`。 字节序保持 Adafruit 的 `NEO_GRB`(GRB + MSB first)。 **ESP32-C3 的 RMT 资源限制(踩坑记录)**:C3 的 RMT 只有一个 block,总计 **96 个 symbol 的通道内存**,按 `SOC_RMT_MEM_WORDS_PER_CHANNEL = 48` 为单位分配给 两个 TX 通道。因此: - `mem_block_symbols = 64` 会被向上取整到 96,即**一个通道吃光全部内存**, 导致第二个 `rmt_new_tx_channel()` 失败: `E rmt: rmt_tx_register_to_group(168): no free tx channels` → `ESP_ERROR_CHECK` 触发 `abort()` → 芯片反复重启。 - 4 条灯带在固件中始终显示同一颜色,所以这里只用**一个** RMT TX 通道, 令其独占全部 96 symbol(足够一次装下 3 颗灯珠的整帧:3×24 bit + reset = 73 symbol),再用 `rmt_tx_switch_gpio()` 依次把该通道切到 4 个 GPIO 上发送。 每条灯带约 140 µs,10 ms 周期内开销可忽略。 ### 3. 按键 按 OneButton 2.6.1 的 `_fsm()` / `debounce()` 逐行移植,默认值与调用方式 (`attachClick` / `attachLongPressStart` / `setClickMs(50)` / `setPressMs(500)` / 每 10 ms `tick()`)完全一致。未使用的双击、多击、idle 等功能没有移植。 ### 4. 控制台 控制台走 USB Serial/JTAG,等价于原来的 `ARDUINO_USB_CDC_ON_BOOT=1`, 同时让 UART0(GPIO20/21)腾出来给 DBUS 数据,与原 `Serial0` 一致。 USB CDC 的 10 字节 DBUS 帧由独立的 `cdc_task` 接收。 ### 5. NVS 命名空间仍是 `armor`,键名 `color`、`peer_mac` 保持原样。 ### 与原始固件的差异(有意为之) - `Air::onRecv()` 现在直接用 `esp_now_recv_info_t::rx_ctrl->rssi` 记录信号强度。 原代码依赖混杂模式回调“恰好先于” ESP-NOW 回调执行,存在竞态;新写法在配对 比较时能拿到当前包的准确 RSSI。 - `my_packet.dbus` 的写入/清零加了临界区保护(原代码在 CDC 回调与业务线程之间 无保护)。 - ESP-NOW 负载长度由 `alignas(1)` 改为 `__attribute__((packed))`, 两者都让 `sizeof(packet_t) == 19`,但后者是可移植写法(C++ 标准不允许 `alignas` 比成员自然对齐更宽松)。 - `hit_cnt++` 改为 `hit_cnt = hit_cnt + 1`:C++20 起对 `volatile` 使用 `++` 已废弃, 而 IDF 默认开启 `-Werror`。 - 分区表使用 2 MB 的 factory 分区(默认的 1 MB 对 WiFi + ESP-NOW 固件偏紧; 当前固件 0xcfbc0 ≈ 852 KB)。 其余业务逻辑(颜色切换、配对流程、串口协议、超时清零)与原实现逐条对应。 ## ⚠️ 非 ASCII 路径问题(本工程必须注意) 本工程所在路径包含中文(`D:\宣筹\校内赛\...`)。**ESP-IDF v6.1 在 Windows 上无法 在非 ASCII 路径下编译**,原因是 `components/esp_libc/project_include.cmake` 会把 编译器的 `-specs=/specs/picolibc.specs` 参数写进 `build/toolchain/cflags`,而读写该文件用的 `tools/cmake/toolchain_flags.cmake` 走的是 CMake `file(STRINGS)`;未指定 `ENCODING` 时它按 ASCII 解析,会**静默丢弃 所有非 ASCII 字节**,于是 `-specs=` 指向的文件名被破坏,每个编译单元都报: ``` riscv32-esp-elf-gcc.exe: fatal error: cannot read spec file 'D:/\x0a/\x0a/2026/SmallMaster-master/code/armor/build/specs/picolibc.specs': Invalid argument ``` 实测字节级证据(`file(STRINGS)` 往返): | | 字节 | | --- | --- | | 输入 `D:/宣筹/校内赛/...` | `... 2F, E5,AE,A3, E7,AD,B9, 2F, E6,A0,A1, E5,86,85, E8,B5,9B, 2F ...` | | `file(READ)` 直读(正常) | 同上 | | `file(STRINGS)` 往返后 | `... 2F, 0D,0A, 2F, 0D,0A, 2F ...` | **解决办法**:把 build 目录放到纯 ASCII 路径下(源码可以留在中文路径), 即 `idf.py -B `。本仓库的 `idf.ps1` 已自动处理: ``` 检测到工程路径含非 ASCII 字符时,自动改用 <盘符>:\esp-idf-build\armor 作为 build 目录 ``` 可用环境变量 `IDF_BUILD_DIR` 覆盖该位置;若想改回在工程内构建, 把工程移到纯 ASCII 路径即可(此时脚本不会加 `-B`)。 > 注意:用目录联接(junction)指向中文路径**无效**——PowerShell 的 > `Set-Location` 会把联接解析回真实路径,CMake 仍然看到中文路径。 ## 构建与烧录 ```powershell # 构建(自动处理非 ASCII 路径) .\idf.ps1 build # 烧录 .\idf.ps1 -p COM9 flash # 烧录并监视(Ctrl+] 退出) .\idf.ps1 -p COM9 flash monitor # 擦除整片 flash .\idf.ps1 erase-flash ``` `idf.ps1` 会在需要时通过 `eim_idf.json` 自动找到并激活本机的 ESP-IDF。 如果你已经激活过 ESP-IDF(`idf.py` 可用),也可以直接: ```powershell idf.py -B build idf.py -B -p COM9 flash monitor ``` 串口参数与原先一致:UART0 = 115200 8N1(DBUS),USB CDC 输出 `color,hit_cnt,peer_rssi,my_rssi,latency_ms` 的 CSV 行。 ## 实测验证结果(COM9,ESP32-C3 rev v0.4) 烧录后 boot log: ``` I (24) boot: ESP-IDF v6.1 2nd stage bootloader I (25) boot.esp32c3: SPI Speed : 80MHz / Mode : DIO / Flash Size : 4MB I (26) boot: 2 factory factory app 00 00 00010000 00200000 I (175) boot: Loaded app from partition at offset 0x10000 I (185) app_init: Project name: armor I (185) app_init: ESP-IDF: v6.1 I (190) main_task: Calling app_main() I (245) wifi:mode : sta (98:a3:16:4c:67:c8) I (247) wifi:(2.4G)set country: cc=JP schan=1 nchan=14 policy=1 I (250) wifi:ic_enable_sniffer <- 混杂模式 I (250) ESPNOW: espnow [version: 2.0] init I (251) armor: armor node started I (252) main_task: Returned from app_main() 16711680,0,0,0,65 16711680,0,0,0,75 ... ``` - 无重启、无 panic、无 abort;连续 6 秒稳定输出 555 行 CSV(约 11 ms 一条,符合 10 ms 循环)。 - `color = 16711680 = 0xFF0000`,即 NVS 为空时的默认红色,与 `Hit::RED` 一致。 - WiFi 国家码 `JP`、信道 1–14、混杂模式、ESP-NOW v2.0 全部按原逻辑生效。 ## 硬件引脚(未改动) | 功能 | GPIO | | --- | --- | | 按键(BOOT,低有效) | 9 | | WS2812 前 / 后 / 左 / 右 | 10 / 5 / 6 / 7 | | 压电片 ADC 前 / 后 / 左 / 右(ADC1_CH0/4/3/1) | 0 / 4 / 3 / 1 | | DBUS UART0 TX / RX | 21 / 20 |