A3:INA226 驱动 + 六片地址表

地址表按设计文档 §8.2 钉死(0x40/41/44/45/42 + 输入侧 0x43),只走
ina226_rail_addr(),并有一条测试专门拦「ina_addr = 0x40 + ch」这一手
(CH3/CH4/CH5 三路全错,需求 §10.4)。

驱动只经 hal_i2c_*:认件(MFG/DIE ID)、写 Config 0x4327 + CAL 2048 并
回读校验、读 V/I/分流/功率。电流有符号,反灌为负(SW-8 判据)。
巡检走 read_vi(),每片两次事务,对应 §10.3 的 8% I²C 占用。

两条留给下次的坑:寄存器指针不依赖自增(sim 会自增、真件不会,靠自增
在宿主机上测不出来);CAL 丢了不报错只让 Current/Power 恒读 0,所以
有 ina226_check()。

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QYuExfgyYa6DzRRcUso8M8
这个提交包含在:
zhou yang
2026-09-06 18:43:34 +08:00
共同撰写人 Claude Opus 5
父节点 cac6ce47ce
当前提交 ceadf08345
共修改 8 个文件,包含 595 行新增和 38 行删除
+38 -35
查看文件
@@ -1,53 +1,56 @@
# 交接
更新:2026-09-06 完成任务包 **A2**(HAL 接口 + sim 实现)
更新:2026-09-06 完成任务包 **A3**(INA226 驱动 + 六片地址表)
## 一句话现状
四路 HAL(i2c / gpio / pwm / time)的接口定死了,两套实现都在:
宿主机链 `sim/`(假 I²C 器件 + 虚拟时钟 + 故障注入),ESP32-S3 链 `esp/`(IDF v5.4 新版
`i2c_master` / gpio / LEDC / esp_timer)。3 条 ctest(16 个用例)全绿,`idf.py build` 零警告。
六片 INA226 的地址表钉死在代码里(`0x40/41/44/45/42` + 输入侧 `0x43`),
驱动能认件(MFG/DIE ID)、写 Config+CAL 并回读校验、读出 V / I / 分流 / 功率,
电流**有符号**(反灌为负)。4 条 ctest(34 个用例)全绿,`idf.py build` 通过。
## 下一步
**A3**:INA226 驱动 + 六片地址表(`0x40/41/44/45/42/43`,设计文档 §8.2)。
驱动只准通过 `hal_i2c_*` 访问总线;测试用 `sim_i2c_add(addr, 2)` 挂假器件、
`sim_i2c_set_reg()` 预置值。**16 位寄存器是大端**,sim 已按大端实现。
**A4**:DAC63204 驱动 + `rail_set_mv()`(需求 §10.5 全节)。
三条断言:①4020 mV 被拒(SR-13)②**写更大的码值输出更低**(反向关系)③3900 mV ↔ Vd 0.548 V。
地址 `0x4A`(U6:CH1~3)/ `0x4B`(U7:CH4~5),同样查表不要算。
## 本次新增/修改的文件
- `components/phonerail_hal/include/hal_{err,i2c,gpio,pwm,time}.h` — 接口(唯一的对外契约)
- `components/phonerail_hal/hal_err.c` — 两个目标共用的错误码字符串
- `components/phonerail_hal/esp/hal_{i2c,gpio,pwm,time}_esp.c` — ESP-IDF 实现
- `components/phonerail_hal/sim/sim_hal.h` — **假板子的控制面**(应用代码不许包含)
- `components/phonerail_hal/sim/{sim_hal,hal_i2c_sim,hal_gpio_sim,hal_pwm_sim,hal_time_sim}.c`
- `components/phonerail_hal/CMakeLists.txt` — IDF 组件(只收 `esp/`,不收 `sim/`)
- `test/test_hal_i2c_sim.c`、`test/test_hal_sim.c` — 本包的测试
- `host/CMakeLists.txt` — 加 `PHONERAIL_HAL_SIM_SRCS` 与两个 include 目录
- `main/CMakeLists.txt` — `REQUIRES phonerail_hal`;`main/app_main.c` 调一次 HAL 保证链接
- `../固件开发计划.md` §4.1 — 记下组件重名那个坑
- `components/drv/include/ina226.h` — 寄存器、量程常量、地址表、采样结构(唯一对外契约)
- `components/drv/ina226.c` — 驱动本体(≈170 行,只经 `hal_i2c_*`)
- `components/drv/CMakeLists.txt` — 新 IDF 组件 `drv`,`REQUIRES phonerail_hal`
- `test/test_ina226.c` — 17 个用例
- `host/CMakeLists.txt` — 加 `PHONERAIL_DRV_SRCS` 与 `drv/include`
- `main/CMakeLists.txt` — `REQUIRES phonerail_hal drv`
## 验收命令与结果
```
$ bash host/test.sh
100% tests passed, 0 tests failed out of 3 # test_hal_i2c_sim / test_hal_sim / test_version
100% tests passed, 0 tests failed out of 4 # +test_ina226(17 个用例)
$ .\build_idf.ps1 # PowerShell
phonerail.bin binary size 0x32550 bytes. ... 0xcdab0 bytes (80%) free.
Project build complete.
Project build complete. # build/esp-idf/drv/.../ina226.c.obj 确认编到了
```
## 踩到的坑(只写会再犯的)
- **组件名不能叫 `hal`**:ESP-IDF 自己有 `components/hal`(`driver` 依赖它),
工程里同名会把它盖掉。所以叫 `phonerail_hal`。头文件名 `hal_*.h` 不冲突。
- **新增 HAL 实现要改两处 CMake**:`components/phonerail_hal/CMakeLists.txt`(esp 那份)
和 `host/CMakeLists.txt` 的 `PHONERAIL_HAL_SIM_SRCS`(sim 那份)。漏一处 =
「宿主机绿了但上板编不过」,反之亦然。`main/` 里的 .c 仍是老规矩的另外两处。
- **IDF 组件的 `SRCS` 是显式列举**,所以 `sim/` 放在组件目录里也不会被 IDF 编到。
- **别依赖 I²C 寄存器指针自增**:sim 的假器件**会**自增,INA226 按 datasheet **不会**。
「一次 write_read 连读 0x01~0x04」在宿主机上全绿,到板上四个量会读成同一个。
驱动每次读之前重写指针,`test_pointer_is_rewritten_before_every_read` 盯着这条。
(指针不自增这条**未在实物上验过**,B1 上板时确认。)
- **`write_verify()` 之后 `last_write` 是那次回读的指针字节(len 1),不是刚写的三字节。**
按「最后一次写就是我写的数据」写断言会红一次 —— 改看 `n_write`/`n_read` 计数。
- cygwin 并行链接偶发 `Error running link command: Subprocess aborted`(这次是 test_version)。
**重跑一次就好**,不是代码问题,别去查。
## HAL 的几条约定(写驱动前看一眼,省得回头改)
- 16 位 I²C 寄存器一律**大端**;写事务的第一个字节是寄存器指针,指针**跨事务保持**并自增。
- 读写长度不是 `reg_width` 整数倍 → `HAL_ERR_ARG`(真器件上是未定义行为,不许悄悄凑合)。
- 器件不在 / `present=false` / 注入 NAK → `HAL_ERR_NAK`;**没 `hal_i2c_init()` 就用 → `HAL_ERR_STATE`**。
- **写一个没配成输出的 GPIO 返回 `HAL_ERR_ARG`**(板子上 `gpio_set_level()` 是静默无效的)。
`HAL_GPIO_INPUT_PULLUP` 配完默认读回 **1** —— 五路 FLT/PG 是开漏 + 上拉,默认 0 会被当成一直有故障。
- PWM 占空比单位是**千分比 0~1000**,应用层不需要知道 LEDC 是几位。
- 时刻比较一律用 `hal_time_elapsed_ms()`,不要写 `now - then > x`(32 位毫秒 49.7 天回绕)。
- sim 的时钟是虚拟的:`hal_time_delay_ms()` 只把时间往前拨,测试零耗时。
每个测试 `setUp()` 里调 `sim_hal_reset()`。
## 这个驱动的几条约定(写 A7 / A10 之前看一眼)
- **地址查表**:`ina226_rail_addr(rail)`,rail 是 **1~5**(不是 0~4),越界 abort。
`ina226_all_addr[6]` 供扫描用;`ina226_addr_table_ok()` 做启动自检(含与
DAC 0x4A/0x4B、PCA9555 0x20、LM75 0x48 的撞车检查)。
- **单位**:`bus_mv` / `shunt_uv` / `current_ua` / `power_mw`,全是 `int32_t`。
电流 LSB 0.5 mA ⇒ `current_ua` 一定是 500 的整数倍;−60 mA(SW-8 判据)= `-60000`。
- **巡检走 `ina226_read_vi()`**(只读母线电压 + 电流,两次事务)——
§10.3 那 8 % 的 I²C 占用是按「每片每 20 ms 两次事务」算的,读四个量会翻倍。
- **`HAL_ERR_BUS` 在这个驱动里有特定含义**:器件应答了但内容不对
(probe 的 ID 不匹配 = 贴错件/地址跳线焊错;init 的回读不一致;check 的 CAL 丢了)。
和「没这个器件」的 `HAL_ERR_NAK` 是两回事,降级矩阵(A15)要分开处理。
- **CAL 丢了不会报错,只会让 Current/Power 恒读 0**,看上去像「这台手机不耗电」。
所以有 `ina226_check()`,巡检里定期查一次、不一致就 `ina226_init()` 重来并记事件。
- Config 定为 **0x4327**(AVG=4、VBUSCT=VSHCT=1.1 ms、连续测量分流与母线),
转换周期 8.8 ms < t_safety 的 20 ms。改这个值前先算一遍这个不等式。