2026-09-16 完成、当时未提交,这里补提交(原样,不含 09-27 上板的修改)。 - 器件层 / 拓扑层整体从 R1 换到 R2:TCA9548A 分通道、五路同址 INA226 0x40 + 输入侧 0x43、 刀片 PCA9555(VBUS_EN 挪到 P0.4)、CH224A 只读驱动、风扇闭环 t_thermal - 新增 i2cbus(mux 原子事务)、slot(刀片 I/O 门面)、host/qtest.sh - 74HC595 链退役(代码与测试保留) - 判据阈值与时间常量未动;19 个 ctest 全绿,idf.py build 通过 Co-Authored-By: Claude Opus 5.5 <[email protected]>
70 行
5.4 KiB
Markdown
70 行
5.4 KiB
Markdown
# 交接
|
||
更新:2026-09-16 完成 **R-0 ~ R-6**(整个 R2 迁移,计划 v2.0 §6.2)
|
||
|
||
## 一句话现状
|
||
固件已经整体从 R1(单板 / 六片不同地址 INA226 / 74HC595 键链 / `VBUS_EN` 是 GPIO)
|
||
迁到 **R2**(底板 + 5 片同构刀片 / 全是 0x40 与 0x20 / TCA9548A 分通道 /
|
||
`VBUS_EN` 在刀片 PCA9555 的 P0.4 上)。**19 个 ctest、351 个用例全绿**,`idf.py build` 通过。
|
||
器件层与拓扑层全部换掉,**判据阈值与时间常量一个没动**(它们来自电路计算,R2 没改电路)。
|
||
|
||
## 下一步
|
||
**A12**:命令处理器(语义 + 序号判重 + 限幅)。R2 多一条验收:
|
||
**空槽(`PRSNT#` 为高)的命令一律拒**(`slot_present()` 已备好)。
|
||
另外 A15 开工前先把启动序列从 `app_main.c` 拆成宿主机可编的函数 —— 见下面「还没有拦网的」。
|
||
|
||
## 本次新增/修改的文件
|
||
- `main/i2cbus.{h,c}` **新** — mux 原子事务:拿锁 → 选通道 → 跑回调 → 放锁;通道影子 + 失败作废
|
||
- `main/slot.{h,c}` **新** — 一片刀片的 I/O 门面:路号 → mux 通道 → 影子下标只在这里换算一次
|
||
- `components/drv/tca9548a.{h,c}` / `ch224a.{h,c}` **新** — I²C 八路多路器 / PD 受电(只读)
|
||
- `main/t_thermal.{h,c}` **新** — 风扇闭环(一路 PWM 两只风扇 + `FAN_EN` 总开关 + 两路 TACH)
|
||
- `main/board_pins.h` — 整表重写(R2 的 GPIO 与 R1 **一个都不一样**)
|
||
- `components/drv/pca9555.{h,c}` — 重写:P0.0~4 是输出、五实例影子、初始化顺序是安全本身
|
||
- `components/drv/ina226.{h,c}` — 五路同址 0x40 + 输入侧 0x43
|
||
- `main/rail_cfg.{h,c}` — 换列:+`mux_ch` +`prsnt_n`,−`vbus_en`;新增 `RAIL_SELFTEST_MUX`
|
||
- `main/t_safety.{h,c}` — 一圈改成「五次 `xact` + 主干一次」;+整圈锁预算;+mux 失联/在位两条判据
|
||
- `main/t_actuator.{h,c}` — 595 链 → PCA9555 Port0;本文件现在**要拿 I²C 锁**
|
||
- `main/app_main.c` — R2 启动顺序(含 `HUB_RST` 只脉冲一次、`ina226_set_reversed(0x43,true)`)
|
||
- `main/evlog.h` — 末尾追加 `LOG_EV_MUX_LOST=14 / SLOT_EMPTY=15 / PD_LIMIT=16`
|
||
- sim:假 TCA9548A(关着的支路一律 NAK)、五实例假 PCA9555、假风扇
|
||
- `host/qtest.sh` **新** — 单个测试的快编快跑,迁移期并行开工用(DoD 仍认 `host/test.sh`)
|
||
- `components/drv/include/sr595.h` — 顶部加「⛔ R2 已退役」横幅,代码与测试保留不动
|
||
|
||
## 验收命令与结果
|
||
```
|
||
$ bash host/test.sh
|
||
100% tests passed, 0 tests failed out of 19 # 351 个用例
|
||
|
||
$ .\build_idf.ps1
|
||
Project build complete. # phonerail.bin 0x3fa90,分区剩 75%
|
||
```
|
||
|
||
## 踩到的坑(只写会再犯的)
|
||
- ⛔⛔ **PCA9555 的初始化顺序就是安全本身。** Output Port 复位值是 **0xFF**,
|
||
所以必须 `OUTPUT=0 → POLINV=0 → CONFIG1 → CONFIG0` ——**方向最后改**。
|
||
反过来的那一刻 P0.0~P0.4 一起输出 1 = 三个键全按下 + EDL 短接 + VBUS 上电。
|
||
注错实测:把顺序调过来,33 条用例里**只有 1 条变红**,终态完全正确 ——
|
||
这就是为什么它在真板上查不出来。拦网是 `sim_pca9555_unsafe_enable()`。
|
||
- ⛔ **`check_mux()` 的两段式不能拿「复位计数器和自己的快照比」做状态**:
|
||
复位之后把后者同步成前者,两个数永远相等 ⇒ 每圈都走「先复位」分支,
|
||
`MUX_RST#` 每 20 ms 脉冲一次而 `MUX_LOST` 一次也不出。状态要单独存(`mux_step`)。
|
||
- ⛔ **R2 一圈要拿六次锁**(R1 只拿一次)。每次 5 ms 超时看着没问题,
|
||
六次加起来 **30 ms > 20 ms 周期** ⇒ 总线繁忙时巡检成片丢拍,而每次 `xact` 都按时返回了。
|
||
现在有 `T_SAFETY_LOCK_BUDGET_MS`(10 ms)这条整圈的闸。
|
||
- ⚠️ **「断 VBUS 在锁内、关 eFuse 在锁外」这条,光断言「写的时候持着锁、通道对」拦不住**:
|
||
把它挪到锁外之后 `slot_vbus_enable()` 自己也会拿锁、也会选对通道,两条断言照样过。
|
||
真正的硬凭据是**「这一写发生在本圈第几次拿锁里」**(对 = 第 rail 次,错 = 第 7 次)。
|
||
- ⚠️ **`slot_present()` 读的是缓存**,`t_safety` 每圈要先 `slot_prsnt_poll()`。
|
||
不 poll 的现象是「拔掉刀片永远判不出来」,而快照里那五个 bool 看着完全正常。
|
||
- ⚠️ 假总线上 `hal_i2c_write_read()` 会让 `n_write` 和 `n_read` **各 +1**,
|
||
所以「巡检有没有写刀片」不能看 `n_write`,要看最后一次写的寄存器号。
|
||
- ⚠️ `i2cbus_init()` **必定留下一次 `MUX_RST#` 低脉冲**(先 config(OUTPUT) 再写 1),
|
||
测试里所有复位断言都要走增量。
|
||
|
||
## 还没有拦网的(不是遗漏,是已知边界)
|
||
- **启动序列一行也测不到**:`app_main.c` 只进 IDF 目标。像 `ina226_set_reversed(0x43,true)`
|
||
这种「漏了就把正常供电判成反灌」的单行,现在唯一的拦网是人眼。A15 先拆函数。
|
||
- **mux 通道整体循环错位**(0→1→2→3→4→0)自检全过:五路互不重复、都不越界、都不等于 5。
|
||
现象是「每一路读到的都是隔壁那台手机」。唯一判据是上板 **B1c 五槽逐一单插**。
|
||
- **选通道「写下去不报错但没生效」**:假器件写了就生效,宿主机造不出来。见计划 §11。
|
||
- **真并发**:sim 单线程,`i2cbus_xact` 把「拿锁 + 选通道」绑成原子的价值只能间接证明。
|