U23 @ 0x20,16 个脚全部是输入:5×EFUSE_FLT# + 5×EFUSE_PGOOD + BUCK_PG + INA_ALERT + 4 个焊死在地上的备用脚(P0_7 / P1_5~7)。位表与地址从 .claude/pcb1-parts.json 的真实焊盘网络逐脚核回来,与设计文档 §17.2 一致。 驱动根本不提供「把某个脚配成输出」的接口(和 sr595.h 不提供「抬 OE#」同一条 规矩),方向字一律过 pca9555_dir_safe(),不安全直接 abort。原因是 PCA9555 的 Output Port 寄存器复位值是 0xFF —— 把接地脚配成输出的那一刻它就输出高电平, 而真器件既不报错也不 NAK,只是发烫。 三种极性(FLT# 低有效 / PGOOD 高有效 / INA_ALERT 低有效)各自只在一个函数里 出现一次;上层问「有没有故障」,不许自己去 & 位。 sim 侧新增按 datasheet 建模的假 PCA9555:Output 复位 0xFF、输入按位合成、 接地脚被驱动时记短路、INT# 只被「读输入端口」清除。为此给假 I²C 总线加了一个 窃听钩子(POST_WRITE / PRE_READ)—— 通用寄存器表模拟不了「配成输出就对地 短路」这类事,以后再有这种器件照这个挂。 验收:8 条 ctest(81 用例)全绿;idf.py build 通过,pca9555.c.obj 在。 12 个「一定会被写出来的错」逐个注进去跑过,全部变红。 Co-Authored-By: Claude Opus 5 <[email protected]>
5.6 KiB
5.6 KiB
交接
更新:2026-09-07 完成任务包 A6(PCA9555 驱动)
一句话现状
板上唯一的 I/O 扩展器(U23 @ 0x20)16 个脚全部是输入:5×FLT# + 5×PGOOD +
BUCK_PG + INA_ALERT + 4 个焊死在地上的备用脚。驱动根本不提供「配成输出」的接口,
方向字一律过 pca9555_dir_safe()。位表与地址从 PCB 焊盘网络逐脚核回来(与文档一致)。
8 条 ctest(81 个用例)全绿,idf.py build 通过。
下一步
A7:通道配置表 + 表驱动遍历(需求 §10.4 全节)。四个驱动包(A3/A4/A5/A6)已全部完成,
A7 的活是把 rail_cfg_t 那张静态表建起来,并在启动自检里一起调上四个自检谓词:
ina226_addr_table_ok() / dac63204_map_ok() / sr595_map_ok() / pca9555_map_ok()
(前三个至今没有任何调用点,A7 是它们的归宿)。
本次新增/修改的文件
components/drv/include/pca9555.h— 位表、三种极性、接地脚掩码(唯一对外契约)components/drv/pca9555.c— 驱动本体(≈195 行,只经hal_i2c_*)components/phonerail_hal/sim/sim_pca9555.c— 按 datasheet 建模的假器件 (Output 复位 0xFF、输入按位合成、接地脚短路检测、INT# 只被读输入端口清)components/phonerail_hal/sim/hal_i2c_sim.c+sim_hal.h— 假 I²C 加了一个 窃听钩子(SIM_I2C_EV_POST_WRITE/PRE_READ),通用寄存器表模拟不了「配成 输出就对地短路」这种事;以后再有这类器件照这个挂test/test_pca9555.c(20 个用例)- 三处 CMakeLists +
sim_hal.c的 reset 汇总 +main/phonerail.h(版本0.1.0-a6) ../需求文档.md§10.4 — 记 A6 落地 + 四条实现约定
验收命令与结果
$ bash host/test.sh
100% tests passed, 0 tests failed out of 8 # +test_pca9555(20)
$ .\build_idf.ps1 # PowerShell
phonerail.bin binary size 0x32550 bytes. ... 0xcdab0 bytes (80%) free.
Project build complete. # pca9555.c.obj 在(已单独确认)
# 12 个「一定会被写出来的错」逐个注进去跑过,**全部变红**(一次性脚本,没入库;做法见下面第 4、5 条坑):
# FLT#/PGOOD/INA_ALERT/BUCK_PG 四处极性各写反 -> 3~4 条红
# FLT 去读 Port1(端口搞反) -> 只有 test_one_rail_at_a_time 红
# 方向写成 0x7F(P0_7 配成输出) -> 4 条红,含 sim 的短路计数
# 忘了清极性反转 / 写了 Output Port -> 各 1~2 条红
# dir_safe 写成近似判据 dir != 0 -> 1 条红
# 读输入拆成两次事务 -> 1 条红(事务计数)
# 读失败时把调用方缓冲清零 -> 1 条红
踩到的坑(只写会再犯的)
- ⚠️ PCA9555 的 Output Port 寄存器复位值是 0xFF。 所以把一个接地脚配成输出的 那一刻它就输出高电平,中间没有「先写个 0 再说」的余地 —— 这比「配错方向」本身 更急。和 A5 那条「ESP-IDF 一配成输出就开始驱动」是同一类坑,只是这里默认值是高。
- FLT 与 PG 这次确实是顺排的(
bit = rail - 1),和 INA226 地址、595 位号那两张表 不一样。所以这一片最容易写出来的错不是算式,而是把 Port0 / Port1 搞反: 两个端口的 bit0~4 长得一模一样,静息态两个端口读回来都是全 1, 单看一帧完全看不出来。注错实验证实:搞反之后 20 条测试里只有test_one_rail_at_a_time一条红 —— 那条是专门为它写的,别删。 - 极性反转寄存器要无条件清零,不能赌复位值:看门狗只复位 ESP32,PCA9555 不掉电。
-Werror会替测试「假装」拦住错:注错实验里「读输入拆成两次事务」第一版是 编译期红(reg变量没人用),根本没跑到断言。补了一版能编过的才算数。 以后做注错实验,编译期红的那一条一定要重写成能编过的。- 注错脚本必须 try/finally 恢复原文:第一版崩在
subprocess吃不下/c/cygwin64/...这种 POSIX 路径(Windows Python 要C:\cygwin64\...), 异常直接把注错留在了源文件里。(同批小坑:长测试文件用 Bash heredoc 写会unexpected EOF,改用 Write 工具。)
这个驱动 / 这一层的几条约定(写 A10 之前看一眼)
- 三种极性各只出现一次(
pca9555_rail_fault/_rail_pg/_buck_pg/_ina_alert)。 上层一律问「有没有故障」,不许自己去 & 位 —— 和sr595_set_hub_reset()里那个 反相、rail_mv_to_code()里那个减法同一个道理:多一处就多一处写反的机会。 IOEXP_INT#(IO38)是加速路径不是主路径:PCA9555 要读走输入端口才释放 INT, 而且输入变回原值时它也会释放。只靠中断的话,漏掉一次读就再也等不到下一个沿。 所以t_safety的 20 ms 轮询是无条件的。A6 只留了引脚宏,读它是 A8/A10 的活。- 这一片不应答不许关输出(§10.10):只是失去 FAULT / PG 可见性,改用 INA226 的 电压判据兜底并持续告警。驱动的读失败一律只返回错误码,不做任何动作。
- ⚠️ 「全 0」不是安全默认值:它和「五路真的全炸了」读起来一模一样 (五路全故障 + 五路全不良 + Buck 不良 + 告警)。上拉没焊、总线被拉低都是这个样子, 所以 A10 判故障之前要先确认这一帧是真读回来的,不是失败后的残留缓冲。