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
这个提交包含在:
@@ -5,5 +5,6 @@
|
||||
idf_component_register(
|
||||
SRCS "ina226.c"
|
||||
"dac63204.c"
|
||||
"sr595.c"
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES phonerail_hal)
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
/* 74HC595 ×3 —— 一条 24 位移位链,带全固件唯一的影子寄存器(需求 §10.6)。
|
||||
|
||||
链上挂着 **15 路侧键 + 5 路 EDL_EN + HUB_RST**,共 21 位有效、3 位空脚。
|
||||
写任何一位都要把整条链重发一遍,所以:
|
||||
|
||||
⚠️ **内存里只有一份 24 位影子值,只有本文件的 `sr595_flush()` 会碰移位链。**
|
||||
任何任务改按键 / EDL 都是「`sr595_modify()` 改影子 → `sr595_flush()` 请求刷新」。
|
||||
多处直接操作移位链的症状是**另一台手机的按键被顺手清掉**,而且要两个动作
|
||||
正好撞在一起才复现 —— 属于会拖很久的那类 bug。
|
||||
|
||||
`RCLK` 打点之前输出不变 ⇒ 整帧刷新是原子的,组合键(VOL− + PWR)必须
|
||||
**一次 `sr595_write()` 两位一起给**,不能先后两次。
|
||||
|
||||
⚠️ **不许用拉高 `OE#` 的方式「临时全松开」**(需求 §10.6):那会让 15 个栅极
|
||||
同时进高阻,靠的是每个栅极自己的 100 kΩ 下拉 —— 那是**复位路径**,不是正常
|
||||
控制路径。所以本头文件根本不提供「抬 OE#」的接口。
|
||||
|
||||
本驱动只经 hal_gpio_*,宿主机上由 sim 的移位链观测器(`sim_sr595_*`)全部测完。 */
|
||||
#ifndef SR595_H
|
||||
#define SR595_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "hal_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ---- 四根线(ESP32-S3 引脚,设计文档 §8.1 的表,§26.18 重排后) --------- */
|
||||
|
||||
#define SR595_PIN_SER 13 /* IO13 模组脚 21 → U21.DS(14) */
|
||||
#define SR595_PIN_SRCLK 12 /* IO12 模组脚 20 → 三片的 SRCLK(11) */
|
||||
#define SR595_PIN_RCLK 9 /* IO9 模组脚 17 → 三片的 RCLK(12) */
|
||||
#define SR595_PIN_OE_N 21 /* IO21 模组脚 23 → 三片的 OE#(13),板上另有 R609 100 kΩ 上拉 */
|
||||
|
||||
/* `MR#`(10) 三片都直接接 +3V3 —— 没有复位线,上电后移位寄存器内容是**不确定**的。
|
||||
所以 `sr595_init()` 必须先把安全影子锁存进去、**再**驱动 `OE#`。 */
|
||||
|
||||
/* ---- 24 位的编号 --------------------------------------------------------- */
|
||||
|
||||
/* 位号 = **链上的物理位置**:0 = U21.QA(离 ESP32 最近的第一级),23 = U33.QH(链尾)。
|
||||
一次移位 = 每一级往编号大的方向走一格,所以**先移入的位停在编号最大处**。 */
|
||||
#define SR595_CHIP_COUNT 3
|
||||
#define SR595_BITS 24
|
||||
#define SR595_MASK_ALL ((uint32_t)0x00FFFFFFu)
|
||||
|
||||
#define SR595_CHIP_U21 0 /* 影子的 bit0~7 ;P3B 页,板上 y≈50(中)*/
|
||||
#define SR595_CHIP_U22 1 /* 影子的 bit8~15 ;P3B 页,板上 y≈35(南)*/
|
||||
#define SR595_CHIP_U33 2 /* 影子的 bit16~23;**P6 页**,板上 y≈70(北)*/
|
||||
|
||||
/* 位分配(**2026-09-06 按 PCB 几何重排**,设计文档 §26.18 C;
|
||||
此处的值是从 `.claude/pcb1-parts.json` 的真实焊盘网络逐脚核对出来的):
|
||||
|
||||
U21 QA=EDL_EN4 QB=K1_PWR QC=K1_VUP QD=K1_VDN QE=空 QF=EDL_EN5 QG=EDL_EN3 QH=EDL_EN2
|
||||
U22 QA=空 QB=K4_PWR QC=K4_VUP QD=K4_VDN QE=空 QF=K5_VDN QG=K5_VUP QH=K5_PWR
|
||||
U33 QA=EDL_EN1 QB=K2_VUP QC=K2_VDN QD=K2_PWR QE=K3_VDN QF=K3_VUP QG=K3_PWR QH=HUB_RST
|
||||
|
||||
⚠️ **「K1/K2/K3 顺排在 U21、U33 是 EDL 专用片」是旧版**(设计文档 §17.2 的
|
||||
09-04 那张表),15 路里有 12 路对不上。三片本来就是一条链,谁在哪一位只是移位
|
||||
的字节序 ⇒ **搞错了板子照样正常工作,只是按 CH1 的电源键按到了 CH4 的音量键**,
|
||||
功能测试完全测不出来。查表,不要算。 */
|
||||
#define SR595_BIT_HUB_RST 23
|
||||
|
||||
/* 空脚(U21.QE / U22.QA / U22.QE)。写它们没有意义,`sr595_modify()` 直接 abort。 */
|
||||
#define SR595_MASK_UNUSED (((uint32_t)1u << 4) | ((uint32_t)1u << 8) | ((uint32_t)1u << 12))
|
||||
#define SR595_MASK_VALID (SR595_MASK_ALL & ~SR595_MASK_UNUSED)
|
||||
|
||||
typedef enum {
|
||||
SR595_KEY_PWR = 0,
|
||||
SR595_KEY_VUP,
|
||||
SR595_KEY_VDN,
|
||||
SR595_KEY_COUNT,
|
||||
} sr595_key_t;
|
||||
|
||||
/* rail 是 **1~5**,key 越界或 rail 越界一律 abort(那是调用方算错了位号)。 */
|
||||
int sr595_key_bit(int rail, sr595_key_t key);
|
||||
uint32_t sr595_key_mask(int rail); /* 该路三键的掩码,「一路全松开」用得上 */
|
||||
int sr595_edl_bit(int rail);
|
||||
|
||||
/* 15 路侧键的总掩码。安全巡检与「全松开」用。 */
|
||||
uint32_t sr595_all_keys_mask(void);
|
||||
|
||||
/* 21 个有效位互不重复、都落在 SR595_MASK_VALID 里、空脚一个都没被占。
|
||||
启动自检调一次(A7);返回 false = 位表被改坏了。 */
|
||||
bool sr595_map_ok(void);
|
||||
|
||||
/* ---- 上电安全影子 -------------------------------------------------------- */
|
||||
|
||||
/* ⚠️ **`HUB_RST` 是低有效**(USB2517I 的 `RESET_N`(43),设计文档 §21.7,
|
||||
板上 R716 10 kΩ 上拉)。影子位 = 1 才是「Hub 正常跑」,= 0 是**按住复位**。
|
||||
全 0 的影子会把 Hub 一直摁在复位里 —— 现象是「插上枚举不出来」,
|
||||
而五路手机的电压电流全都正常,看起来一点毛病没有。
|
||||
所以上电安全值不是 0,是这个: */
|
||||
#define SR595_SHADOW_SAFE ((uint32_t)1u << SR595_BIT_HUB_RST)
|
||||
|
||||
/* 复位是低有效,反相只在 `sr595_set_hub_reset()` 里出现这一次
|
||||
(和 `rail_mv_to_code()` 里那个减法同一个道理:多一处就多一处写反的机会)。 */
|
||||
void sr595_set_hub_reset(bool in_reset);
|
||||
|
||||
/* ---- 器件操作 ------------------------------------------------------------ */
|
||||
|
||||
/* 三步,顺序不能换(上电时移位寄存器内容不确定,`MR#` 又接死在 +3V3):
|
||||
① SER / SRCLK / RCLK 配成输出并置低;**`OE#` 这时还是输入**,
|
||||
靠板上 R609 上拉着 ⇒ 三片输出保持高阻,15 个栅极被各自的 100 kΩ 拉低
|
||||
② 把 `SR595_SHADOW_SAFE` 移进去并锁存
|
||||
③ 这才把 `OE#` 配成输出并拉低,输出使能
|
||||
②③ 换个个儿 = 上电瞬间把移位寄存器里的随机值放到 15 个栅极上,
|
||||
也就是**五台手机的电源键可能一起被按下去**。 */
|
||||
hal_err_t sr595_init(void);
|
||||
|
||||
uint32_t sr595_shadow(void); /* 当前影子(未必已经刷下去,看 sr595_dirty()) */
|
||||
bool sr595_dirty(void); /* 影子和链上的值不一致 */
|
||||
|
||||
/* 影子 = (影子 & ~mask) | (value & mask),并置脏。**不碰总线**。
|
||||
mask 越出 SR595_MASK_VALID(含碰到空脚)直接 abort。 */
|
||||
void sr595_modify(uint32_t mask, uint32_t value);
|
||||
|
||||
/* 把影子整帧发下去:U33 字节 → U22 字节 → U21 字节,每个字节 QH 先发,
|
||||
最后一个 RCLK 上升沿统一生效。不脏就直接返回 HAL_OK(不空发一帧)。
|
||||
没 init 过 → HAL_ERR_STATE。 */
|
||||
hal_err_t sr595_flush(void);
|
||||
|
||||
/* `sr595_modify()` + `sr595_flush()`。**组合键必须走它**——
|
||||
VOL− + PWR 要落在同一帧里,分两次写会先单按一个键。 */
|
||||
hal_err_t sr595_write(uint32_t mask, uint32_t value);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SR595_H */
|
||||
@@ -0,0 +1,226 @@
|
||||
/* 74HC595 ×3 的 24 位链 + 影子寄存器。契约见 sr595.h。 */
|
||||
#include "sr595.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "hal_gpio.h"
|
||||
|
||||
/* ---- 位表 ---------------------------------------------------------------- */
|
||||
|
||||
/* 从 `.claude/pcb1-parts.json` 的真实焊盘网络逐脚抄出来的(设计文档 §26.18 C)。
|
||||
位号 = 链上物理位置:0 = U21.QA … 23 = U33.QH。
|
||||
排布规矩:**每一路的三个键必须落在同一片的连续三脚上**(出芯片是一束线),
|
||||
所以三个键的位号在表里挨着,但**顺序每一路都不一样**(U22 上的 CH5 是倒着的)。 */
|
||||
static const int8_t k_key_bit[5][SR595_KEY_COUNT] = {
|
||||
/* PWR VUP VDN */
|
||||
/* CH1 */ { 1, 2, 3 }, /* U21.QB QC QD */
|
||||
/* CH2 */ { 19, 17, 18 }, /* U33.QD QB QC */
|
||||
/* CH3 */ { 22, 21, 20 }, /* U33.QG QF QE */
|
||||
/* CH4 */ { 9, 10, 11 }, /* U22.QB QC QD */
|
||||
/* CH5 */ { 15, 14, 13 }, /* U22.QH QG QF */
|
||||
};
|
||||
|
||||
/* EDL_EN1~5。⚠️ 只有 EDL_EN1 还在 U33(QA),其余四路搬到了 U21 上,
|
||||
而且**不是顺序的**:QA=EDL_EN4、QF=EDL_EN5、QG=EDL_EN3、QH=EDL_EN2。 */
|
||||
static const int8_t k_edl_bit[5] = {
|
||||
16, /* CH1 → U33.QA */
|
||||
7, /* CH2 → U21.QH */
|
||||
6, /* CH3 → U21.QG */
|
||||
0, /* CH4 → U21.QA */
|
||||
5, /* CH5 → U21.QF */
|
||||
};
|
||||
|
||||
int sr595_key_bit(int rail, sr595_key_t key)
|
||||
{
|
||||
assert(rail >= 1 && rail <= 5);
|
||||
assert((unsigned)key < (unsigned)SR595_KEY_COUNT); /* 枚举可能是无符号,别写 key >= 0 */
|
||||
return k_key_bit[rail - 1][key];
|
||||
}
|
||||
|
||||
uint32_t sr595_key_mask(int rail)
|
||||
{
|
||||
uint32_t m = 0;
|
||||
for (int k = 0; k < SR595_KEY_COUNT; k++) {
|
||||
m |= (uint32_t)1u << sr595_key_bit(rail, (sr595_key_t)k);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
int sr595_edl_bit(int rail)
|
||||
{
|
||||
assert(rail >= 1 && rail <= 5);
|
||||
return k_edl_bit[rail - 1];
|
||||
}
|
||||
|
||||
uint32_t sr595_all_keys_mask(void)
|
||||
{
|
||||
uint32_t m = 0;
|
||||
for (int rail = 1; rail <= 5; rail++) {
|
||||
m |= sr595_key_mask(rail);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
bool sr595_map_ok(void)
|
||||
{
|
||||
uint32_t seen = 0;
|
||||
int n = 0;
|
||||
|
||||
for (int rail = 1; rail <= 5; rail++) {
|
||||
for (int k = 0; k < SR595_KEY_COUNT; k++) {
|
||||
uint32_t b = (uint32_t)1u << k_key_bit[rail - 1][k];
|
||||
if ((b & SR595_MASK_VALID) == 0 || (seen & b) != 0) {
|
||||
return false; /* 落到空脚上,或者两个信号抢同一位 */
|
||||
}
|
||||
seen |= b;
|
||||
n++;
|
||||
}
|
||||
uint32_t e = (uint32_t)1u << k_edl_bit[rail - 1];
|
||||
if ((e & SR595_MASK_VALID) == 0 || (seen & e) != 0) {
|
||||
return false;
|
||||
}
|
||||
seen |= e;
|
||||
n++;
|
||||
}
|
||||
|
||||
uint32_t h = (uint32_t)1u << SR595_BIT_HUB_RST;
|
||||
if ((h & SR595_MASK_VALID) == 0 || (seen & h) != 0) {
|
||||
return false;
|
||||
}
|
||||
seen |= h;
|
||||
n++;
|
||||
|
||||
/* 21 个信号占满 21 个有效位,一位不多一位不少。 */
|
||||
return n == 21 && seen == SR595_MASK_VALID;
|
||||
}
|
||||
|
||||
/* ---- 影子 ---------------------------------------------------------------- */
|
||||
|
||||
static uint32_t s_shadow;
|
||||
static bool s_dirty;
|
||||
static bool s_inited;
|
||||
|
||||
uint32_t sr595_shadow(void) { return s_shadow; }
|
||||
bool sr595_dirty(void) { return s_dirty; }
|
||||
|
||||
void sr595_modify(uint32_t mask, uint32_t value)
|
||||
{
|
||||
/* 越出有效位 = 位号算错了(多半是把 rail 当下标又减了一次 1)。
|
||||
悄悄忽略的话,那一位会一直是 0,而「按键没反应」要到接上真手机才发现。 */
|
||||
assert((mask & ~SR595_MASK_VALID) == 0);
|
||||
|
||||
uint32_t next = (s_shadow & ~mask) | (value & mask);
|
||||
if (next != s_shadow) {
|
||||
s_shadow = next;
|
||||
s_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void sr595_set_hub_reset(bool in_reset)
|
||||
{
|
||||
/* 低有效:按住复位 = 输出 0。整个固件里这个取反只出现这一次。 */
|
||||
uint32_t bit = (uint32_t)1u << SR595_BIT_HUB_RST;
|
||||
sr595_modify(bit, in_reset ? 0u : bit);
|
||||
}
|
||||
|
||||
/* ---- 移位链 -------------------------------------------------------------- */
|
||||
|
||||
/* 先出错的那个错误码留住,后面照样把该发的边发完 —— 半帧数据留在链上比整帧发完更糟。 */
|
||||
static void keep(hal_err_t *first, hal_err_t e)
|
||||
{
|
||||
if (*first == HAL_OK) {
|
||||
*first = e;
|
||||
}
|
||||
}
|
||||
|
||||
static void clock_bit(hal_err_t *err, int bit)
|
||||
{
|
||||
keep(err, hal_gpio_write(SR595_PIN_SER, bit));
|
||||
keep(err, hal_gpio_write(SR595_PIN_SRCLK, 1)); /* 上升沿采样 SER */
|
||||
keep(err, hal_gpio_write(SR595_PIN_SRCLK, 0));
|
||||
}
|
||||
|
||||
/* 先移入的字节走到链尾 ⇒ 发送顺序 **U33 → U22 → U21**;
|
||||
每个字节里 QH(bit7)先发,同样因为先移入的位走得最远。 */
|
||||
static const uint8_t k_send_order[SR595_CHIP_COUNT] = {
|
||||
SR595_CHIP_U33,
|
||||
SR595_CHIP_U22,
|
||||
SR595_CHIP_U21,
|
||||
};
|
||||
|
||||
static hal_err_t shift_and_latch(uint32_t value)
|
||||
{
|
||||
hal_err_t err = HAL_OK;
|
||||
|
||||
for (int i = 0; i < SR595_CHIP_COUNT; i++) {
|
||||
uint8_t byte = (uint8_t)((value >> (8 * k_send_order[i])) & 0xFFu);
|
||||
for (int b = 7; b >= 0; b--) {
|
||||
clock_bit(&err, (byte >> b) & 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* RCLK 上升沿之前输出一点不变 ⇒ 整帧是原子的。 */
|
||||
keep(&err, hal_gpio_write(SR595_PIN_RCLK, 1));
|
||||
keep(&err, hal_gpio_write(SR595_PIN_RCLK, 0));
|
||||
return err;
|
||||
}
|
||||
|
||||
hal_err_t sr595_init(void)
|
||||
{
|
||||
hal_err_t err = HAL_OK;
|
||||
|
||||
s_inited = false;
|
||||
|
||||
/* ① 三根数据线配成输出并置低。`OE#` **故意还不碰** —— 板上 R609 把它拉在高位,
|
||||
三片输出保持高阻,15 个栅极被各自的 100 kΩ 拉低。 */
|
||||
keep(&err, hal_gpio_config(SR595_PIN_SER, HAL_GPIO_OUTPUT));
|
||||
keep(&err, hal_gpio_config(SR595_PIN_SRCLK, HAL_GPIO_OUTPUT));
|
||||
keep(&err, hal_gpio_config(SR595_PIN_RCLK, HAL_GPIO_OUTPUT));
|
||||
keep(&err, hal_gpio_write(SR595_PIN_SER, 0));
|
||||
keep(&err, hal_gpio_write(SR595_PIN_SRCLK, 0));
|
||||
keep(&err, hal_gpio_write(SR595_PIN_RCLK, 0));
|
||||
if (err != HAL_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
/* ② 安全影子先锁存进去(上电后移位寄存器内容不确定,`MR#` 接死在 +3V3,
|
||||
没有别的办法把它清干净)。 */
|
||||
s_shadow = SR595_SHADOW_SAFE;
|
||||
s_dirty = false;
|
||||
err = shift_and_latch(s_shadow);
|
||||
if (err != HAL_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
/* ③ 这才使能输出。②③ 换个个儿 = 上电瞬间把随机值放到 15 个栅极上。 */
|
||||
keep(&err, hal_gpio_config(SR595_PIN_OE_N, HAL_GPIO_OUTPUT));
|
||||
keep(&err, hal_gpio_write(SR595_PIN_OE_N, 0));
|
||||
if (err != HAL_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
s_inited = true;
|
||||
return HAL_OK;
|
||||
}
|
||||
|
||||
hal_err_t sr595_flush(void)
|
||||
{
|
||||
if (!s_inited) {
|
||||
return HAL_ERR_STATE;
|
||||
}
|
||||
if (!s_dirty) {
|
||||
return HAL_OK; /* 不空发一帧:链上已经是这个值了 */
|
||||
}
|
||||
|
||||
hal_err_t err = shift_and_latch(s_shadow);
|
||||
if (err == HAL_OK) {
|
||||
s_dirty = false; /* 发失败就留着脏,下一次 flush 会重发整帧 */
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
hal_err_t sr595_write(uint32_t mask, uint32_t value)
|
||||
{
|
||||
sr595_modify(mask, value);
|
||||
return sr595_flush();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户