`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 文档站,与本仓库无关。
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,MAC98: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 |