文件
phonerail/firmware/HANDOFF.md
T
zhou yang和Claude Opus 5 45de84ed3c A7:五路配置表 + 表驱动遍历 + 启动自检
六组编号(INA 地址 / DAC 片通道 / 三个按键位 / EDL 位 / 两根 EN 的 GPIO)拼成
一张 const 表 rail_cfg(1..5),没有一列是顺排的。开机 rail_selftest() 把它和
A3~A6 四个驱动的表逐格对账,再查全板引脚重复 / 越界,不过就 abort ——
那四个自检谓词至此全部有了调用点。

判据拎成 rail_selftest_table(t) 单独可调:真表是 const 的,不这么做就没法证明
「真有事的时候查得出来」。测试复制一张表出来逐样改坏,六种坏法都验过。

顺带修一个真 bug:HAL 两侧 GPIO 边界写的是 pin < 48,而 EFUSE_EN2 就在 IO48
上 —— CH2 的 eFuse 既开不了也关不掉。sim 与 ESP 两份都改成 0~48 含。

9 条 ctest(131 个用例)全绿,idf.py build 通过,16 条注错 15 条变红
(唯一没红的那条与原因记在 HANDOFF.md)。

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-07 07:13:04 +08:00

6.8 KiB
原始文件 Blame 文件历史

交接

更新:2026-09-07 完成任务包 A7(通道配置表 + 表驱动遍历 + 启动自检)

一句话现状

五路的六组编号(INA 地址 / DAC 片通道 / 三个按键位 / EDL 位 / 两根 EN 的 GPIO) 拼成了一张 const 表 rail_cfg(1..5),开机自检 rail_selftest() 把它和四个驱动 那四张表逐格对账,再查一遍全板引脚有没有重复 / 越界,不过就 abort()。 A3~A6 留下的四个自检谓词至此全部有了调用点。9 条 ctest(131 个用例)全绿, idf.py build 通过。

下一步

A8:任务骨架与调度(5 个任务、两把锁,需求 §10.3 全节)。 验收是「sim 时钟下 t_safety 周期 20 ms ±1 ms;I²C 占用打印出来与 §10.3 的 8 % 对得上」。 A7 给它备好的东西:rail_cfg() 表驱动遍历、board_pins.h 的 SDA/SCL 引脚、 sim_gpio_write_seq()(断言顺序用,A9 的 17 ms 上电时序会更需要它)。

本次新增/修改的文件

  • main/rail_cfg.h / main/rail_cfg.c — 那张表、启动自检、两根 EN 线(≈250 行)
  • main/board_pins.h — 全板引脚表(§8.1 里「全板独一份」的那些脚:I²C、风扇、 NTC、USB、UP_VBUS_DET,加上 strapping / 不存在 / R8 版 PSRAM 三张禁用清单)。 五路自己的 EFUSE_ENn / VBUS_ENn 不在这里,它们是「每路一个」,在五路表里
  • main/app_main.c — 开机跑 rail_selftest(),不过就 abort;然后 rail_en_init()
  • test/test_rail_cfg.c(28 个用例)
  • components/phonerail_hal/sim/sim_hal.h + sim/hal_gpio_sim.c — 新增 sim_gpio_write_seq()(全局写序号,断言「先断 VBUS 再关 eFuse」这类顺序用); SIM_GPIO_PINS 48 → 49
  • components/phonerail_hal/esp/hal_gpio_esp.c — PIN_MAX 48 → 49
  • 两处 CMakeLists + main/phonerail.h(版本 0.1.0-a7)
  • ../需求文档.md §10.4 — 记 A7 落地 + 五条实现约定 + 那张六列表

验收命令与结果

$ bash host/test.sh
100% tests passed, 0 tests failed out of 9     # +test_rail_cfg(28),总计 131 个用例

$ .\build_idf.ps1          # PowerShell
phonerail.bin binary size 0x34250 bytes. ... 0xcbdb0 bytes (80%) free.
Project build complete.    # rail_cfg.c.obj 在(已单独确认)

# 16 个「一定会被写出来的错」逐个注进去跑过(一次性脚本,没入库):
#   INA 地址改回 0x40+ch / CH3 的 DAC 挪回 U6 / CH4↔CH5 按键位对调
#   EDL 位改回 16+ch / PG 位跟着 FLT 加偏移
#   EFUSE_EN2 撞上 UP_VBUS_DET / EFUSE_EN1 落到 strapping / CH1 两根 EN 写反
#   EN 极性写反 / all_off 先关 eFuse / all_off 第一个错就 return
#   en_init 不写那句明文的 0 / 表与驱动对账被删 / 引脚查重被删
#   GPIO 上限退回 48
#   → 以上 15 条全部变红。
#
# ⚠️ 唯一没能变红的一条:**把 `rail_selftest()` 整个改成 `return 0;`**。
#   判据本身(`rail_selftest_table()`)已经被测得很死,但「rail_selftest 有没有
#   真的去调它」这一行接线,在一切正常的前提下测试**原理上分不出来**
#   —— 真表是 const 的,没有任何输入能让它该报错。这是已知残留,不是漏做。

踩到的坑(只写会再犯的)

  • ⚠️ HAL 两侧的 GPIO 边界原来都是 pin < 48,而 EFUSE_EN2 在 IO48 上。 ESP32-S3 的编号是 0~48(含 48),写成「个数 48」正好把它关在门外, 现象是 CH2 的 eFuse 既开不了也关不掉(hal_gpio_config() 直接 HAL_ERR_ARG)。 已改成 49,test_io48_is_a_usable_pin 是回归拦网。 顺带记住:IO22IO25 在 S3 上根本不存在、IO26IO32 是模组内部 flash/PSRAM, 写进配置表 gpio_config() 既不报错也不干活 —— 自检把这些号也当越界。
  • ⚠️ 「不是顺排」要看整列,逐格撞算式会撞空。 第一版测试是「拿几个常见错算式 逐路比,断言错了几路」。写完逐格核对才发现:CH4 的按键位正好就是 9/10/11、 CH1 的 EDL 位正好是 16、CH3 的两根 EN 正好落在猜的算式上 —— 三处「错了 5 路」 实际都只有 4 路。猜错算式的测试是句空话,而且这种空话看上去和真断言一样。 改成断言整列不是等差数列(is_arithmetic()),任何 起点+步长×ch 一次拦掉。
  • 注错实验里把源文件 mtime 推到未来(os.utime(path, now+5)):注错与恢复 挤在同一秒里,make 有可能认为 .o 还是新的、跳过重编,于是跑的是上一轮的 二进制 —— 那样「注了错还是绿」是假绿,看起来却像「这条测试没覆盖到」。 ⚠️ 本轮没有观察到它真的发生(三条绿是真绿,原因见上面那段), 这条是预防性的;但假绿这种错一旦发生就会被当成结论写进文档,代价不对称。 (A6 那两条纪律仍然有效:try/finally 恢复原文;编译期红的一条要重写成能编过的。)
  • 判据要能脱离真表被调用。真表是 const 的,测试改不动,所以「查得出问题吗」 这件事本来是测不了的 —— 把判据拎成 rail_selftest_table(const rail_cfg_t *t) 之后,测试复制一份表出来逐样改坏即可。这条比多写十个断言管用。

这一层的几条约定(写 A8/A9/A10 之前看一眼)

  • rail_cfg() 是唯一入口。 任何地方都不许再出现 0x40 + rail、3 * (rail-1) 这类由通道号算出来的编号。六列里没有一列是顺排的,而写错了板子照样正常工作, 只是「重启 CH1」重启了 CH4。
  • ⚠️ 两列 GPIO 没有第二张表能对账(INA/DAC/595/PCA 那四列都有驱动那边兜着)。 所以自检查不出「EFUSE_EN1 和 VBUS_EN1 写反」—— 两个都是合法空闲脚、 不重复、不越界。唯一的拦网是 test_en_pins_match_the_pcb_netlist 里钉死的十个值 (出处 .claude/pcb1-parts.json 的 U20 焊盘网络)和 B1 上板实测。
  • rail_all_off() 是复位 / 收尾路径,不是故障快路径。 A10 过压要断的是出事 那一路,直接调 rail_efuse_enable(rail, false),别等着遍历五路。
  • 开机顺序:rail_selftest() → rail_en_init()(十个脚配成输出并拉低)→ 才轮到 rail_volt_init() 等器件。DAC 一上电输出就有电压,EN 这时必须已经确定是低的。
  • 高有效只出现一次(en_write())。和 sr595_set_hub_reset() 的反相、 rail_mv_to_code() 的减法同一条规矩。
  • 自检不过就 abort,不降级。 它查的全是编译进固件的常量表,不过 = 固件写错了, §10.10 的降级矩阵管的是硬件坏了。带着错表跑起来的样子是「命令发给了另一路」, 五台手机全在跑、一个错误码都不报。