文件
zhou yang和Claude Opus 5.5 b8ad69681f 固件:R2 迁移 R-0 ~ R-6(底板 + 5 片同构刀片)
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]>
2026-09-27 10:26:24 +08:00

355 行
16 KiB
C

此文件含有模棱两可的 Unicode 字符
此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。
/* A3:INA226 驱动 + 地址表(**R2 版**,计划 §5.2)。
两个验收主项:
① 总线上只有**两个** INA226 地址:刀片那五片全是 `0x40`,底板输入侧 `U13` 是 `0x43`;
② ⭐ **五路同址不是 bug** —— 区分是哪一路的是 **mux 通道**,不是地址。
---- R1 → R2 换掉了什么 --------------------------------------------------
R1 是「六片六个地址、一条总线」,于是那时的验收主项是「通道号 ≠ 地址低位」,
拦的是有人写出 `0x40 + rail`。R2 把五路做成五片完全同构的刀片(D8),
地址 strap 全部焊死在 0x40,**`0x40 + rail` 这行代码现在写出来会当场读不到器件**,
一上板就暴露。真正接替它的坑是通道:选错通道读到的是**另一台手机**的电压电流,
量纲、范围、变化趋势全都正常,没有任何一层会报错。
所以那条用例在这里被换成了「五路地址必须全相同」,
而「五路通道必须两两不同」那一半在 `test_rail_cfg.c` 里。 */
#include "ina226.h"
#include "sim_hal.h"
#include "unity.h"
void setUp(void)
{
sim_hal_reset();
for (uint8_t a = 0x40; a <= 0x4F; a++) { /* 对调登记是驱动里的静态表,不清会串到别的用例 */
ina226_set_reversed(a, false);
}
}
void tearDown(void) {}
/* 挂一片假 INA226 并预置 ID,返回地址。 */
static uint8_t mount(uint8_t addr)
{
sim_i2c_add(addr, 2);
sim_i2c_set_reg(addr, INA226_REG_MFG_ID, INA226_MFG_ID);
sim_i2c_set_reg(addr, INA226_REG_DIE_ID, INA226_DIE_ID);
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_init(NULL));
return addr;
}
/* ---- 地址表 -------------------------------------------------------------- */
/* ⭐ 验收主项一:总线上出现过的 INA226 地址只有两个,逐个钉死。
出处:刀片 `U3` 的 pin1/pin2(A1/A0)都接 GND ⇒ 0x40;底板 `U13` pin1=GND、
pin2=SCL ⇒ 0x43(`.claude/r2/_blade_pcb_nets.json` / `_base_pcb_nets.json`)。 */
static void test_addr_list_is_the_two_from_5_2(void)
{
static const uint8_t want[INA226_ADDR_COUNT] = { 0x40, 0x43 };
TEST_ASSERT_EQUAL_INT_MESSAGE(2, INA226_ADDR_COUNT, "R2 只有两个地址,不是六个");
TEST_ASSERT_EQUAL_HEX8_ARRAY(want, ina226_addr_list, INA226_ADDR_COUNT);
TEST_ASSERT_EQUAL_HEX8(0x40, INA226_ADDR_RAIL);
TEST_ASSERT_EQUAL_HEX8(0x43, INA226_ADDR_INPUT);
/* **物理片数仍然是 6**。两个数不一样,正是 R2 的全部意思 ——
把 `INA226_COUNT` 当成「扫描要扫几个地址」用,就会去扫 0x44/0x45 那种
R2 板上根本不存在的地址,然后得出「四片 INA226 失联」的结论。 */
TEST_ASSERT_EQUAL_INT(6, INA226_COUNT);
TEST_ASSERT_EQUAL_INT(5, INA226_RAIL_COUNT);
}
/* ⭐ 验收主项二:**五路的地址全是 0x40 —— 这不是 bug。**
五片刀片完全同构(D8),地址 strap 焊死;区分是哪一路的是
`rail_cfg(rail)->mux_ch`,光有地址是选不中任何一路的。
⚠️ 写错了会怎样:把这张表改回「每路一个地址」(R1 的记忆),
CH2~CH5 一上板就读不到器件 —— 这一半会当场暴露,不可怕。
可怕的是反过来:既然五路同址,任何「拿到地址就直接读」的代码
读的都是**上一次选中的那一路**,五路数字全部正常、还很稳,只是有几路是同一份。
这条用例只能钉住地址这一半,另一半(通道两两不同)在 test_rail_cfg.c 里。 */
static void test_all_five_rails_share_one_address(void)
{
for (int rail = 1; rail <= INA226_RAIL_COUNT; rail++) {
TEST_ASSERT_EQUAL_HEX8_MESSAGE(INA226_ADDR_RAIL, ina226_rail_addr(rail),
"五路同址是设计(D8),不是笔误");
}
/* 输入侧那片**必须**不同址:它挂主干,和刀片那片同时可见。 */
TEST_ASSERT_NOT_EQUAL(INA226_ADDR_RAIL, INA226_ADDR_INPUT);
/* R1 那行 `0x40 + rail` 现在对 CH1 仍然「碰巧对」,对其余四路全错 ——
写下来是为了让「拿 CH1 试一下没问题」这条老路在这里就断掉。 */
int naive_ok = 0;
for (int rail = 1; rail <= INA226_RAIL_COUNT; rail++) {
if (ina226_rail_addr(rail) == (uint8_t)(0x40 + rail - 1)) {
naive_ok++;
}
}
TEST_ASSERT_EQUAL_INT_MESSAGE(1, naive_ok, "只有 CH1 碰巧和 0x40+ch 对得上");
}
/* 撞车检查的对象在 R2 里换了一批:主干上的 DAC63204(0x4A/0x4B) 与 TCA9548A(0x70),
加上刀片支路上的 PCA9555(0x20)。**0x48 的 LM75 不在里面** —— R2 没有载板。 */
static void test_addr_table_has_no_collisions(void)
{
TEST_ASSERT_TRUE(ina226_addr_table_ok());
}
/* ---- 认件 ---------------------------------------------------------------- */
static void test_probe_accepts_real_ina226(void)
{
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_probe(mount(0x40)));
}
/* 应答了但 ID 不对 = 贴错件或地址跳线焊错,必须和「没这个器件」区分开。 */
static void test_probe_rejects_wrong_die_id(void)
{
uint8_t a = mount(INA226_ADDR_RAIL);
sim_i2c_set_reg(a, INA226_REG_DIE_ID, 0x1234);
TEST_ASSERT_EQUAL_INT(HAL_ERR_BUS, ina226_probe(a));
}
static void test_probe_absent_device_naks(void)
{
mount(INA226_ADDR_RAIL);
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, ina226_probe(INA226_ADDR_INPUT)); /* 没挂 */
sim_i2c_set_present(0x40, false);
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, ina226_probe(0x40)); /* 失联 */
}
/* 需求 §12.1 步 2 的 R2 版:扫描只会扫到**两个**地址。
⚠️ 这里挂的两片都在主干上,因为本文件测的是驱动、不带 mux;
真板上 0x40 那片在支路后面,**只有对应通道开着的时候才应答** ——
所以「扫一圈看到几片 INA226」在 R2 上回答不了「五路都在不在」。
那件事要靠逐通道扫(i2cbus + slot),不在本文件的范围里。 */
static void test_only_two_addresses_answer_a_plain_scan(void)
{
for (int i = 0; i < INA226_ADDR_COUNT; i++) {
mount(ina226_addr_list[i]);
}
for (int i = 0; i < INA226_ADDR_COUNT; i++) {
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_probe(ina226_addr_list[i]));
}
/* R1 的那四个地址在 R2 板上根本不存在 —— 照着老表扫会得出「四片失联」。 */
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, ina226_probe(0x41));
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, ina226_probe(0x42));
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, ina226_probe(0x44));
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, ina226_probe(0x45));
}
static void test_bus_not_initialised_propagates(void)
{
sim_i2c_add(0x40, 2);
int32_t mv = 0;
TEST_ASSERT_EQUAL_INT(HAL_ERR_STATE, ina226_read_bus_mv(0x40, &mv));
}
/* ---- 初始化 -------------------------------------------------------------- */
static void test_init_writes_config_and_calibration(void)
{
uint8_t a = mount(INA226_ADDR_INPUT);
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_init(a));
TEST_ASSERT_EQUAL_HEX16(INA226_CONFIG, sim_i2c_get_reg(a, INA226_REG_CONFIG));
TEST_ASSERT_EQUAL_HEX16(2048, sim_i2c_get_reg(a, INA226_REG_CALIB));
/* 两个寄存器都是「写完立刻回读校验」:4 次写(两次三字节的写 + 两次一字节的
读指针)、2 次读。最后落下的那次写就是 CAL 的读指针。 */
const sim_i2c_dev_t *d = sim_i2c_dev(a);
TEST_ASSERT_EQUAL_UINT32(4, d->n_write);
TEST_ASSERT_EQUAL_UINT32(2, d->n_read);
TEST_ASSERT_EQUAL_UINT8(1, d->last_write_len);
TEST_ASSERT_EQUAL_HEX8(INA226_REG_CALIB, d->last_write[0]);
}
/* CAL 掉了的现象是 Current/Power 恒读 0,不是报错 —— 所以要能查出来。 */
static void test_check_catches_lost_calibration(void)
{
uint8_t a = mount(0x40);
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_init(a));
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_check(a));
sim_i2c_set_reg(a, INA226_REG_CALIB, 0); /* 器件掉过一次电 */
TEST_ASSERT_EQUAL_INT(HAL_ERR_BUS, ina226_check(a));
}
/* ---- 换算 ---------------------------------------------------------------- */
/* 满载一路:4.010 V / 4 A / 16 W。5 mΩ + 0.5 mA LSB 下分流码与电流码同值。 */
static void test_readings_at_full_load(void)
{
uint8_t a = mount(INA226_ADDR_RAIL);
sim_i2c_set_reg(a, INA226_REG_BUS_V, 3208); /* ×1.25 mV */
sim_i2c_set_reg(a, INA226_REG_SHUNT_V, 8000); /* ×2.5 µV = 20 mV */
sim_i2c_set_reg(a, INA226_REG_CURRENT, 8000); /* ×0.5 mA */
sim_i2c_set_reg(a, INA226_REG_POWER, 1280); /* ×12.5 mW */
ina226_sample_t s = { 0, 0, 0, 0 };
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_read_all(a, &s));
TEST_ASSERT_EQUAL_INT32(4010, s.bus_mv);
TEST_ASSERT_EQUAL_INT32(20000, s.shunt_uv);
TEST_ASSERT_EQUAL_INT32(4000000, s.current_ua);
TEST_ASSERT_EQUAL_INT32(16000, s.power_mw);
}
/* ⭐ 反灌(SW-8):电流寄存器是**有符号**的。当成无符号会得到 +32 A 这种数,
而判据是「电流为负」,于是保护永远不触发。 */
static void test_reverse_current_is_negative(void)
{
uint8_t a = mount(INA226_ADDR_RAIL);
sim_i2c_set_reg(a, INA226_REG_CURRENT, (uint16_t)(int16_t)-120); /* −60 mA */
sim_i2c_set_reg(a, INA226_REG_SHUNT_V, (uint16_t)(int16_t)-120); /* −300 µV */
int32_t ua = 0, uv = 0;
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_read_current_ua(a, &ua));
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_read_shunt_uv(a, &uv));
TEST_ASSERT_EQUAL_INT32(-60000, ua);
TEST_ASSERT_EQUAL_INT32(-300, uv);
}
/* 3.90 V 出厂默认值那一点,顺带看四舍五入。3120 × 1.25 = 3900 mV 整。 */
static void test_bus_voltage_rounding(void)
{
uint8_t a = mount(0x40);
sim_i2c_set_reg(a, INA226_REG_BUS_V, 3120);
int32_t mv = 0;
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_read_bus_mv(a, &mv));
TEST_ASSERT_EQUAL_INT32(3900, mv);
sim_i2c_set_reg(a, INA226_REG_BUS_V, 3121); /* 3901.25 → 3901 */
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_read_bus_mv(a, &mv));
TEST_ASSERT_EQUAL_INT32(3901, mv);
}
/* ⚠️ 驱动不许依赖寄存器指针自增:sim 的假器件会自增,INA226 按 datasheet 不会。
靠自增省事的写法在这里会绿,到板上四个量全读成同一个。 */
static void test_pointer_is_rewritten_before_every_read(void)
{
uint8_t a = mount(INA226_ADDR_RAIL);
ina226_sample_t s = { 0, 0, 0, 0 };
sim_i2c_dev(a)->n_write = 0;
sim_i2c_dev(a)->n_read = 0;
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_read_all(a, &s));
TEST_ASSERT_EQUAL_UINT32(4, sim_i2c_dev(a)->n_write);
TEST_ASSERT_EQUAL_UINT32(4, sim_i2c_dev(a)->n_read);
}
/* 巡检主路径:只读两个量(§10.3 那 8 % I²C 占用是按这个算的)。 */
static void test_read_vi_uses_two_transactions(void)
{
uint8_t a = mount(0x40);
sim_i2c_set_reg(a, INA226_REG_BUS_V, 3120);
sim_i2c_set_reg(a, INA226_REG_CURRENT, 4000);
sim_i2c_dev(a)->n_read = 0;
ina226_vi_t vi = { 0, 0 };
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_read_vi(a, &vi));
TEST_ASSERT_EQUAL_INT32(3900, vi.bus_mv);
TEST_ASSERT_EQUAL_INT32(2000000, vi.current_ua);
TEST_ASSERT_EQUAL_UINT32(2, sim_i2c_dev(a)->n_read);
}
/* 器件中途失联:错误必须一路传上去,不能返回一个看着正常的 0。 */
static void test_read_propagates_nak(void)
{
uint8_t a = mount(INA226_ADDR_INPUT);
sim_i2c_inject_nak(a, -1);
ina226_vi_t vi = { 123, 456 };
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, ina226_read_vi(a, &vi));
}
static void test_null_output_rejected(void)
{
mount(0x40);
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, ina226_read_vi(0x40, NULL));
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, ina226_read_all(0x40, NULL));
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, ina226_read_bus_mv(0x40, NULL));
}
/* ---- Kelvin 对调接的片子(R2 底板 U13)--------------------------------- */
/* ⭐ R2 底板 U13 的 IN+ 接下游:正常供电时芯片读出负值。登记后驱动取反,
调用方看到正值 —— 否则 SW-8 会把正常供电当成反灌。母线电压 / 功率是无符号量,不动。 */
static void test_reversed_chip_reads_positive_in_normal_flow(void)
{
uint8_t a = mount(INA226_ADDR_INPUT);
ina226_set_reversed(a, true);
TEST_ASSERT_TRUE(ina226_is_reversed(a));
sim_i2c_set_reg(a, INA226_REG_BUS_V, 9600); /* 12.000 V */
sim_i2c_set_reg(a, INA226_REG_SHUNT_V, (uint16_t)(int16_t)-8000); /* 芯片读 −20 mV */
sim_i2c_set_reg(a, INA226_REG_CURRENT, (uint16_t)(int16_t)-8000); /* 芯片读 −4 A */
sim_i2c_set_reg(a, INA226_REG_POWER, 3840); /* 48 W,恒正 */
ina226_sample_t s = { 0, 0, 0, 0 };
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_read_all(a, &s));
TEST_ASSERT_EQUAL_INT32(12000, s.bus_mv);
TEST_ASSERT_EQUAL_INT32(20000, s.shunt_uv);
TEST_ASSERT_EQUAL_INT32(4000000, s.current_ua);
TEST_ASSERT_EQUAL_INT32(48000, s.power_mw);
ina226_vi_t vi = { 0, 0 };
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_read_vi(a, &vi));
TEST_ASSERT_EQUAL_INT32(4000000, vi.current_ua);
}
/* 对调片子上真反灌:芯片读正值 → 驱动给出负值,SW-8 照样触发。
没登记的片子(CH 路)不受影响。 */
static void test_reversed_only_affects_that_chip(void)
{
uint8_t in = mount(INA226_ADDR_INPUT);
uint8_t ch = mount(0x40);
ina226_set_reversed(in, true);
sim_i2c_set_reg(in, INA226_REG_CURRENT, 120); /* 芯片读 +60 mA = 反灌 */
sim_i2c_set_reg(ch, INA226_REG_CURRENT, 120);
int32_t ua_in = 0, ua_ch = 0;
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_read_current_ua(in, &ua_in));
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_read_current_ua(ch, &ua_ch));
TEST_ASSERT_EQUAL_INT32(-60000, ua_in);
TEST_ASSERT_EQUAL_INT32(60000, ua_ch);
TEST_ASSERT_FALSE(ina226_is_reversed(ch));
}
/* 取消登记恢复原样;地址越界(不是 INA226 的 0x40~0x4F)的调用被忽略、不越界写。 */
static void test_reversed_can_be_cleared_and_ignores_bad_addr(void)
{
uint8_t a = mount(INA226_ADDR_RAIL);
ina226_set_reversed(a, true);
ina226_set_reversed(a, false);
TEST_ASSERT_FALSE(ina226_is_reversed(a));
sim_i2c_set_reg(a, INA226_REG_CURRENT, (uint16_t)(int16_t)-120);
int32_t ua = 0;
TEST_ASSERT_EQUAL_INT(HAL_OK, ina226_read_current_ua(a, &ua));
TEST_ASSERT_EQUAL_INT32(-60000, ua);
ina226_set_reversed(0x20, true); /* PCA9555 的地址 */
ina226_set_reversed(0x50, true);
TEST_ASSERT_FALSE(ina226_is_reversed(0x20));
TEST_ASSERT_FALSE(ina226_is_reversed(0x50));
for (uint8_t b = 0x40; b <= 0x4F; b++) {
TEST_ASSERT_FALSE(ina226_is_reversed(b));
}
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_addr_list_is_the_two_from_5_2);
RUN_TEST(test_all_five_rails_share_one_address);
RUN_TEST(test_addr_table_has_no_collisions);
RUN_TEST(test_probe_accepts_real_ina226);
RUN_TEST(test_probe_rejects_wrong_die_id);
RUN_TEST(test_probe_absent_device_naks);
RUN_TEST(test_only_two_addresses_answer_a_plain_scan);
RUN_TEST(test_bus_not_initialised_propagates);
RUN_TEST(test_init_writes_config_and_calibration);
RUN_TEST(test_check_catches_lost_calibration);
RUN_TEST(test_readings_at_full_load);
RUN_TEST(test_reverse_current_is_negative);
RUN_TEST(test_bus_voltage_rounding);
RUN_TEST(test_pointer_is_rewritten_before_every_read);
RUN_TEST(test_read_vi_uses_two_transactions);
RUN_TEST(test_read_propagates_nak);
RUN_TEST(test_null_output_rejected);
RUN_TEST(test_reversed_chip_reads_positive_in_normal_flow);
RUN_TEST(test_reversed_only_affects_that_chip);
RUN_TEST(test_reversed_can_be_cleared_and_ignores_bad_addr);
return UNITY_END();
}