固件: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]>
这个提交包含在:
@@ -1,77 +1,114 @@
|
||||
/* 按 datasheet 建模的假 PCA9555。宿主机专用。
|
||||
/* 按 datasheet 建模的假 PCA9555 ×5(R2:每片刀片一颗)。宿主机专用。
|
||||
|
||||
通用寄存器表(sim_i2c)能测「写下去的字节对不对」,但测不出 A6 真正要拦的那件事:
|
||||
**把一个焊死在地上的脚配成输出**。真板子上这既不报错也不 NAK,只是发烫。
|
||||
所以这里把三件真器件会做的事补上(见 sim_hal.h 那一节)。
|
||||
通用寄存器表(sim_i2c)能测「写下去的字节对不对」,但测不出这一层真正要拦的
|
||||
两件事:
|
||||
① **把一个焊死在地上的脚配成输出**(P0.5~7 / P1.4~7)——
|
||||
真板子上这既不报错也不 NAK,只是发烫;
|
||||
② **在输出锁存器还是 0xFF 的时候就把 Port0 配成输出** ——
|
||||
那一刻三个键全被按下 + EDL 短接 + VBUS 上电(pca9555.h 那个框)。
|
||||
② 在真板上的现象是「五台手机一起进了 Recovery」,而 I²C 从头到尾一切正常。
|
||||
|
||||
⚠️ 这个模型是用来证明**代码逻辑**对的。别拿它推翻设计文档 §17.2 的任何一条。 */
|
||||
⚠️ 这个模型是用来证明**代码逻辑**对的。别拿它推翻硬件设计的任何一条。 */
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "pca9555.h"
|
||||
#include "sim_hal.h"
|
||||
|
||||
/* 焊死在地上的备用脚(设计文档 §17.2 的注)。 */
|
||||
/* 焊死在地上的备用脚(`_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;
|
||||
uint32_t gnd_driven;
|
||||
uint32_t shorted;
|
||||
} sim_pca9555_t;
|
||||
} sim_pca_t;
|
||||
|
||||
static sim_pca9555_t s;
|
||||
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));
|
||||
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, int port)
|
||||
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)((s.ext[port] & cfg) | (out & (uint8_t)~cfg));
|
||||
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_grounded_pins(const sim_i2c_dev_t *d)
|
||||
/* 方向寄存器一变就查两件事(见文件头 ① ②)。 */
|
||||
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) {
|
||||
continue;
|
||||
}
|
||||
s.gnd_driven++;
|
||||
if ((driven & out) != 0) {
|
||||
s.shorted++; /* 真板子上这一刻开始冒烟 */
|
||||
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)
|
||||
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, port) != s.int_latch[port]) {
|
||||
if (!s.int_low) {
|
||||
s.int_low = true;
|
||||
s.int_asserts++;
|
||||
if (input_value(d, p, port) != p->int_latch[port]) {
|
||||
if (!p->int_low) {
|
||||
p->int_low = true;
|
||||
p->int_asserts++;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -80,34 +117,39 @@ static void update_int(const sim_i2c_dev_t *d)
|
||||
|
||||
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_grounded_pins(d);
|
||||
update_int(d);
|
||||
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, port);
|
||||
d->regs[PCA9555_REG_INPUT0 + port] = input_value(d, p, port);
|
||||
}
|
||||
/* 读输入端口 = 确认中断。读别的寄存器不清 INT#(真器件就是这个行为,
|
||||
也是「只靠中断会永远收不到下一个沿」的根源)。 */
|
||||
if (d->ptr == PCA9555_REG_INPUT0 || d->ptr == PCA9555_REG_INPUT1) {
|
||||
s.int_latch[0] = (uint8_t)d->regs[PCA9555_REG_INPUT0];
|
||||
s.int_latch[1] = (uint8_t)d->regs[PCA9555_REG_INPUT1];
|
||||
s.int_low = false;
|
||||
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(uint8_t addr)
|
||||
void sim_pca9555_attach_ch(int idx, int ch, uint8_t addr)
|
||||
{
|
||||
assert(!s.attached); /* 板上只有一片 */
|
||||
sim_i2c_dev_t *d = sim_i2c_add(addr, 1);
|
||||
assert(idx >= 0 && idx < SIM_PCA9555_MAX);
|
||||
assert(!s[idx].attached);
|
||||
sim_i2c_dev_t *d = sim_i2c_add_ch(ch, addr, 1);
|
||||
|
||||
s.attached = true;
|
||||
s.addr = addr;
|
||||
s.ext[0] = 0xFF; /* FLT# / PGOOD 都是开漏上拉,静息是高 */
|
||||
s.ext[1] = 0xFF;
|
||||
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 是本器件最阴的一条:配成输出的那一刻就输出高电平。 */
|
||||
@@ -118,45 +160,61 @@ void sim_pca9555_attach(uint8_t addr)
|
||||
d->regs[PCA9555_REG_POLINV0] = 0x00;
|
||||
d->regs[PCA9555_REG_POLINV1] = 0x00;
|
||||
|
||||
s.int_latch[0] = input_value(d, 0);
|
||||
s.int_latch[1] = input_value(d, 1);
|
||||
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 *must_dev(void)
|
||||
static sim_i2c_dev_t *dev_of(int idx)
|
||||
{
|
||||
assert(s.attached); /* 先 sim_pca9555_attach() */
|
||||
sim_i2c_dev_t *d = sim_i2c_dev(s.addr);
|
||||
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 port, uint8_t level)
|
||||
void sim_pca9555_drive(int idx, int port, uint8_t level)
|
||||
{
|
||||
assert(port == 0 || port == 1);
|
||||
s.ext[port] = level;
|
||||
update_int(must_dev());
|
||||
sim_pca_t *p = inst(idx);
|
||||
p->ext[port] = level;
|
||||
update_int(dev_of(idx), p);
|
||||
}
|
||||
|
||||
uint8_t sim_pca9555_config(int port)
|
||||
uint8_t sim_pca9555_config(int idx, int port)
|
||||
{
|
||||
assert(port == 0 || port == 1);
|
||||
return (uint8_t)must_dev()->regs[PCA9555_REG_CONFIG0 + port];
|
||||
return (uint8_t)dev_of(idx)->regs[PCA9555_REG_CONFIG0 + port];
|
||||
}
|
||||
|
||||
uint8_t sim_pca9555_output(int port)
|
||||
uint8_t sim_pca9555_output(int idx, int port)
|
||||
{
|
||||
assert(port == 0 || port == 1);
|
||||
return (uint8_t)must_dev()->regs[PCA9555_REG_OUTPUT0 + port];
|
||||
return (uint8_t)dev_of(idx)->regs[PCA9555_REG_OUTPUT0 + port];
|
||||
}
|
||||
|
||||
uint8_t sim_pca9555_polinv(int port)
|
||||
uint8_t sim_pca9555_polinv(int idx, int port)
|
||||
{
|
||||
assert(port == 0 || port == 1);
|
||||
return (uint8_t)must_dev()->regs[PCA9555_REG_POLINV0 + port];
|
||||
return (uint8_t)dev_of(idx)->regs[PCA9555_REG_POLINV0 + port];
|
||||
}
|
||||
|
||||
uint32_t sim_pca9555_gnd_driven(void) { return s.gnd_driven; }
|
||||
uint32_t sim_pca9555_shorted(void) { return s.shorted; }
|
||||
bool sim_pca9555_int_asserted(void) { return s.int_low; }
|
||||
uint32_t sim_pca9555_int_asserts(void) { return s.int_asserts; }
|
||||
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; }
|
||||
|
||||
在新工单中引用
屏蔽一个用户