/* 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(); }