/* I²C 的假实现:一张寄存器表 + 可注入的 NAK + **一颗假 TCA9548A**。宿主机专用。 ---- R2 为什么要在假总线里做 mux ------------------------------------------ R2 的五片刀片**完全同构**:五片上都是 0x40 的 INA226 和 0x20 的 PCA9555。 在真板上区分它们的唯一手段是底板那颗 TCA9548A 的通道选择。 假总线如果只按地址查表,五片就会塌成一片 —— 于是「选错通道」这一类 R2 最危险的错误(计划 §4.3)在宿主机上**一次也复现不出来**。 所以这里的器件带一个 `bus`:-1 = 主干,0~7 = 挂在 mux 的某个通道后面。 只有**当前打开的那个通道**上的器件看得见;关着的通道上的器件一律 NAK (真器件就是这个行为:支路被断开,从机根本收不到地址)。 */ #include #include #include "sim_hal.h" static sim_i2c_dev_t s_dev[SIM_I2C_MAX_DEV]; static int s_n; static bool s_inited; static bool s_bus_time = true; /* 假 mux 的状态。`s_mux_addr < 0` = 板上没挂 mux(A3~A6 那批驱动测试就是这样, 它们直接挂器件到主干上)。 */ static int s_mux_addr = -1; static int s_mux_rst_pin = -1; static uint8_t s_mux_mask; /* 控制字:bit n = 通道 n 通 */ static uint32_t s_mux_resets; static uint32_t s_ambiguous; /* 同一地址在当前可见范围里出现了不止一次 */ 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; s_bus_time = true; s_mux_addr = -1; s_mux_rst_pin = -1; s_mux_mask = 0; s_mux_resets = 0; s_ambiguous = 0; } /* 挂在**主干**上的那一个(bus < 0)。A3~A6 的驱动测试用的就是这个语义。 */ sim_i2c_dev_t *sim_i2c_dev(uint8_t addr) { for (int i = 0; i < s_n; i++) { if (s_dev[i].addr == addr && s_dev[i].bus < 0) { return &s_dev[i]; } } return NULL; } sim_i2c_dev_t *sim_i2c_dev_ch(int ch, uint8_t addr) { for (int i = 0; i < s_n; i++) { if (s_dev[i].addr == addr && s_dev[i].bus == ch) { return &s_dev[i]; } } return NULL; } /* 当前**看得见**的那一个:主干上的永远看得见,支路上的要那个通道开着。 同一地址看得见不止一片时记一次 `ambiguous` —— 真板上那是两片一起应答, 读回来的是两片的线与(数值正常、还很稳),这里让它可被断言。 */ static sim_i2c_dev_t *visible(uint8_t addr) { sim_i2c_dev_t *hit = NULL; int n = 0; for (int i = 0; i < s_n; i++) { if (s_dev[i].addr != addr) { continue; } const bool on = (s_dev[i].bus < 0) || (s_mux_addr >= 0 && (s_mux_mask & (uint8_t)(1u << s_dev[i].bus)) != 0); if (!on) { continue; } n++; if (hit == NULL) { hit = &s_dev[i]; } } if (n > 1) { s_ambiguous++; } return hit; } static sim_i2c_dev_t *add(int bus, uint8_t addr, uint8_t reg_width) { assert(addr <= 0x7f); assert(reg_width == 1 || reg_width == 2); /* 地址撞车只在**同一条支路上**才算错 —— 五片刀片同址正是 R2 的设计。 */ assert(sim_i2c_dev_ch(bus, 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->bus = bus; d->reg_width = reg_width; d->present = true; return d; } sim_i2c_dev_t *sim_i2c_add(uint8_t addr, uint8_t reg_width) { return add(-1, addr, reg_width); } sim_i2c_dev_t *sim_i2c_add_ch(int ch, uint8_t addr, uint8_t reg_width) { assert(ch >= 0 && ch < SIM_I2C_MUX_CH); return add(ch, addr, reg_width); } void sim_i2c_add_mux(uint8_t addr, int rst_pin) { assert(s_mux_addr < 0); /* 板上只有一颗 */ sim_i2c_dev_t *d = add(-1, addr, 1); d->is_mux = true; s_mux_addr = addr; s_mux_rst_pin = rst_pin; s_mux_mask = 0; /* 上电全关 */ } /* 当前打开的是哪一路:全关 -1,恰好一路 0~7,**多于一路 -2**(那是 bug 而不是一种用法,见 tca9548a.h 文件头第 2 条)。 */ int sim_i2c_mux_ch(void) { if (s_mux_mask == 0) { return -1; } if ((s_mux_mask & (uint8_t)(s_mux_mask - 1u)) != 0) { return -2; } for (int ch = 0; ch < SIM_I2C_MUX_CH; ch++) { if (s_mux_mask == (uint8_t)(1u << ch)) { return ch; } } return -2; } uint8_t sim_i2c_mux_mask(void) { return s_mux_mask; } uint32_t sim_i2c_mux_resets(void) { return s_mux_resets; } uint32_t sim_i2c_ambiguous(void) { return s_ambiguous; } /* `MUX_RST#` 的窃听口(低有效)。真器件复位之后**通道全关** —— 固件那一侧的通道影子必须跟着作废,否则下一次事务会发到断开的支路上。 */ void sim_mux_gpio_hook(int pin, int level) { if (s_mux_addr < 0 || pin != s_mux_rst_pin) { return; } if (level == 0) { s_mux_mask = 0; s_mux_resets++; } } 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; } static sim_i2c_dev_t *must_ch(int ch, uint8_t addr) { sim_i2c_dev_t *d = sim_i2c_dev_ch(ch, addr); assert(d != NULL); 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; must(addr)->fail_err = HAL_OK; } void sim_i2c_set_present(uint8_t addr, bool present) { must(addr)->present = present; } void sim_i2c_set_reg_ch(int ch, uint8_t addr, uint8_t reg, uint16_t val) { must_ch(ch, addr)->regs[reg] = val; } uint16_t sim_i2c_get_reg_ch(int ch, uint8_t addr, uint8_t reg) { return must_ch(ch, addr)->regs[reg]; } void sim_i2c_inject_nak_ch(int ch, uint8_t addr, int count) { must_ch(ch, addr)->nak_left = count; must_ch(ch, addr)->fail_err = HAL_OK; } void sim_i2c_inject_err_ch(int ch, uint8_t addr, int count, hal_err_t err) { must_ch(ch, addr)->nak_left = count; must_ch(ch, addr)->fail_err = err; } void sim_i2c_set_present_ch(int ch, uint8_t addr, bool present) { must_ch(ch, addr)->present = present; } void sim_i2c_set_bus_time(bool on) { s_bus_time = on; } /* 每次事务(成功或 NAK)都记账并把虚拟时钟花掉。NAK 也占总线:地址字节已经发出去了。 同时查一次加锁纪律 —— I²C 是 §10.3 的共享资源。 */ static void spend(size_t wr_len, size_t rd_len) { sim_lock_check(HAL_LOCK_I2C); hal_i2c_acct_add(wr_len, rd_len); if (s_bus_time) { sim_time_advance_us(hal_i2c_bits_to_us(hal_i2c_txn_bits(wr_len, rd_len))); } } /* ---- HAL 实现 ---------------------------------------------------------- */ hal_err_t hal_i2c_init(const hal_i2c_cfg_t *cfg) { (void)cfg; /* sim 不关心引脚 —— 但速率要告诉占用核算,它按频率折算 µs */ hal_i2c_acct_set_freq(cfg != NULL ? cfg->freq_hz : 0); 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 = visible(addr); if (d == NULL || !d->present) { return HAL_ERR_NAK; /* 不存在,或者它那条支路现在是断开的 */ } if (d->nak_left != 0) { if (d->nak_left > 0) { d->nak_left--; } return d->fail_err != HAL_OK ? d->fail_err : HAL_ERR_NAK; } *out = d; return HAL_OK; } /* data[0] 是寄存器指针,其后按 reg_width 一格一格写,指针自增(uint8_t 自然回绕)。 ⚠️ **mux 除外**:它整颗器件只有一个控制寄存器,写就是一个裸字节 (tca9548a.h 文件头第 1 条)。按「寄存器 + 值」写它会把通道选成寄存器号, 而且不会报错 —— 这里如实照做,让那种写法在宿主机上就现形。 */ 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 (d->is_mux) { d->n_write++; if (len == 0) { return HAL_OK; /* 只发地址 = 探测 */ } d->last_write_len = 1; d->last_write[0] = data[0]; s_mux_mask = data[0]; /* 裸字节就是控制字 */ d->regs[0] = data[0]; return HAL_OK; } 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 (d->is_mux) { d->n_read++; for (size_t i = 0; i < len; i++) { data[i] = s_mux_mask; /* 读它永远只回控制字 */ } return HAL_OK; } 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; spend(len, 0); 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; spend(0, len); 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; spend(wr_len, rd_len); 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; spend(0, 0); return take(addr, &d); }