Files
ESP32S3_vt/reference/armor
Horldsence fd71b83e3a reference: 早先那版 C 写的装甲板固件(ESP-IDF 工程)
`armor/` 是它的 Rust 改写版,这一份留着当参考:ADC 阈值的 2000 就是从它标定出来的
(`.env.example` 里那句 "same as the reference firmware" 指的就是它),而且它的 README
记着实测设备与 MAC,换硬件时是有用的现场记录。

只收了 20 个源文件。它自带的 `.gitignore` 已经排掉 `build/`(211 MB 的 ESP-IDF 构建产物)
与生成的 `sdkconfig`,所以仓库里不会多出这些东西。

两处顺带处理:
- `reference/armor/.git` 是一个**空的**仓库壳(`git init` 过,零提交、零 remote、零 ref),
  而外层 git 正因为它的存在而拒绝索引 `reference/` 下的任何文件
  (`error: 'reference/armor/' does not have a commit checked out`)。它被挪到
  `desktop/.dev/backup/reference-armor-git`(可逆,且该目录已被忽略),不是删除。
- 它的 README 指着 `../armor-platformio-backup/`,那份备份并不存在。改成明说"没有进仓库",
  免得以后有人照着找一个不存在的目录。

另外把 `/docs/` 加进 .gitignore:那是工作区里另起的一个 Astro 文档站,与本仓库无关。
2026-10-14 19:45:15 +08:00
..

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=<build>/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 <ASCII 目录>。本仓库的 idf.ps1 已自动处理:

检测到工程路径含非 ASCII 字符时,自动改用 <盘符>:\esp-idf-build\armor 作为 build 目录

可用环境变量 IDF_BUILD_DIR 覆盖该位置;若想改回在工程内构建, 把工程移到纯 ASCII 路径即可(此时脚本不会加 -B)。

注意:用目录联接(junction)指向中文路径无效——PowerShell 的 Set-Location 会把联接解析回真实路径,CMake 仍然看到中文路径。

构建与烧录

# 构建(自动处理非 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 可用),也可以直接:

idf.py -B <ASCII 路径> build
idf.py -B <ASCII 路径> -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