2026-09-16 完成、当时未提交,这里补提交(原样,不含 09-27 上板的修改)。 - 器件层 / 拓扑层整体从 R1 换到 R2:TCA9548A 分通道、五路同址 INA226 0x40 + 输入侧 0x43、 刀片 PCA9555(VBUS_EN 挪到 P0.4)、CH224A 只读驱动、风扇闭环 t_thermal - 新增 i2cbus(mux 原子事务)、slot(刀片 I/O 门面)、host/qtest.sh - 74HC595 链退役(代码与测试保留) - 判据阈值与时间常量未动;19 个 ctest 全绿,idf.py build 通过 Co-Authored-By: Claude Opus 5.5 <[email protected]>
146 行
7.9 KiB
C
146 行
7.9 KiB
C
/* ⛔⛔ **R2 已退役:这条 24 位链在现在的板子上不存在。**
|
||
(2026-09-16。R2 方案 D9 用「每片刀片一颗 PCA9555」取代了它,
|
||
少 4 根跨板信号,而且复位态天然安全 —— PCA9555 上电是高阻输入,
|
||
配栅极 100 k 下拉 ⇒ 按键必然松开;595 那套要靠 `KEY_OE#` 上拉才做得到同一件事。)
|
||
|
||
文件和测试**保留**(它自洽、测试全绿、不碰任何板级事实),但:
|
||
· 没有任何生产代码 include 它,`rail_cfg` / `t_actuator` 都已经改走 `pca9555.h`;
|
||
· 下面那些 `SR595_PIN_*` 的 GPIO 号是 **R1 底板**的,与 R2 的 `board_pins.h` 冲突
|
||
(IO9/12/13/21 在 R2 上分别是 EFUSE_EN1/EN4/EN5 与 FAN_TACH_IN)——
|
||
**别拿它们去查重、也别照着改 board_pins.h**;
|
||
· 三个逻辑键位(PWR/VUP/VDN)已经搬到 `pca9555.h` 的 `pca9555_key_t`。
|
||
要改 R2 的按键,去 `pca9555.h` 与 `main/slot.h`,不要动这个文件。 */
|
||
/* 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 */
|