固件: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]>
这个提交包含在:
@@ -33,9 +33,10 @@ void sim_hal_reboot(void);
|
||||
|
||||
/* ---------------- I²C ---------------- */
|
||||
|
||||
#define SIM_I2C_MAX_DEV 16
|
||||
#define SIM_I2C_MAX_DEV 24 /* R2:主干 4 + 五片刀片各 2 = 14,留些余量 */
|
||||
#define SIM_I2C_REGS 256
|
||||
#define SIM_I2C_LOG_BYTES 8
|
||||
#define SIM_I2C_MUX_CH 8 /* TCA9548A 的通道数 */
|
||||
|
||||
/* 假器件的窃听事件。通用寄存器表模拟不了「配成输出就对地短路」这种事,
|
||||
所以按 datasheet 建模的假器件(sim_pca9555)挂一个钩子进来。 */
|
||||
@@ -49,7 +50,11 @@ typedef void (*sim_i2c_hook_t)(struct sim_i2c_dev *d, sim_i2c_ev_t ev);
|
||||
|
||||
typedef struct sim_i2c_dev {
|
||||
uint8_t addr;
|
||||
uint8_t reg_width; /* 1 或 2 字节。2 字节按**大端**——INA226 / DAC63204 / LM75 都是 */
|
||||
/* **R2**:-1 = 挂在主干上;0~7 = 挂在 mux 的那个通道后面。
|
||||
通道关着的时候它看不见(事务一律 NAK),这正是「选错通道」能被测出来的原因。 */
|
||||
int bus;
|
||||
bool is_mux; /* 这颗就是 TCA9548A 自己:写它是**一个裸字节**,不带寄存器号 */
|
||||
uint8_t reg_width; /* 1 或 2 字节。2 字节按**大端**——INA226 / DAC63204 都是 */
|
||||
bool present; /* false = 器件失联(A15 降级矩阵用),事务一律 NAK */
|
||||
int nak_left; /* >0:接下来 n 次事务 NAK 后自动恢复;<0:一直 NAK;0:正常 */
|
||||
uint16_t regs[SIM_I2C_REGS];
|
||||
@@ -61,17 +66,38 @@ typedef struct sim_i2c_dev {
|
||||
sim_i2c_hook_t hook; /* NULL = 就是一张寄存器表 */
|
||||
} sim_i2c_dev_t;
|
||||
|
||||
/* 往假总线上挂一个器件。地址重复或超出 16 个会 abort(测试写错了,不该悄悄过)。 */
|
||||
/* 往假总线的**主干**上挂一个器件。同一条支路上地址重复会 abort(测试写错了)。 */
|
||||
sim_i2c_dev_t *sim_i2c_add(uint8_t addr, uint8_t reg_width);
|
||||
sim_i2c_dev_t *sim_i2c_dev(uint8_t addr); /* 没挂就返回 NULL */
|
||||
sim_i2c_dev_t *sim_i2c_dev(uint8_t addr); /* 主干上的那一个,没挂返回 NULL */
|
||||
|
||||
/* 往 mux 的某个通道后面挂一个器件(**R2**:五片刀片可以用同一个地址)。 */
|
||||
sim_i2c_dev_t *sim_i2c_add_ch(int ch, uint8_t addr, uint8_t reg_width);
|
||||
sim_i2c_dev_t *sim_i2c_dev_ch(int ch, uint8_t addr);
|
||||
|
||||
/* 挂那颗假 TCA9548A。`rst_pin` 是 `MUX_RST#` 接的 GPIO(低有效)——
|
||||
固件拉低它的那一刻,假器件的通道**全部关掉**,和真器件一样。 */
|
||||
void sim_i2c_add_mux(uint8_t addr, int rst_pin);
|
||||
|
||||
int sim_i2c_mux_ch(void); /* 当前通道:-1 全关 / 0~7 / **-2 同时开了多路** */
|
||||
uint8_t sim_i2c_mux_mask(void); /* 控制字原样 */
|
||||
uint32_t sim_i2c_mux_resets(void);
|
||||
|
||||
/* 同一个地址在**当前可见范围**里出现了不止一次的次数。
|
||||
真板上那是两片一起应答、读回来是线与(数值正常、还很稳),
|
||||
所以这个计数必须恒为 0 —— 非 0 就是通道开重了。 */
|
||||
uint32_t sim_i2c_ambiguous(void);
|
||||
|
||||
/* 预置 / 回读寄存器。地址没挂过会 abort。 */
|
||||
void sim_i2c_set_reg(uint8_t addr, uint8_t reg, uint16_t val);
|
||||
uint16_t sim_i2c_get_reg(uint8_t addr, uint8_t reg);
|
||||
void sim_i2c_set_reg_ch(int ch, uint8_t addr, uint8_t reg, uint16_t val);
|
||||
uint16_t sim_i2c_get_reg_ch(int ch, uint8_t addr, uint8_t reg);
|
||||
|
||||
/* 故障注入。count>0:接下来 count 次事务 NAK;count<0:一直 NAK;0:清除。 */
|
||||
void sim_i2c_inject_nak(uint8_t addr, int count);
|
||||
void sim_i2c_set_present(uint8_t addr, bool present);
|
||||
void sim_i2c_inject_nak_ch(int ch, uint8_t addr, int count);
|
||||
void sim_i2c_set_present_ch(int ch, uint8_t addr, bool present);
|
||||
|
||||
/* **假总线上的事务是要花时间的**(默认开):每次事务把虚拟时钟往前拨
|
||||
`hal_i2c_txn_bits() / 频率` 那么多微秒。没有这一条,t_safety 的一个周期在 sim 里
|
||||
@@ -126,31 +152,47 @@ uint32_t sim_sr595_enable_outputs(void);
|
||||
/* 某一位在栅极上的实际电平;OE# 为高时返回 -1(高阻,不是 0)。 */
|
||||
int sim_sr595_pin(int bit);
|
||||
|
||||
/* ---------------- PCA9555(按 datasheet 建模的假器件) ---------------- */
|
||||
/* ---------------- PCA9555 ×5(按 datasheet 建模的假器件) ---------------- */
|
||||
|
||||
/* 通用寄存器表证明不了 A6 的核心判据。这个模型只多做三件真器件会做的事:
|
||||
/* 通用寄存器表证明不了这一层的核心判据。这个模型只多做三件真器件会做的事:
|
||||
① **Output Port 复位值 0xFF** —— 所以把一个脚配成输出,它下一刻就输出高;
|
||||
② 输入寄存器按位合成:配成输入的位读**外部电平**,配成输出的位读**自己的输出**;
|
||||
③ 四个备用脚 P0_7 / P1_5~7 **焊死在地上** —— 读永远是 0,被驱动成高就是短路。
|
||||
③ 是这块板子上「配错方向」唯一会留下痕迹的地方:真板子上它既不报错也不 NAK。 */
|
||||
void sim_pca9555_attach(uint8_t addr);
|
||||
③ 备用脚 P0.5~7 / P1.4~7 **焊死在地上** —— 读永远是 0,被驱动成高就是短路。
|
||||
③ 是这块板子上「配错方向」唯一会留下痕迹的地方:真板子上它既不报错也不 NAK。
|
||||
|
||||
**R2**:五片刀片各一颗,同地址、不同 mux 通道。`idx` 是 0~4,
|
||||
与 `rail - 1` 对应 —— 但那是测试自己的约定,假器件只认 attach 的顺序。 */
|
||||
#define SIM_PCA9555_MAX 5
|
||||
|
||||
void sim_pca9555_attach_ch(int idx, int ch, uint8_t addr);
|
||||
|
||||
/* 外部世界把某个端口的 8 根线驱动成 level(接地脚不受影响,恒 0)。
|
||||
FLT#/PGOOD 都是开漏上拉,所以「什么都没发生」的静息值是 0xFF。 */
|
||||
void sim_pca9555_drive(int port, uint8_t level);
|
||||
void sim_pca9555_drive(int idx, int port, uint8_t level);
|
||||
|
||||
uint8_t sim_pca9555_config(int port); /* 方向寄存器现值,1 = 输入 */
|
||||
uint8_t sim_pca9555_output(int port); /* 输出锁存器现值(复位 0xFF) */
|
||||
uint8_t sim_pca9555_polinv(int port);
|
||||
uint8_t sim_pca9555_config(int idx, int port); /* 方向寄存器现值,1 = 输入 */
|
||||
uint8_t sim_pca9555_output(int idx, int port); /* 输出锁存器现值(复位 0xFF) */
|
||||
uint8_t sim_pca9555_polinv(int idx, int port);
|
||||
|
||||
/* 接地脚被配成输出过几次(不管输出高低,配了就算 —— 那已经是在对地灌电流)。 */
|
||||
/* 某个输出脚**在栅极上的实际电平**:配成输入时返回 -1(高阻),
|
||||
配成输出时返回锁存器里那一位。断言「键真的被按下了」用这个,
|
||||
不要去读驱动自己的影子(那是自己证明自己)。 */
|
||||
int sim_pca9555_pin(int idx, int port, int bit);
|
||||
|
||||
/* 接地脚被配成输出过几次(不管输出高低,配了就算 —— 那已经是在对地灌电流)。
|
||||
**五片合计**:这个数必须恒为 0,是哪一片犯的都一样严重。 */
|
||||
uint32_t sim_pca9555_gnd_driven(void);
|
||||
/* 其中真的输出了高电平、也就是**直接对地短路**过几次。 */
|
||||
uint32_t sim_pca9555_shorted(void);
|
||||
|
||||
/* ⭐ 「方向先于输出」那条规矩的拦网:某一片在**输出锁存器还是 0xFF 的时候**
|
||||
就把 Port0 配成了输出的次数。真板上那一刻五路的键全被按下 + VBUS 全部上电。
|
||||
必须恒为 0。 */
|
||||
uint32_t sim_pca9555_unsafe_enable(void);
|
||||
|
||||
/* INT# 现在是不是被拉低(低有效)。输入变化拉低,**读走输入端口才释放**。 */
|
||||
bool sim_pca9555_int_asserted(void);
|
||||
uint32_t sim_pca9555_int_asserts(void); /* 累计拉低次数 */
|
||||
bool sim_pca9555_int_asserted(int idx);
|
||||
uint32_t sim_pca9555_int_asserts(int idx); /* 累计拉低次数 */
|
||||
|
||||
/* ---------------- PWM ---------------- */
|
||||
|
||||
@@ -158,6 +200,32 @@ int sim_pwm_pin(int ch); /* 未配置返回 -1 */
|
||||
uint32_t sim_pwm_freq(int ch); /* 未配置返回 0 */
|
||||
int sim_pwm_permille(int ch); /* 未配置返回 -1 */
|
||||
|
||||
/* ---------------- 假风扇 ×2(A16 / TR-6 的 TACH 采样窗口) ---------------- */
|
||||
|
||||
/* 按板子的接法建模的假风扇:**一路 PWM 喂两只**、一个 12V 总开关、两路独立测速。
|
||||
它比「一个转速变量」多做四件真板子会做的事,每一件都是一条判据的靠山:
|
||||
① `FAN_EN` 是低电平就**一圈都不转**(复位态就是这样,固件不主动开就没风);
|
||||
② PWM 反相(板上 Q6 低边管):GPIO 占空比 d ⇒ 风扇侧 `1000 − d`
|
||||
—— 固件把反相写反时,这里的转速会随温度**下降**;
|
||||
③ **可选的「关断期无脉冲」**(`sim_fan_set_gated`,三线低边调速的行为):
|
||||
占空比 40 % 就只有 40 % 的时间在吐脉冲。TR-6 那条误报全靠它才摆得出来;
|
||||
④ 0 % PWM 仍转 `rpm_min`(设计文档 §22.6:四线风扇 0 % 下仍 3300 RPM)。
|
||||
|
||||
边沿数是按**虚拟时钟**积分出来的,积分在读 `sim_fan_edges()/sim_fan_rpm()`、
|
||||
写 `hal_pwm_set_permille()`、以及 `sim_fan_set_*()` 时结算。
|
||||
⚠️ 改 `FAN_EN` 引脚之前请先读一次 `sim_fan_edges()`:模型不监视 GPIO 的写,
|
||||
那一段时间会整段按新的 EN 状态算。 */
|
||||
#define SIM_FAN_MAX 2
|
||||
|
||||
void sim_fan_attach(int idx, int pwm_ch, int en_pin,
|
||||
uint32_t rpm_full, uint32_t rpm_min, uint8_t ppr);
|
||||
|
||||
void sim_fan_set_gated(int idx, bool on); /* 默认 true = 关断期不吐脉冲 */
|
||||
void sim_fan_set_stalled(int idx, bool on); /* 堵转 / 拔线:一个脉冲也没有 */
|
||||
|
||||
uint32_t sim_fan_edges(int idx); /* 累计 TACH 边沿数(没 attach 返回 0) */
|
||||
uint32_t sim_fan_rpm(int idx); /* 此刻的真实转速,给测试自查用 */
|
||||
|
||||
/* ---------------- 两把互斥锁 ---------------- */
|
||||
|
||||
/* 扮演第几号任务(默认 0)。单线程 sim 里「另一个任务持着锁」就靠它模拟。 */
|
||||
|
||||
在新工单中引用
屏蔽一个用户