文件
phonerail/firmware/components/phonerail_hal/sim/sim_pca9555.c
T
zhou yang和Claude Opus 5.5 b8ad69681f 固件:R2 迁移 R-0 ~ R-6(底板 + 5 片同构刀片)
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]>
2026-09-27 10:26:24 +08:00

221 行
7.8 KiB
C

/* 按 datasheet 建模的假 PCA9555 ×5(R2:每片刀片一颗)。宿主机专用。
通用寄存器表(sim_i2c)能测「写下去的字节对不对」,但测不出这一层真正要拦的
两件事:
① **把一个焊死在地上的脚配成输出**(P0.5~7 / P1.4~7)——
真板子上这既不报错也不 NAK,只是发烫;
② **在输出锁存器还是 0xFF 的时候就把 Port0 配成输出** ——
那一刻三个键全被按下 + EDL 短接 + VBUS 上电(pca9555.h 那个框)。
② 在真板上的现象是「五台手机一起进了 Recovery」,而 I²C 从头到尾一切正常。
⚠️ 这个模型是用来证明**代码逻辑**对的。别拿它推翻硬件设计的任何一条。 */
#include <assert.h>
#include <string.h>
#include "pca9555.h"
#include "sim_hal.h"
/* 焊死在地上的备用脚(`_blade_pcb_nets.json` 的 `U4` pin9~11 / pin17~20)。 */
static const uint8_t s_gnd_mask[2] = { PCA9555_P0_GND_MASK, PCA9555_P1_GND_MASK };
typedef struct {
bool attached;
int ch; /* 挂在 mux 的哪个通道后面 */
uint8_t addr;
uint8_t ext[2]; /* 外部世界驱动的电平;开漏上拉,静息 0xFF */
uint8_t int_latch[2];/* 上一次「被读走」时的输入值,用来判 INT# */
bool int_low;
uint32_t int_asserts;
} sim_pca_t;
static sim_pca_t s[SIM_PCA9555_MAX];
static uint32_t s_gnd_driven; /* 五片合计 */
static uint32_t s_shorted;
static uint32_t s_unsafe_enable;
void sim_pca9555_reset_internal(void); /* 只给 sim_hal_reset() 用 */
void sim_pca9555_reset_internal(void)
{
memset(s, 0, sizeof(s));
s_gnd_driven = 0;
s_shorted = 0;
s_unsafe_enable = 0;
}
static sim_pca_t *inst(int idx)
{
assert(idx >= 0 && idx < SIM_PCA9555_MAX);
assert(s[idx].attached); /* 先 sim_pca9555_attach_ch() */
return &s[idx];
}
/* 从 dev 反查是哪一片:地址相同,靠通道区分(真板上也是这么区分的)。 */
static sim_pca_t *from_dev(const sim_i2c_dev_t *d)
{
for (int i = 0; i < SIM_PCA9555_MAX; i++) {
if (s[i].attached && s[i].addr == d->addr && s[i].ch == d->bus) {
return &s[i];
}
}
assert(0 && "hook 挂在了一个没登记的假 PCA9555 上");
return &s[0];
}
/* 输入寄存器 = 逐位选择:配成输入的位取外部电平,配成输出的位取自己的输出锁存器。
接地脚无论配成什么,线上都是 0(它焊在地上,赢的永远是铜)。 */
static uint8_t input_value(const sim_i2c_dev_t *d, const sim_pca_t *p, int port)
{
const uint8_t cfg = (uint8_t)d->regs[PCA9555_REG_CONFIG0 + port];
const uint8_t out = (uint8_t)d->regs[PCA9555_REG_OUTPUT0 + port];
const uint8_t pol = (uint8_t)d->regs[PCA9555_REG_POLINV0 + port];
uint8_t v = (uint8_t)((p->ext[port] & cfg) | (out & (uint8_t)~cfg));
v &= (uint8_t)~s_gnd_mask[port]; /* 接地脚读回来永远是 0 */
return (uint8_t)(v ^ pol); /* 极性反转寄存器作用在**输入**上 */
}
/* 方向寄存器一变就查两件事(见文件头 ① ②)。 */
static void check_dangerous(const sim_i2c_dev_t *d)
{
for (int port = 0; port < 2; port++) {
const uint8_t cfg = (uint8_t)d->regs[PCA9555_REG_CONFIG0 + port];
const uint8_t out = (uint8_t)d->regs[PCA9555_REG_OUTPUT0 + port];
const uint8_t driven = (uint8_t)(s_gnd_mask[port] & (uint8_t)~cfg); /* cfg 0 = 输出 */
if (driven != 0) {
s_gnd_driven++;
if ((driven & out) != 0) {
s_shorted++; /* 真板子上这一刻开始冒烟 */
}
}
}
/* ② Port0 上被配成输出、而锁存器里是 1 的位:那一刻它就开始输出高电平。
本板 Port0 的五个输出(三个键 / EDL / VBUS)全都是**高有效**,
所以这等于「键被按下 + VBUS 上电」。 */
const uint8_t cfg0 = (uint8_t)d->regs[PCA9555_REG_CONFIG0];
const uint8_t out0 = (uint8_t)d->regs[PCA9555_REG_OUTPUT0];
const uint8_t as_output = (uint8_t)(PCA9555_P0_OUT_MASK & (uint8_t)~cfg0);
if ((as_output & out0) != 0) {
s_unsafe_enable++;
}
}
/* INT#:任何输入位相对「上次被读走的那一帧」发生变化就拉低,读走输入端口才释放。 */
static void update_int(const sim_i2c_dev_t *d, sim_pca_t *p)
{
for (int port = 0; port < 2; port++) {
if (input_value(d, p, port) != p->int_latch[port]) {
if (!p->int_low) {
p->int_low = true;
p->int_asserts++;
}
return;
}
}
}
static void hook(sim_i2c_dev_t *d, sim_i2c_ev_t ev)
{
sim_pca_t *p = from_dev(d);
if (ev == SIM_I2C_EV_POST_WRITE) {
check_dangerous(d);
update_int(d, p);
return;
}
/* PRE_READ:把两个输入寄存器合成好再让总线取走。 */
for (int port = 0; port < 2; port++) {
d->regs[PCA9555_REG_INPUT0 + port] = input_value(d, p, port);
}
/* 读输入端口 = 确认中断。读别的寄存器不清 INT#(真器件就是这个行为,
也是「只靠中断会永远收不到下一个沿」的根源)。 */
if (d->ptr == PCA9555_REG_INPUT0 || d->ptr == PCA9555_REG_INPUT1) {
p->int_latch[0] = (uint8_t)d->regs[PCA9555_REG_INPUT0];
p->int_latch[1] = (uint8_t)d->regs[PCA9555_REG_INPUT1];
p->int_low = false;
}
}
void sim_pca9555_attach_ch(int idx, int ch, uint8_t addr)
{
assert(idx >= 0 && idx < SIM_PCA9555_MAX);
assert(!s[idx].attached);
sim_i2c_dev_t *d = sim_i2c_add_ch(ch, addr, 1);
sim_pca_t *p = &s[idx];
p->attached = true;
p->ch = ch;
p->addr = addr;
p->ext[0] = 0xFF; /* 开漏上拉,静息是高 */
p->ext[1] = 0xFF;
/* 上电复位值(datasheet 表 6):方向全输入 0xFF,**输出锁存器 0xFF**,极性 0。
输出的 0xFF 是本器件最阴的一条:配成输出的那一刻就输出高电平。 */
d->regs[PCA9555_REG_CONFIG0] = 0xFF;
d->regs[PCA9555_REG_CONFIG1] = 0xFF;
d->regs[PCA9555_REG_OUTPUT0] = 0xFF;
d->regs[PCA9555_REG_OUTPUT1] = 0xFF;
d->regs[PCA9555_REG_POLINV0] = 0x00;
d->regs[PCA9555_REG_POLINV1] = 0x00;
p->int_latch[0] = input_value(d, p, 0);
p->int_latch[1] = input_value(d, p, 1);
d->hook = hook;
}
static sim_i2c_dev_t *dev_of(int idx)
{
sim_pca_t *p = inst(idx);
sim_i2c_dev_t *d = sim_i2c_dev_ch(p->ch, p->addr);
assert(d != NULL);
return d;
}
void sim_pca9555_drive(int idx, int port, uint8_t level)
{
assert(port == 0 || port == 1);
sim_pca_t *p = inst(idx);
p->ext[port] = level;
update_int(dev_of(idx), p);
}
uint8_t sim_pca9555_config(int idx, int port)
{
assert(port == 0 || port == 1);
return (uint8_t)dev_of(idx)->regs[PCA9555_REG_CONFIG0 + port];
}
uint8_t sim_pca9555_output(int idx, int port)
{
assert(port == 0 || port == 1);
return (uint8_t)dev_of(idx)->regs[PCA9555_REG_OUTPUT0 + port];
}
uint8_t sim_pca9555_polinv(int idx, int port)
{
assert(port == 0 || port == 1);
return (uint8_t)dev_of(idx)->regs[PCA9555_REG_POLINV0 + port];
}
int sim_pca9555_pin(int idx, int port, int bit)
{
assert(port == 0 || port == 1);
assert(bit >= 0 && bit < 8);
const sim_i2c_dev_t *d = dev_of(idx);
const uint8_t cfg = (uint8_t)d->regs[PCA9555_REG_CONFIG0 + port];
const uint8_t out = (uint8_t)d->regs[PCA9555_REG_OUTPUT0 + port];
if ((cfg & (uint8_t)(1u << bit)) != 0) {
return -1; /* 配成输入 = 高阻,不是 0 */
}
return (out & (uint8_t)(1u << bit)) != 0 ? 1 : 0;
}
uint32_t sim_pca9555_gnd_driven(void) { return s_gnd_driven; }
uint32_t sim_pca9555_shorted(void) { return s_shorted; }
uint32_t sim_pca9555_unsafe_enable(void) { return s_unsafe_enable; }
bool sim_pca9555_int_asserted(int idx) { return inst(idx)->int_low; }
uint32_t sim_pca9555_int_asserts(int idx) { return inst(idx)->int_asserts; }