A2:HAL 接口(i2c/gpio/pwm/time)+ sim 与 ESP 两套实现

接口在 components/phonerail_hal/include/,宿主机链 sim/、ESP32-S3 链 esp/
(IDF v5.4 新版 i2c_master / gpio / LEDC / esp_timer)。

sim 提供 A3 起要用的假板子控制面:假 I²C 器件(寄存器表 + 大端 + 指针自增)、
可注入 NAK / 器件失联、GPIO 外部驱动与写计数、虚拟时钟。

组件名不叫 hal —— ESP-IDF 自己有 components/hal(driver 依赖它),同名会盖掉。

验收:bash host/test.sh → 3 条 ctest 全过;build_idf.ps1 → 编过且零警告。

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QYuExfgyYa6DzRRcUso8M8
这个提交包含在:
zhou yang
2026-09-06 18:33:26 +08:00
共同撰写人 Claude Opus 5
父节点 1b5bd159ec
当前提交 cac6ce47ce
共修改 25 个文件,包含 1174 行新增和 40 行删除
@@ -0,0 +1,71 @@
/* GPIO 的假实现。宿主机专用。 */
#include <assert.h>
#include <string.h>
#include "sim_hal.h"
typedef struct {
bool configured;
hal_gpio_mode_t mode;
int level;
uint32_t writes;
} pin_t;
static pin_t s_pin[SIM_GPIO_PINS];
void sim_gpio_reset_internal(void); /* 只给 sim_hal_reset() 用 */
void sim_gpio_reset_internal(void) { memset(s_pin, 0, sizeof(s_pin)); }
static bool valid(int pin) { return pin >= 0 && pin < SIM_GPIO_PINS; }
hal_err_t hal_gpio_config(int pin, hal_gpio_mode_t mode)
{
if (!valid(pin)) {
return HAL_ERR_ARG;
}
pin_t *p = &s_pin[pin];
p->configured = true;
p->mode = mode;
/* 上拉输入没人驱动时读回高电平 —— 五路 FLT/PG 都是开漏 + 上拉,
「没有故障」在假板子上也必须是 1,不然测试会把默认 0 当成一直有故障。 */
p->level = (mode == HAL_GPIO_INPUT_PULLUP) ? 1 : 0;
return HAL_OK;
}
hal_err_t hal_gpio_write(int pin, int level)
{
if (!valid(pin) || !s_pin[pin].configured || s_pin[pin].mode != HAL_GPIO_OUTPUT) {
return HAL_ERR_ARG;
}
s_pin[pin].level = level ? 1 : 0;
s_pin[pin].writes++;
return HAL_OK;
}
hal_err_t hal_gpio_read(int pin, int *level)
{
if (!valid(pin) || !s_pin[pin].configured || level == NULL) {
return HAL_ERR_ARG;
}
*level = s_pin[pin].level;
return HAL_OK;
}
void sim_gpio_drive(int pin, int level)
{
assert(valid(pin));
assert(s_pin[pin].configured);
assert(s_pin[pin].mode != HAL_GPIO_OUTPUT); /* 外部驱一个输出脚 = 打架,测试写错了 */
s_pin[pin].level = level ? 1 : 0;
}
int sim_gpio_level(int pin)
{
if (!valid(pin) || !s_pin[pin].configured) {
return -1;
}
return s_pin[pin].level;
}
uint32_t sim_gpio_writes(int pin) { return valid(pin) ? s_pin[pin].writes : 0; }
@@ -0,0 +1,168 @@
/* 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;
}
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++;
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);
}
@@ -0,0 +1,50 @@
/* PWM 的假实现:只记住配置与占空比。宿主机专用。 */
#include <string.h>
#include "sim_hal.h"
typedef struct {
bool configured;
int pin;
uint32_t freq_hz;
uint16_t permille;
} ch_t;
static ch_t s_ch[HAL_PWM_CH_MAX];
void sim_pwm_reset_internal(void); /* 只给 sim_hal_reset() 用 */
void sim_pwm_reset_internal(void) { memset(s_ch, 0, sizeof(s_ch)); }
static bool valid(int ch) { return ch >= 0 && ch < HAL_PWM_CH_MAX; }
hal_err_t hal_pwm_config(int ch, int pin, uint32_t freq_hz)
{
if (!valid(ch) || pin < 0 || pin >= SIM_GPIO_PINS || freq_hz == 0) {
return HAL_ERR_ARG;
}
s_ch[ch] = (ch_t){ .configured = true, .pin = pin, .freq_hz = freq_hz, .permille = 0 };
return HAL_OK;
}
hal_err_t hal_pwm_set_permille(int ch, uint16_t permille)
{
if (!valid(ch) || !s_ch[ch].configured || permille > 1000) {
return HAL_ERR_ARG;
}
s_ch[ch].permille = permille;
return HAL_OK;
}
hal_err_t hal_pwm_get_permille(int ch, uint16_t *permille)
{
if (!valid(ch) || !s_ch[ch].configured || permille == NULL) {
return HAL_ERR_ARG;
}
*permille = s_ch[ch].permille;
return HAL_OK;
}
int sim_pwm_pin(int ch) { return valid(ch) && s_ch[ch].configured ? s_ch[ch].pin : -1; }
uint32_t sim_pwm_freq(int ch) { return valid(ch) && s_ch[ch].configured ? s_ch[ch].freq_hz : 0; }
int sim_pwm_permille(int ch) { return valid(ch) && s_ch[ch].configured ? (int)s_ch[ch].permille : -1; }
@@ -0,0 +1,35 @@
/* 时钟的假实现:虚拟时钟,delay 只是把时间往前拨,不真的睡。
所以 A9 那种「17 ms 上电时序」的场景测试跑起来是零耗时的。 */
#include "sim_hal.h"
static uint64_t s_now_us;
static uint32_t s_delay_calls;
void sim_time_reset_internal(void); /* 只给 sim_hal_reset() 用 */
void sim_time_reset_internal(void)
{
s_now_us = 0;
s_delay_calls = 0;
}
uint64_t hal_time_now_us(void) { return s_now_us; }
uint32_t hal_time_now_ms(void) { return (uint32_t)(s_now_us / 1000u); }
uint32_t hal_time_elapsed_ms(uint32_t since_ms)
{
/* 无符号减法自然回绕,49.7 天那个坎不用特判。 */
return hal_time_now_ms() - since_ms;
}
void hal_time_delay_ms(uint32_t ms)
{
s_delay_calls++;
s_now_us += (uint64_t)ms * 1000u;
}
void sim_time_set_us(uint64_t us) { s_now_us = us; }
void sim_time_advance_us(uint64_t us) { s_now_us += us; }
void sim_time_advance_ms(uint32_t ms) { s_now_us += (uint64_t)ms * 1000u; }
uint32_t sim_time_delay_calls(void) { return s_delay_calls; }
@@ -0,0 +1,16 @@
/* sim 的总闸:一次把四路都恢复出厂。每个测试的 setUp() 调它。
各路的清理函数是内部符号,不进 sim_hal.h —— 测试没有理由单独重置某一路。 */
#include "sim_hal.h"
void sim_i2c_reset_internal(void);
void sim_gpio_reset_internal(void);
void sim_pwm_reset_internal(void);
void sim_time_reset_internal(void);
void sim_hal_reset(void)
{
sim_i2c_reset_internal();
sim_gpio_reset_internal();
sim_pwm_reset_internal();
sim_time_reset_internal();
}
@@ -0,0 +1,86 @@
/* 假板子的控制面 —— 只有宿主机测试与 sim/ 自己会包含它,应用代码一律不许包含。
(包含了就编不过:ESP 目标根本不编译 sim/。)
⚠️ 模拟器是用来证明**代码逻辑**对的,不是用来证明**设计**对的。
电压、余量、时序裕量的结论全部来自设计文档的计算与首板实测。见计划 §4.2。 */
#ifndef SIM_HAL_H
#define SIM_HAL_H
#include <stdbool.h>
#include <stdint.h>
#include "hal_gpio.h"
#include "hal_i2c.h"
#include "hal_pwm.h"
#include "hal_time.h"
#ifdef __cplusplus
extern "C" {
#endif
/* 四路 sim 全部恢复出厂:器件表清空、引脚未配置、PWM 未配置、时钟归零、
init 标志清掉。每个测试的 setUp() 里调它。 */
void sim_hal_reset(void);
/* ---------------- I²C ---------------- */
#define SIM_I2C_MAX_DEV 16
#define SIM_I2C_REGS 256
#define SIM_I2C_LOG_BYTES 8
typedef struct {
uint8_t addr;
uint8_t reg_width; /* 1 或 2 字节。2 字节按**大端**——INA226 / DAC63204 / LM75 都是 */
bool present; /* false = 器件失联(A15 降级矩阵用),事务一律 NAK */
int nak_left; /* >0:接下来 n 次事务 NAK 后自动恢复;<0:一直 NAK;0:正常 */
uint16_t regs[SIM_I2C_REGS];
uint8_t ptr; /* 器件内部的寄存器指针,跨事务保持(真器件就是这个行为) */
uint32_t n_write; /* 事务计数,可用来断言「驱动真的访问过它 / 没多访问」 */
uint32_t n_read;
uint8_t last_write[SIM_I2C_LOG_BYTES]; /* 最后一次写进去的原始字节 */
uint8_t last_write_len;
} sim_i2c_dev_t;
/* 往假总线上挂一个器件。地址重复或超出 16 个会 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 */
/* 预置 / 回读寄存器。地址没挂过会 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);
/* 故障注入。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);
/* ---------------- GPIO ---------------- */
#define SIM_GPIO_PINS 48 /* ESP32-S3 的 GPIO 编号上限 */
/* 外部世界把某个**输入**脚拉成 level。对输出脚调用会 abort(那是总线打架)。 */
void sim_gpio_drive(int pin, int level);
/* 该脚当前电平:输出脚 = 固件驱动的值,输入脚 = 外部驱动的值。没配过返回 -1。 */
int sim_gpio_level(int pin);
/* 被 hal_gpio_write() 写成功过几次。断言「改 CH1 没动 CH2~5」用得上(A5)。 */
uint32_t sim_gpio_writes(int pin);
/* ---------------- PWM ---------------- */
int sim_pwm_pin(int ch); /* 未配置返回 -1 */
uint32_t sim_pwm_freq(int ch); /* 未配置返回 0 */
int sim_pwm_permille(int ch); /* 未配置返回 -1 */
/* ---------------- 时钟 ---------------- */
void sim_time_set_us(uint64_t us);
void sim_time_advance_us(uint64_t us);
void sim_time_advance_ms(uint32_t ms);
uint32_t sim_time_delay_calls(void); /* hal_time_delay_ms() 被调了几次 */
#ifdef __cplusplus
}
#endif
#endif /* SIM_HAL_H */