文件
phonerail/firmware/components/phonerail_hal/sim/hal_i2c_sim.c
T
zhou yang和Claude Opus 5 eb3df9ff49 A6:PCA9555 驱动 + 「接地脚永远是输入」的硬约束
U23 @ 0x20,16 个脚全部是输入:5×EFUSE_FLT# + 5×EFUSE_PGOOD + BUCK_PG
+ INA_ALERT + 4 个焊死在地上的备用脚(P0_7 / P1_5~7)。位表与地址从
.claude/pcb1-parts.json 的真实焊盘网络逐脚核回来,与设计文档 §17.2 一致。

驱动根本不提供「把某个脚配成输出」的接口(和 sr595.h 不提供「抬 OE#」同一条
规矩),方向字一律过 pca9555_dir_safe(),不安全直接 abort。原因是 PCA9555 的
Output Port 寄存器复位值是 0xFF —— 把接地脚配成输出的那一刻它就输出高电平,
而真器件既不报错也不 NAK,只是发烫。

三种极性(FLT# 低有效 / PGOOD 高有效 / INA_ALERT 低有效)各自只在一个函数里
出现一次;上层问「有没有故障」,不许自己去 & 位。

sim 侧新增按 datasheet 建模的假 PCA9555:Output 复位 0xFF、输入按位合成、
接地脚被驱动时记短路、INT# 只被「读输入端口」清除。为此给假 I²C 总线加了一个
窃听钩子(POST_WRITE / PRE_READ)—— 通用寄存器表模拟不了「配成输出就对地
短路」这类事,以后再有这种器件照这个挂。

验收:8 条 ctest(81 用例)全绿;idf.py build 通过,pca9555.c.obj 在。
12 个「一定会被写出来的错」逐个注进去跑过,全部变红。

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-07 06:28:11 +08:00

175 行
4.7 KiB
C

/* I²C 的假实现:一张寄存器表 + 可注入的 NAK。宿主机专用。 */
#include <assert.h>
#include <string.h>
#include "sim_hal.h"
static sim_i2c_dev_t s_dev[SIM_I2C_MAX_DEV];
static int s_n;
static bool s_inited;
void sim_i2c_reset_internal(void); /* 只给 sim_hal_reset() 用 */
void sim_i2c_reset_internal(void)
{
memset(s_dev, 0, sizeof(s_dev));
s_n = 0;
s_inited = false;
}
sim_i2c_dev_t *sim_i2c_dev(uint8_t addr)
{
for (int i = 0; i < s_n; i++) {
if (s_dev[i].addr == addr) {
return &s_dev[i];
}
}
return NULL;
}
sim_i2c_dev_t *sim_i2c_add(uint8_t addr, uint8_t reg_width)
{
assert(addr <= 0x7f);
assert(reg_width == 1 || reg_width == 2);
assert(sim_i2c_dev(addr) == NULL); /* 地址撞车:测试写错了 */
assert(s_n < SIM_I2C_MAX_DEV);
sim_i2c_dev_t *d = &s_dev[s_n++];
memset(d, 0, sizeof(*d));
d->addr = addr;
d->reg_width = reg_width;
d->present = true;
return d;
}
static sim_i2c_dev_t *must(uint8_t addr)
{
sim_i2c_dev_t *d = sim_i2c_dev(addr);
assert(d != NULL); /* 先 sim_i2c_add() 再预置寄存器 */
return d;
}
void sim_i2c_set_reg(uint8_t addr, uint8_t reg, uint16_t val) { must(addr)->regs[reg] = val; }
uint16_t sim_i2c_get_reg(uint8_t addr, uint8_t reg) { return must(addr)->regs[reg]; }
void sim_i2c_inject_nak(uint8_t addr, int count) { must(addr)->nak_left = count; }
void sim_i2c_set_present(uint8_t addr, bool present) { must(addr)->present = present; }
/* ---- HAL 实现 ---------------------------------------------------------- */
hal_err_t hal_i2c_init(const hal_i2c_cfg_t *cfg)
{
(void)cfg; /* sim 不关心引脚与速率 */
s_inited = true;
return HAL_OK;
}
/* 取器件并把「这次事务要不要 NAK」判掉。 */
static hal_err_t take(uint8_t addr, sim_i2c_dev_t **out)
{
if (!s_inited) {
return HAL_ERR_STATE;
}
if (addr > 0x7f) {
return HAL_ERR_ARG;
}
sim_i2c_dev_t *d = sim_i2c_dev(addr);
if (d == NULL || !d->present) {
return HAL_ERR_NAK;
}
if (d->nak_left != 0) {
if (d->nak_left > 0) {
d->nak_left--;
}
return HAL_ERR_NAK;
}
*out = d;
return HAL_OK;
}
/* data[0] 是寄存器指针,其后按 reg_width 一格一格写,指针自增(uint8_t 自然回绕)。 */
static hal_err_t do_write(sim_i2c_dev_t *d, const uint8_t *data, size_t len)
{
if (len != 0 && data == NULL) {
return HAL_ERR_ARG;
}
if (len > 1 && ((len - 1) % d->reg_width) != 0) {
return HAL_ERR_ARG; /* 半个寄存器:真器件上是未定义行为,这里直接报错 */
}
d->n_write++;
d->last_write_len = (uint8_t)(len < SIM_I2C_LOG_BYTES ? len : SIM_I2C_LOG_BYTES);
if (d->last_write_len != 0) {
memcpy(d->last_write, data, d->last_write_len);
}
if (len == 0) {
return HAL_OK; /* 只发地址 = 探测 */
}
d->ptr = data[0];
for (size_t i = 1; i < len; i += d->reg_width) {
uint16_t v = data[i];
if (d->reg_width == 2) {
v = (uint16_t)((v << 8) | data[i + 1]);
}
d->regs[d->ptr++] = v;
}
if (d->hook != NULL) {
d->hook(d, SIM_I2C_EV_POST_WRITE);
}
return HAL_OK;
}
static hal_err_t do_read(sim_i2c_dev_t *d, uint8_t *data, size_t len)
{
if (len != 0 && data == NULL) {
return HAL_ERR_ARG;
}
if ((len % d->reg_width) != 0) {
return HAL_ERR_ARG;
}
d->n_read++;
if (d->hook != NULL) {
d->hook(d, SIM_I2C_EV_PRE_READ); /* 按 datasheet 建模的器件在这里合成输入寄存器 */
}
for (size_t i = 0; i < len; i += d->reg_width) {
uint16_t v = d->regs[d->ptr++];
if (d->reg_width == 2) {
data[i] = (uint8_t)(v >> 8);
data[i + 1] = (uint8_t)(v & 0xff);
} else {
data[i] = (uint8_t)v;
}
}
return HAL_OK;
}
hal_err_t hal_i2c_write(uint8_t addr, const uint8_t *data, size_t len)
{
sim_i2c_dev_t *d = NULL;
hal_err_t err = take(addr, &d);
return err != HAL_OK ? err : do_write(d, data, len);
}
hal_err_t hal_i2c_read(uint8_t addr, uint8_t *data, size_t len)
{
sim_i2c_dev_t *d = NULL;
hal_err_t err = take(addr, &d);
return err != HAL_OK ? err : do_read(d, data, len);
}
hal_err_t hal_i2c_write_read(uint8_t addr, const uint8_t *wr, size_t wr_len,
uint8_t *rd, size_t rd_len)
{
sim_i2c_dev_t *d = NULL;
hal_err_t err = take(addr, &d);
if (err != HAL_OK) {
return err;
}
err = do_write(d, wr, wr_len);
return err != HAL_OK ? err : do_read(d, rd, rd_len);
}
hal_err_t hal_i2c_probe(uint8_t addr)
{
sim_i2c_dev_t *d = NULL;
return take(addr, &d);
}