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
227 行
6.8 KiB
C
227 行
6.8 KiB
C
/* 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();
|
||
}
|