/* I²C 总线占用核算 —— 需求 §10.3 那 8 % 的可执行版本。 §10.3 手算了一遍「t_safety 每 20 ms 要占总线多久」,结论是约 1.5 ms、约 8 %, 并在末尾要求「首板上电时用逻辑分析仪实测复核一次」。这里做的是中间那一档: 让固件**自己按线上真实发生的事务**把这个数算出来,两个目标(sim 与 ESP) 用的是同一份计数代码,所以宿主机上跑出来的数就是上板会打印出来的数。 为什么放在 HAL 里而不是应用层:只有 HAL 知道每次事务真正过了多少字节。 放在应用层就变成「按计划中的事务数手算」,那是把 §10.3 的算式抄第二遍 —— 少读一次寄存器、多一次重试,手算的那份都不会变。 ⚠️ 这是**理想时序**的下限:不含时钟延展、不含 NAK 重试、不含事务之间 固件自己的开销。它能证明「预算没被写崩」,不能替代 §10.3 要求的实测。 */ #ifndef HAL_I2C_ACCT_H #define HAL_I2C_ACCT_H #include #include #ifdef __cplusplus extern "C" { #endif #define HAL_I2C_FREQ_HZ_DEFAULT 400000u /* 一次事务在总线上占的**位周期数**。 写: START + (1+wr_len) 个字节 × 9 位 + STOP 写后读: START + (1+wr_len) 字节 + 重复 START + (1+rd_len) 字节 + STOP 每字节 9 位 = 8 数据 + 1 应答;START / 重复 START / STOP 各按 1 个位周期计。 rd_len == 0 就是纯写,wr_len == 0 就是纯读(探测是 wr_len = rd_len = 0)。 对一次 INA226 寄存器读(wr 1 字节、rd 2 字节)它给出 48 位 = 120 µs @400 kHz。 §10.3 手算的是「≈46 个位周期 ≈115 µs」—— 差的两位是 STOP 和一个 START, §10.3 没数进去。整周期的结论不变(1.56 ms / 7.8 %,见 §10.3 的「约 1.5 ms / 约 8 %」)。 */ uint32_t hal_i2c_txn_bits(size_t wr_len, size_t rd_len); /* 累计位周期数。两个 HAL 实现都在事务成功**与失败**之后调它 —— NAK 掉的事务也占了总线时间(地址字节已经发出去了)。 */ void hal_i2c_acct_add(size_t wr_len, size_t rd_len); uint64_t hal_i2c_acct_bits(void); uint32_t hal_i2c_acct_txns(void); /* 位周期 → µs,按当前总线频率。hal_i2c_init() 会把频率填进来(0 视为默认 400 kHz)。 */ void hal_i2c_acct_set_freq(uint32_t hz); uint32_t hal_i2c_acct_freq(void); uint64_t hal_i2c_bits_to_us(uint64_t bits); /* 取「从上一次取样到现在」占了多少 µs,并把取样点推到现在。 t_safety 每个周期调一次,拿到的就是这一周期的总线占用。 */ uint32_t hal_i2c_acct_window_us(uint64_t *cursor_bits); void hal_i2c_acct_reset(void); #ifdef __cplusplus } #endif #endif /* HAL_I2C_ACCT_H */