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 文档站,与本仓库无关。
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
# 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 |
|
||||
Reference in New Issue
Block a user