15 路侧键 + 5 路 EDL_EN + HUB_RST 在一条 24 位链上,内存里只有一份影子, 唯一写入点是 sr595_flush()。发送顺序 U33 → U22 → U21,每字节 QH 先发。 位表是从 .claude/pcb1-parts.json 的真实焊盘网络逐脚核回来的,不是照文档抄 —— 设计文档 §17.2 那张是 09-04 的两片版,§26.18 重排后 15 路里有 12 路对不上。 顺手把 §17.2 与 §8.2 里过期的两张表都改了。 sim 侧新增按 datasheet 建模的假移位链(SRCLK 上升沿移一格、RCLK 上升沿锁存), 只看线上真实发生的边 —— 对着驱动自己的影子变量断言等于自己证明自己。 四个注错进去的 bug(字节序反、OE# 先使能、安全影子写 0、位表写成算式)全被拦住。 上电顺序按「先锁存安全值、再使能 OE#」写死:三片 MR# 接死在 +3V3, 上电时移位寄存器内容不确定,先拉低 OE# 就是把随机值放到 15 个栅极上。 Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QYuExfgyYa6DzRRcUso8M8
373 行
17 KiB
C
373 行
17 KiB
C
/* A5:74HC595 24 位链 + 影子寄存器单一写入点。
|
||
两条验收断言(固件开发计划 §5 A5 那一行)都打了 ⭐:
|
||
① 改 CH1 的按键**不动** CH2~5 的位
|
||
② 字节顺序 **U33 → U22 → U21**
|
||
|
||
全部断言都对着 `sim_sr595_*`(按 datasheet 建模的假移位链,只看线上真实发生的
|
||
边),不对着驱动自己的影子变量 —— 后者是自己证明自己。 */
|
||
#include "hal_gpio.h"
|
||
#include "phonerail.h"
|
||
#include "sim_hal.h"
|
||
#include "sr595.h"
|
||
#include "unity.h"
|
||
|
||
void setUp(void) { sim_hal_reset(); }
|
||
void tearDown(void) {}
|
||
|
||
static void attach(void)
|
||
{
|
||
sim_sr595_attach(SR595_PIN_SER, SR595_PIN_SRCLK, SR595_PIN_RCLK, SR595_PIN_OE_N);
|
||
}
|
||
|
||
static void up(void)
|
||
{
|
||
attach();
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, sr595_init());
|
||
}
|
||
|
||
/* 链上某一位现在是什么(输出锁存器,不是影子)。 */
|
||
static int out_bit(int bit) { return (int)((sim_sr595_outputs() >> bit) & 1u); }
|
||
|
||
/* ---- 位表:只能查,不能算 ------------------------------------------------- */
|
||
|
||
/* 位表逐条对着 `.claude/pcb1-parts.json` 里的真实焊盘网络(设计文档 §26.18 C)。
|
||
这张表**不是任何公式**:CH2/CH3 在链尾那片 U33 上,CH5 的三个键还是倒着排的。 */
|
||
static void test_bit_table_matches_the_board(void)
|
||
{
|
||
/* CH1 -> U21.QB QC QD */
|
||
TEST_ASSERT_EQUAL_INT(1, sr595_key_bit(1, SR595_KEY_PWR));
|
||
TEST_ASSERT_EQUAL_INT(2, sr595_key_bit(1, SR595_KEY_VUP));
|
||
TEST_ASSERT_EQUAL_INT(3, sr595_key_bit(1, SR595_KEY_VDN));
|
||
/* CH2 -> U33.QD QB QC */
|
||
TEST_ASSERT_EQUAL_INT(19, sr595_key_bit(2, SR595_KEY_PWR));
|
||
TEST_ASSERT_EQUAL_INT(17, sr595_key_bit(2, SR595_KEY_VUP));
|
||
TEST_ASSERT_EQUAL_INT(18, sr595_key_bit(2, SR595_KEY_VDN));
|
||
/* CH3 -> U33.QG QF QE */
|
||
TEST_ASSERT_EQUAL_INT(22, sr595_key_bit(3, SR595_KEY_PWR));
|
||
TEST_ASSERT_EQUAL_INT(21, sr595_key_bit(3, SR595_KEY_VUP));
|
||
TEST_ASSERT_EQUAL_INT(20, sr595_key_bit(3, SR595_KEY_VDN));
|
||
/* CH4 -> U22.QB QC QD */
|
||
TEST_ASSERT_EQUAL_INT(9, sr595_key_bit(4, SR595_KEY_PWR));
|
||
TEST_ASSERT_EQUAL_INT(10, sr595_key_bit(4, SR595_KEY_VUP));
|
||
TEST_ASSERT_EQUAL_INT(11, sr595_key_bit(4, SR595_KEY_VDN));
|
||
/* CH5 -> U22.QH QG QF —— 倒着的 */
|
||
TEST_ASSERT_EQUAL_INT(15, sr595_key_bit(5, SR595_KEY_PWR));
|
||
TEST_ASSERT_EQUAL_INT(14, sr595_key_bit(5, SR595_KEY_VUP));
|
||
TEST_ASSERT_EQUAL_INT(13, sr595_key_bit(5, SR595_KEY_VDN));
|
||
|
||
TEST_ASSERT_EQUAL_INT(16, sr595_edl_bit(1)); /* U33.QA —— 只剩这一路还在 U33 */
|
||
TEST_ASSERT_EQUAL_INT(7, sr595_edl_bit(2)); /* U21.QH */
|
||
TEST_ASSERT_EQUAL_INT(6, sr595_edl_bit(3)); /* U21.QG */
|
||
TEST_ASSERT_EQUAL_INT(0, sr595_edl_bit(4)); /* U21.QA */
|
||
TEST_ASSERT_EQUAL_INT(5, sr595_edl_bit(5)); /* U21.QF */
|
||
}
|
||
|
||
/* 把「一定会被写出来的那个算式」按死:三键顺排 `bit = 3(rail−1) + key`。
|
||
⚠️ CH4 **碰巧**就是 9/10/11,和公式一模一样 —— 所以这里只能整体断言
|
||
「15 位里只有 CH4 那三位对得上」,不能逐路断言「都不等于公式」。
|
||
写成算式的症状是**按 CH1 的电源键按到了别路的音量键**:板子照样正常工作,
|
||
功能测试测不出来(§26.18)。 */
|
||
static void test_bit_table_is_not_a_formula(void)
|
||
{
|
||
int naive_hits = 0;
|
||
for (int rail = 1; rail <= PHONERAIL_RAIL_COUNT; rail++) {
|
||
for (int k = 0; k < SR595_KEY_COUNT; k++) {
|
||
if (sr595_key_bit(rail, (sr595_key_t)k) == 3 * (rail - 1) + k) {
|
||
naive_hits++;
|
||
}
|
||
}
|
||
}
|
||
TEST_ASSERT_EQUAL_INT_MESSAGE(3, naive_hits,
|
||
"15 位里只有 CH4 的三位碰巧和顺排公式重合");
|
||
|
||
for (int rail = 1; rail <= PHONERAIL_RAIL_COUNT; rail++) {
|
||
/* 每一路的三个键在表里**未必**是 PWR<VUP<VDN 的升序(CH3 / CH5 是倒着的) */
|
||
if (rail == 3 || rail == 5) {
|
||
TEST_ASSERT_TRUE(sr595_key_bit(rail, SR595_KEY_PWR) >
|
||
sr595_key_bit(rail, SR595_KEY_VDN));
|
||
}
|
||
/* 「U33 是 EDL 专用片、EDL_ENn 在它的第 n−1 位」—— 只剩 EDL_EN1 还对 */
|
||
if (rail >= 2) {
|
||
TEST_ASSERT_NOT_EQUAL_INT(16 + (rail - 1), sr595_edl_bit(rail));
|
||
}
|
||
}
|
||
|
||
/* CH2 / CH3 的六个键位全在链尾那片 U33(bit16~23)上,一个都不在 U21 */
|
||
for (int k = 0; k < SR595_KEY_COUNT; k++) {
|
||
TEST_ASSERT_TRUE(sr595_key_bit(2, (sr595_key_t)k) >= 16);
|
||
TEST_ASSERT_TRUE(sr595_key_bit(3, (sr595_key_t)k) >= 16);
|
||
}
|
||
}
|
||
|
||
/* 21 个信号占满 21 个有效位,一位不多一位不少,空脚一个都没被占。启动自检用。 */
|
||
static void test_map_ok(void)
|
||
{
|
||
TEST_ASSERT_TRUE(sr595_map_ok());
|
||
TEST_ASSERT_EQUAL_HEX32(0x00FFEEEFu, SR595_MASK_VALID); /* 去掉 bit4/8/12 */
|
||
|
||
uint32_t keys = sr595_all_keys_mask();
|
||
int n = 0;
|
||
for (int b = 0; b < SR595_BITS; b++) {
|
||
n += (int)((keys >> b) & 1u);
|
||
}
|
||
TEST_ASSERT_EQUAL_INT_MESSAGE(15, n, "15 路侧键");
|
||
/* 按键掩码里不许混进 EDL 与 HUB_RST */
|
||
TEST_ASSERT_EQUAL_HEX32(0, keys & ((uint32_t)1u << SR595_BIT_HUB_RST));
|
||
for (int rail = 1; rail <= PHONERAIL_RAIL_COUNT; rail++) {
|
||
TEST_ASSERT_EQUAL_HEX32(0, keys & ((uint32_t)1u << sr595_edl_bit(rail)));
|
||
}
|
||
}
|
||
|
||
/* ---- ② 字节顺序 ---------------------------------------------------------- */
|
||
|
||
/* ⭐ 验收主项二:一帧的字节顺序是 **U33 → U22 → U21**。
|
||
先移入的位走得最远,所以链尾那片的字节要先发。发反了 U21 的字节会停在 U33 上
|
||
—— 现象是「按 CH1 的电源键,CH3 的手机弹出了 EDL」。
|
||
这里靠的是假移位链自己按 datasheet 移的结果,不是驱动的影子变量。 */
|
||
static void test_byte_order_is_u33_u22_u21(void)
|
||
{
|
||
up();
|
||
|
||
/* 三片各给一个只有该片认得的图案,这样字节走错片一眼就看出来。 */
|
||
const uint32_t pattern = 0x00A1B2C3u & SR595_MASK_VALID;
|
||
sr595_modify(SR595_MASK_VALID, pattern);
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, sr595_flush());
|
||
|
||
TEST_ASSERT_EQUAL_HEX32(pattern, sim_sr595_outputs());
|
||
TEST_ASSERT_EQUAL_HEX32_MESSAGE(pattern & 0xFFu, sim_sr595_outputs() & 0xFFu,
|
||
"U21 = 影子的 bit0~7");
|
||
TEST_ASSERT_EQUAL_HEX32_MESSAGE((pattern >> 8) & 0xFFu, (sim_sr595_outputs() >> 8) & 0xFFu,
|
||
"U22 = 影子的 bit8~15");
|
||
TEST_ASSERT_EQUAL_HEX32_MESSAGE((pattern >> 16) & 0xFFu, (sim_sr595_outputs() >> 16) & 0xFFu,
|
||
"U33 = 影子的 bit16~23(链尾,字节要先发)");
|
||
|
||
/* 字节顺序反过来发的话,输出会变成字节序倒过来的那个值 —— 图案不对称,认得出来。 */
|
||
uint32_t reversed = ((pattern & 0xFFu) << 16) | (pattern & 0xFF00u) | ((pattern >> 16) & 0xFFu);
|
||
TEST_ASSERT_NOT_EQUAL_HEX32(reversed, sim_sr595_outputs());
|
||
}
|
||
|
||
/* 一帧正好 24 个 SRCLK 上升沿 + 1 个 RCLK 上升沿。
|
||
多打一个时钟整条链就转一格(**每一位都错开一位**,症状是十五个按键全乱套),
|
||
而只看最终电压电流是完全正常的。 */
|
||
static void test_one_frame_is_24_clocks_and_1_latch(void)
|
||
{
|
||
up();
|
||
uint32_t c0 = sim_sr595_clocks(), l0 = sim_sr595_latches();
|
||
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, sr595_write(sr595_key_mask(2), sr595_key_mask(2)));
|
||
TEST_ASSERT_EQUAL_UINT32(c0 + 24, sim_sr595_clocks());
|
||
TEST_ASSERT_EQUAL_UINT32(l0 + 1, sim_sr595_latches());
|
||
}
|
||
|
||
/* RCLK 打点之前输出一点不变 ⇒ 整帧是原子的,组合键不会先单按一个。
|
||
假移位链把「移位寄存器」和「输出锁存器」分开建模,正是为了能断言这一条。 */
|
||
static void test_frame_is_atomic(void)
|
||
{
|
||
up();
|
||
uint32_t before = sim_sr595_outputs();
|
||
|
||
/* 手动把一帧发一半:只移 8 位,不打 RCLK。 */
|
||
for (int i = 0; i < 8; i++) {
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_gpio_write(SR595_PIN_SER, 1));
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_gpio_write(SR595_PIN_SRCLK, 1));
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_gpio_write(SR595_PIN_SRCLK, 0));
|
||
}
|
||
TEST_ASSERT_NOT_EQUAL_HEX32(before, sim_sr595_shift()); /* 移位寄存器动了 */
|
||
TEST_ASSERT_EQUAL_HEX32(before, sim_sr595_outputs()); /* 输出一点没动 */
|
||
}
|
||
|
||
/* ---- ① 改一路不动别路 ---------------------------------------------------- */
|
||
|
||
/* ⭐ 验收主项一:改 CH1 的按键,CH2~5 的 12 位一位都不许动。
|
||
这是「全固件只有一个函数能写 595」的核心理由 —— 多处直接操作移位链的症状是
|
||
**另一台手机的按键被顺手清掉**,要两个动作正好撞在一起才复现。 */
|
||
static void test_changing_ch1_leaves_ch2_to_ch5_alone(void)
|
||
{
|
||
up();
|
||
|
||
/* 先让 CH2~5 各按住一个不同的键,作为「别人的现场」。 */
|
||
uint32_t others = 0;
|
||
others |= (uint32_t)1u << sr595_key_bit(2, SR595_KEY_PWR);
|
||
others |= (uint32_t)1u << sr595_key_bit(3, SR595_KEY_VUP);
|
||
others |= (uint32_t)1u << sr595_key_bit(4, SR595_KEY_VDN);
|
||
others |= (uint32_t)1u << sr595_key_bit(5, SR595_KEY_PWR);
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, sr595_write(others, others));
|
||
TEST_ASSERT_EQUAL_HEX32(others, sim_sr595_outputs() & sr595_all_keys_mask());
|
||
|
||
/* CH1 按下电源键 -> 松开。两次之后,别路的四位必须原封不动。 */
|
||
uint32_t k1_pwr = (uint32_t)1u << sr595_key_bit(1, SR595_KEY_PWR);
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, sr595_write(k1_pwr, k1_pwr));
|
||
TEST_ASSERT_EQUAL_HEX32_MESSAGE(others, sim_sr595_outputs() & ~sr595_key_mask(1) & sr595_all_keys_mask(),
|
||
"按下 CH1 动了别路的位");
|
||
TEST_ASSERT_EQUAL_INT(1, out_bit(sr595_key_bit(1, SR595_KEY_PWR)));
|
||
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, sr595_write(k1_pwr, 0));
|
||
TEST_ASSERT_EQUAL_HEX32_MESSAGE(others, sim_sr595_outputs() & ~sr595_key_mask(1) & sr595_all_keys_mask(),
|
||
"松开 CH1 动了别路的位");
|
||
TEST_ASSERT_EQUAL_INT(0, out_bit(sr595_key_bit(1, SR595_KEY_PWR)));
|
||
|
||
/* 顺带:HUB_RST 从头到尾没被别人顺手清掉。 */
|
||
TEST_ASSERT_EQUAL_INT(1, out_bit(SR595_BIT_HUB_RST));
|
||
}
|
||
|
||
/* 「一路全松开」用 sr595_key_mask(),同样不许碰别路,也不许碰本路的 EDL_EN。 */
|
||
static void test_releasing_one_rail_keeps_edl_and_others(void)
|
||
{
|
||
up();
|
||
|
||
uint32_t all_keys = sr595_all_keys_mask();
|
||
uint32_t edl3 = (uint32_t)1u << sr595_edl_bit(3);
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, sr595_write(all_keys | edl3, all_keys | edl3));
|
||
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, sr595_write(sr595_key_mask(3), 0));
|
||
TEST_ASSERT_EQUAL_HEX32(0, sim_sr595_outputs() & sr595_key_mask(3));
|
||
TEST_ASSERT_EQUAL_HEX32(all_keys & ~sr595_key_mask(3), sim_sr595_outputs() & all_keys);
|
||
TEST_ASSERT_EQUAL_INT_MESSAGE(1, out_bit(sr595_edl_bit(3)), "松开按键不该碰 EDL_EN3");
|
||
}
|
||
|
||
/* 组合键(VOL− + PWR)必须落在**同一帧**里:一次 sr595_write() 两位一起给。
|
||
分两次写会先单按一个键 —— EDL 的组合键对顺序是敏感的(需求 §10.6)。 */
|
||
static void test_combo_lands_in_one_frame(void)
|
||
{
|
||
up();
|
||
uint32_t l0 = sim_sr595_latches();
|
||
|
||
uint32_t combo = ((uint32_t)1u << sr595_key_bit(4, SR595_KEY_PWR)) |
|
||
((uint32_t)1u << sr595_key_bit(4, SR595_KEY_VDN));
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, sr595_write(combo, combo));
|
||
|
||
TEST_ASSERT_EQUAL_UINT32_MESSAGE(l0 + 1, sim_sr595_latches(), "组合键必须一帧发完");
|
||
TEST_ASSERT_EQUAL_HEX32(combo, sim_sr595_outputs() & sr595_key_mask(4));
|
||
}
|
||
|
||
/* ---- 上电顺序与 HUB_RST -------------------------------------------------- */
|
||
|
||
/* `HUB_RST` 是**低有效**(USB2517I 的 RESET_N,设计文档 §21.7)。
|
||
影子全 0 会把 Hub 一直摁在复位里 —— 现象是「插上枚举不出来」,
|
||
而五路手机的电压电流全都正常。所以上电安全值不是 0。 */
|
||
static void test_hub_rst_is_active_low_and_released_after_init(void)
|
||
{
|
||
up();
|
||
TEST_ASSERT_EQUAL_HEX32(SR595_SHADOW_SAFE, sr595_shadow());
|
||
TEST_ASSERT_EQUAL_INT_MESSAGE(1, out_bit(SR595_BIT_HUB_RST), "上电后 Hub 不能被摁在复位里");
|
||
|
||
sr595_set_hub_reset(true);
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, sr595_flush());
|
||
TEST_ASSERT_EQUAL_INT(0, out_bit(SR595_BIT_HUB_RST));
|
||
|
||
sr595_set_hub_reset(false);
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, sr595_flush());
|
||
TEST_ASSERT_EQUAL_INT(1, out_bit(SR595_BIT_HUB_RST));
|
||
|
||
/* 复位 Hub 期间 15 个按键一个都不许被按下去。 */
|
||
sr595_set_hub_reset(true);
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, sr595_flush());
|
||
TEST_ASSERT_EQUAL_HEX32(0, sim_sr595_outputs() & sr595_all_keys_mask());
|
||
}
|
||
|
||
/* 上电顺序:先锁存安全值,**再**使能 OE#。
|
||
换个个儿 = 把移位寄存器里的随机值放到 15 个栅极上,也就是五台手机的电源键
|
||
可能一起被按下去(设计文档 §8.1「复位安全三条」)。
|
||
假移位链 attach 时被塞了 0xA5A5A5,正是用来照出这个错的。 */
|
||
static void test_outputs_are_enabled_only_after_a_safe_latch(void)
|
||
{
|
||
attach();
|
||
TEST_ASSERT_EQUAL_INT_MESSAGE(1, sim_sr595_oe(), "上电时 OE# 由 R609 拉高");
|
||
TEST_ASSERT_FALSE(sim_sr595_enabled());
|
||
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, sr595_init());
|
||
|
||
TEST_ASSERT_TRUE_MESSAGE(sim_sr595_enabled(), "init 之后 OE# 必须被拉低");
|
||
TEST_ASSERT_EQUAL_INT(0, sim_sr595_oe());
|
||
TEST_ASSERT_TRUE_MESSAGE(sim_sr595_enable_latches() >= 1,
|
||
"OE# 拉低之前一帧都还没锁存过 —— 15 个栅极上是随机值");
|
||
TEST_ASSERT_EQUAL_HEX32_MESSAGE(SR595_SHADOW_SAFE, sim_sr595_enable_outputs(),
|
||
"使能那一刻链上不是安全值");
|
||
TEST_ASSERT_EQUAL_HEX32(0, sim_sr595_outputs() & sr595_all_keys_mask());
|
||
}
|
||
|
||
/* ---- 影子与刷新 ---------------------------------------------------------- */
|
||
|
||
/* 「改影子 -> 请求刷新」是两步:`sr595_modify()` 一个字节都不许发出去。 */
|
||
static void test_modify_does_not_touch_the_chain(void)
|
||
{
|
||
up();
|
||
uint32_t c0 = sim_sr595_clocks(), out0 = sim_sr595_outputs();
|
||
|
||
sr595_modify(sr595_key_mask(5), sr595_key_mask(5));
|
||
TEST_ASSERT_TRUE(sr595_dirty());
|
||
TEST_ASSERT_EQUAL_UINT32_MESSAGE(c0, sim_sr595_clocks(), "modify 不许碰总线");
|
||
TEST_ASSERT_EQUAL_HEX32(out0, sim_sr595_outputs());
|
||
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, sr595_flush());
|
||
TEST_ASSERT_FALSE(sr595_dirty());
|
||
TEST_ASSERT_EQUAL_HEX32(sr595_shadow(), sim_sr595_outputs());
|
||
}
|
||
|
||
/* 不脏就不空发一帧:24 个时钟 + 一次锁存不是免费的,而且真板子上每发一帧
|
||
都要占住那三根线。写一个和现在一样的值同样不算脏。 */
|
||
static void test_flush_is_a_noop_when_clean(void)
|
||
{
|
||
up();
|
||
uint32_t c0 = sim_sr595_clocks(), l0 = sim_sr595_latches();
|
||
|
||
TEST_ASSERT_FALSE(sr595_dirty());
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, sr595_flush());
|
||
TEST_ASSERT_EQUAL_UINT32(c0, sim_sr595_clocks());
|
||
TEST_ASSERT_EQUAL_UINT32(l0, sim_sr595_latches());
|
||
|
||
sr595_set_hub_reset(false); /* 已经是这个值了 */
|
||
TEST_ASSERT_FALSE(sr595_dirty());
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, sr595_flush());
|
||
TEST_ASSERT_EQUAL_UINT32(l0, sim_sr595_latches());
|
||
}
|
||
|
||
/* value 里 mask 以外的位不许漏进影子(`sr595_write(bit, 0xFFFFFFFF)` 这种写法)。 */
|
||
static void test_modify_only_touches_the_masked_bits(void)
|
||
{
|
||
up();
|
||
uint32_t k2 = (uint32_t)1u << sr595_key_bit(2, SR595_KEY_VUP);
|
||
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, sr595_write(k2, 0xFFFFFFFFu));
|
||
TEST_ASSERT_EQUAL_HEX32(SR595_SHADOW_SAFE | k2, sim_sr595_outputs());
|
||
}
|
||
|
||
/* 发送失败(把 SER 那根脚踢回输入,模拟「没配成输出就用」)要留着脏,
|
||
下一次 flush 重发**整帧**。丢了脏标志 = 链上和影子永久对不上,
|
||
而后面每一次 flush 都会「成功」。 */
|
||
static void test_failed_flush_stays_dirty(void)
|
||
{
|
||
up();
|
||
uint32_t k1 = (uint32_t)1u << sr595_key_bit(1, SR595_KEY_VDN);
|
||
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_gpio_config(SR595_PIN_SER, HAL_GPIO_INPUT));
|
||
sr595_modify(k1, k1);
|
||
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, sr595_flush());
|
||
TEST_ASSERT_TRUE_MESSAGE(sr595_dirty(), "发失败必须留着脏");
|
||
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_gpio_config(SR595_PIN_SER, HAL_GPIO_OUTPUT));
|
||
TEST_ASSERT_EQUAL_INT(HAL_OK, sr595_flush());
|
||
TEST_ASSERT_EQUAL_HEX32(SR595_SHADOW_SAFE | k1, sim_sr595_outputs());
|
||
}
|
||
|
||
int main(void)
|
||
{
|
||
UNITY_BEGIN();
|
||
RUN_TEST(test_bit_table_matches_the_board);
|
||
RUN_TEST(test_bit_table_is_not_a_formula);
|
||
RUN_TEST(test_map_ok);
|
||
RUN_TEST(test_byte_order_is_u33_u22_u21);
|
||
RUN_TEST(test_one_frame_is_24_clocks_and_1_latch);
|
||
RUN_TEST(test_frame_is_atomic);
|
||
RUN_TEST(test_changing_ch1_leaves_ch2_to_ch5_alone);
|
||
RUN_TEST(test_releasing_one_rail_keeps_edl_and_others);
|
||
RUN_TEST(test_combo_lands_in_one_frame);
|
||
RUN_TEST(test_hub_rst_is_active_low_and_released_after_init);
|
||
RUN_TEST(test_outputs_are_enabled_only_after_a_safe_latch);
|
||
RUN_TEST(test_modify_does_not_touch_the_chain);
|
||
RUN_TEST(test_flush_is_a_noop_when_clean);
|
||
RUN_TEST(test_modify_only_touches_the_masked_bits);
|
||
RUN_TEST(test_failed_flush_stays_dirty);
|
||
return UNITY_END();
|
||
}
|