A5:74HC595 24 位链 + 影子寄存器单一写入点
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
这个提交包含在:
@@ -15,6 +15,11 @@ static pin_t s_pin[SIM_GPIO_PINS];
|
||||
|
||||
void sim_gpio_reset_internal(void); /* 只给 sim_hal_reset() 用 */
|
||||
|
||||
/* 假 74HC595 链的窃听口(sim_sr595.c)。没 attach 过就是个空动作。
|
||||
放在这里而不是让测试自己数边:移位链要看的是**电平的跳变**,
|
||||
而 hal_gpio_write() 写同一个值也会计一次 writes。 */
|
||||
void sim_sr595_gpio_hook(int pin, int level);
|
||||
|
||||
void sim_gpio_reset_internal(void) { memset(s_pin, 0, sizeof(s_pin)); }
|
||||
|
||||
static bool valid(int pin) { return pin >= 0 && pin < SIM_GPIO_PINS; }
|
||||
@@ -30,6 +35,12 @@ hal_err_t hal_gpio_config(int pin, hal_gpio_mode_t mode)
|
||||
/* 上拉输入没人驱动时读回高电平 —— 五路 FLT/PG 都是开漏 + 上拉,
|
||||
「没有故障」在假板子上也必须是 1,不然测试会把默认 0 当成一直有故障。 */
|
||||
p->level = (mode == HAL_GPIO_INPUT_PULLUP) ? 1 : 0;
|
||||
/* 配成输出的那一刻引脚就开始驱动了(ESP-IDF 的 gpio_config 也是这个行为,
|
||||
输出寄存器复位值 0)。`KEY_OE#` 正是靠这一条:配成输出 = 输出立刻使能,
|
||||
所以 sr595_init() 必须等安全值锁存完了再配它。 */
|
||||
if (mode == HAL_GPIO_OUTPUT) {
|
||||
sim_sr595_gpio_hook(pin, p->level);
|
||||
}
|
||||
return HAL_OK;
|
||||
}
|
||||
|
||||
@@ -40,6 +51,7 @@ hal_err_t hal_gpio_write(int pin, int level)
|
||||
}
|
||||
s_pin[pin].level = level ? 1 : 0;
|
||||
s_pin[pin].writes++;
|
||||
sim_sr595_gpio_hook(pin, s_pin[pin].level);
|
||||
return HAL_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
void sim_i2c_reset_internal(void);
|
||||
void sim_gpio_reset_internal(void);
|
||||
void sim_sr595_reset_internal(void);
|
||||
void sim_pwm_reset_internal(void);
|
||||
void sim_time_reset_internal(void);
|
||||
|
||||
@@ -11,6 +12,7 @@ void sim_hal_reset(void)
|
||||
{
|
||||
sim_i2c_reset_internal();
|
||||
sim_gpio_reset_internal();
|
||||
sim_sr595_reset_internal();
|
||||
sim_pwm_reset_internal();
|
||||
sim_time_reset_internal();
|
||||
}
|
||||
|
||||
@@ -66,6 +66,31 @@ int sim_gpio_level(int pin);
|
||||
/* 被 hal_gpio_write() 写成功过几次。断言「改 CH1 没动 CH2~5」用得上(A5)。 */
|
||||
uint32_t sim_gpio_writes(int pin);
|
||||
|
||||
/* ---------------- 74HC595 ×3 的 24 位链 ---------------- */
|
||||
|
||||
/* 按 datasheet 建模的假移位链,**只看线上真实发生的边**(SRCLK / RCLK 上升沿)。
|
||||
对着驱动自己的影子变量断言等于自己证明自己 —— 字节顺序发反、每帧多打一个时钟、
|
||||
RCLK 打早了,影子变量一律看不出来。位号与 sr595.h 一致:0 = U21.QA,23 = U33.QH。
|
||||
|
||||
attach 之后移位寄存器与输出锁存器被塞成 0xA5A5A5:真板子上电时它们的内容
|
||||
**不确定**(`MR#` 接死在 +3V3,没有复位线),驱动必须先锁存安全值再使能 OE#。 */
|
||||
void sim_sr595_attach(int ser_pin, int srclk_pin, int rclk_pin, int oe_pin);
|
||||
|
||||
uint32_t sim_sr595_outputs(void); /* 输出锁存器(不管 OE# 是高是低) */
|
||||
uint32_t sim_sr595_shift(void); /* 移位寄存器当前内容(还没锁存的那一份) */
|
||||
uint32_t sim_sr595_clocks(void); /* SRCLK 上升沿总数:一帧应当正好 +24 */
|
||||
uint32_t sim_sr595_latches(void); /* RCLK 上升沿总数:一帧应当正好 +1 */
|
||||
int sim_sr595_oe(void); /* OE# 当前电平。初值 1 —— 板上 R609 拉着 */
|
||||
|
||||
/* OE# 第一次被拉低那一刻的现场。断言「输出使能之前已经锁存过安全值」用:
|
||||
驱动要是把顺序换了,enable_latches 会是 0、enable_outputs 会是那坨 0xA5A5A5。 */
|
||||
bool sim_sr595_enabled(void);
|
||||
uint32_t sim_sr595_enable_latches(void);
|
||||
uint32_t sim_sr595_enable_outputs(void);
|
||||
|
||||
/* 某一位在栅极上的实际电平;OE# 为高时返回 -1(高阻,不是 0)。 */
|
||||
int sim_sr595_pin(int bit);
|
||||
|
||||
/* ---------------- PWM ---------------- */
|
||||
|
||||
int sim_pwm_pin(int ch); /* 未配置返回 -1 */
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/* 假的 74HC595 ×3 移位链 —— 挂在 GPIO sim 上,只看**线上真实发生的边**。
|
||||
|
||||
为什么要这么一个东西:A5 的两条验收断言(改一路不动别路、字节顺序 U33→U22→U21)
|
||||
如果只对着驱动自己的影子变量断言,那是**自己证明自己** —— 字节顺序发反了、
|
||||
每帧多打一个时钟、RCLK 在数据发完之前就打了,影子变量一律看不出来。
|
||||
所以这里按 datasheet 建模:SRCLK 上升沿移一格,RCLK 上升沿把移位寄存器抄进输出锁存器。
|
||||
|
||||
24 级的编号与 sr595.h 一致:0 = U21.QA(DS 进来的第一级),23 = U33.QH(链尾)。 */
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "sim_hal.h"
|
||||
|
||||
typedef struct {
|
||||
bool attached;
|
||||
int ser_pin, srclk_pin, rclk_pin, oe_pin;
|
||||
int ser, srclk, rclk, oe; /* 当前电平;oe 初值 1 = 板上 R609 拉着 */
|
||||
uint32_t shift; /* 移位寄存器(未锁存) */
|
||||
uint32_t out; /* 输出锁存器 */
|
||||
uint32_t clocks; /* SRCLK 上升沿总数 */
|
||||
uint32_t latches; /* RCLK 上升沿总数 */
|
||||
bool enabled_once;
|
||||
uint32_t enable_latches; /* OE# 第一次被拉低时,已经锁存过几次 */
|
||||
uint32_t enable_out; /* OE# 第一次被拉低那一刻,输出锁存器里是什么 */
|
||||
} chain_t;
|
||||
|
||||
static chain_t s_ch;
|
||||
|
||||
void sim_sr595_reset_internal(void); /* 只给 sim_hal_reset() 用 */
|
||||
void sim_sr595_gpio_hook(int pin, int level);
|
||||
|
||||
void sim_sr595_reset_internal(void) { memset(&s_ch, 0, sizeof(s_ch)); }
|
||||
|
||||
void sim_sr595_attach(int ser_pin, int srclk_pin, int rclk_pin, int oe_pin)
|
||||
{
|
||||
memset(&s_ch, 0, sizeof(s_ch));
|
||||
s_ch.attached = true;
|
||||
s_ch.ser_pin = ser_pin;
|
||||
s_ch.srclk_pin = srclk_pin;
|
||||
s_ch.rclk_pin = rclk_pin;
|
||||
s_ch.oe_pin = oe_pin;
|
||||
/* `OE#` 上电就被 R609 拉高 —— 高阻,不是 0。 */
|
||||
s_ch.oe = 1;
|
||||
/* 上电后移位寄存器与输出锁存器的内容**不确定**(`MR#` 接死在 +3V3,没有复位线)。
|
||||
这里塞一个显眼的非零值:驱动要是先使能 OE# 再锁存安全值,
|
||||
`sim_sr595_enable_outputs()` 就会把这坨脏东西原样报出来。 */
|
||||
s_ch.shift = 0x00A5A5A5u & 0x00FFFFFFu;
|
||||
s_ch.out = s_ch.shift;
|
||||
}
|
||||
|
||||
void sim_sr595_gpio_hook(int pin, int level)
|
||||
{
|
||||
if (!s_ch.attached) {
|
||||
return;
|
||||
}
|
||||
level = level ? 1 : 0;
|
||||
|
||||
if (pin == s_ch.ser_pin) {
|
||||
s_ch.ser = level;
|
||||
} else if (pin == s_ch.srclk_pin) {
|
||||
if (level == 1 && s_ch.srclk == 0) { /* 上升沿:整条链往编号大的方向挪一格 */
|
||||
s_ch.shift = ((s_ch.shift << 1) | (uint32_t)s_ch.ser) & 0x00FFFFFFu;
|
||||
s_ch.clocks++;
|
||||
}
|
||||
s_ch.srclk = level;
|
||||
} else if (pin == s_ch.rclk_pin) {
|
||||
if (level == 1 && s_ch.rclk == 0) {
|
||||
s_ch.out = s_ch.shift;
|
||||
s_ch.latches++;
|
||||
}
|
||||
s_ch.rclk = level;
|
||||
} else if (pin == s_ch.oe_pin) {
|
||||
if (level == 0 && s_ch.oe != 0 && !s_ch.enabled_once) {
|
||||
s_ch.enabled_once = true;
|
||||
s_ch.enable_latches = s_ch.latches;
|
||||
s_ch.enable_out = s_ch.out;
|
||||
}
|
||||
s_ch.oe = level;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t sim_sr595_outputs(void) { return s_ch.out; }
|
||||
uint32_t sim_sr595_shift(void) { return s_ch.shift; }
|
||||
uint32_t sim_sr595_clocks(void) { return s_ch.clocks; }
|
||||
uint32_t sim_sr595_latches(void) { return s_ch.latches; }
|
||||
int sim_sr595_oe(void) { return s_ch.oe; }
|
||||
bool sim_sr595_enabled(void) { return s_ch.enabled_once; }
|
||||
uint32_t sim_sr595_enable_latches(void) { return s_ch.enable_latches; }
|
||||
uint32_t sim_sr595_enable_outputs(void) { return s_ch.enable_out; }
|
||||
|
||||
int sim_sr595_pin(int bit)
|
||||
{
|
||||
assert(bit >= 0 && bit < 24);
|
||||
if (s_ch.oe) {
|
||||
return -1; /* 高阻。栅极侧靠自己的 100 kΩ 下拉,HUB_RST 侧靠 R716 上拉 */
|
||||
}
|
||||
return (int)((s_ch.out >> bit) & 1u);
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户