Files
ESP32S3_vt/reference/armor/README.md
T
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

206 lines
9.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 仍然看到中文路径。
## 构建与烧录
```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 <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 |