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,25 @@
/* HAL 的统一错误码。四路 HAL(i2c/gpio/pwm/time)共用这一套。 */
#ifndef HAL_ERR_H
#define HAL_ERR_H
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
HAL_OK = 0,
HAL_ERR_ARG, /* 参数不对:引脚越界、没配就用、长度不是寄存器宽度的整数倍 */
HAL_ERR_NAK, /* 器件不应答:地址错、器件失联、总线上根本没这个东西 */
HAL_ERR_TIMEOUT, /* 事务超时(时钟被拉死、从机时钟延展过长) */
HAL_ERR_BUS, /* 仲裁丢失等其它总线级故障 */
HAL_ERR_STATE, /* 没 init 就调 */
} hal_err_t;
/* 只用于日志与测试失败信息。返回的是静态字符串,不会是 NULL。 */
const char *hal_err_str(hal_err_t err);
#ifdef __cplusplus
}
#endif
#endif /* HAL_ERR_H */
@@ -0,0 +1,31 @@
/* 单根 GPIO 的方向与电平。74HC595 的三根线、五路 PG/FLT、风扇 TACH 都走这里。 */
#ifndef HAL_GPIO_H
#define HAL_GPIO_H
#include "hal_err.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
HAL_GPIO_INPUT = 0,
HAL_GPIO_INPUT_PULLUP,
HAL_GPIO_OUTPUT,
} hal_gpio_mode_t;
hal_err_t hal_gpio_config(int pin, hal_gpio_mode_t mode);
/* 写一个没配成输出的脚返回 HAL_ERR_ARG,不是悄悄成功 ——
「把输入脚当输出用」是 PCA9555 那条禁令(计划 A6)的 GPIO 版本,
在宿主机测试里就要响。 */
hal_err_t hal_gpio_write(int pin, int level);
/* level 用出参,是为了把「读到低电平」和「读失败」分开。 */
hal_err_t hal_gpio_read(int pin, int *level);
#ifdef __cplusplus
}
#endif
#endif /* HAL_GPIO_H */
@@ -0,0 +1,44 @@
/* I²C 主机事务。板上挂着 6 片 INA226 + 2 片 DAC63204 + PCA9555(+ 载板 LM75),
见设计文档 §8.2 的地址表。
刻意只抽象「事务」这一层:不暴露 start/stop/ack,也不暴露时钟延展 ——
驱动层需要的全部就是「往这个地址写这几个字节 / 从这个地址读这几个字节」。 */
#ifndef HAL_I2C_H
#define HAL_I2C_H
#include <stddef.h>
#include <stdint.h>
#include "hal_err.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
int sda_gpio;
int scl_gpio;
uint32_t freq_hz; /* 0 = 用默认 400 kHz */
} hal_i2c_cfg_t;
/* 幂等:重复调用直接返回 HAL_OK,不会重建总线。
sim 实现忽略 cfg 的内容,但仍然要求先 init —— 让「忘了 init」在宿主机上就暴露。 */
hal_err_t hal_i2c_init(const hal_i2c_cfg_t *cfg);
/* 地址一律是 7 位(0x00~0x7F),不含读写位。 */
hal_err_t hal_i2c_write(uint8_t addr, const uint8_t *data, size_t len);
hal_err_t hal_i2c_read(uint8_t addr, uint8_t *data, size_t len);
/* 写后重复起始再读 —— INA226 / LM75 / PCA9555 读寄存器都是这个形状。
拆成 write + read 两次事务在多主机总线上会被插进来,所以单列一个接口。 */
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);
/* 只发地址看有没有 ACK。需求 §12.1 步 2「I²C 扫到 10 个器件」用它。 */
hal_err_t hal_i2c_probe(uint8_t addr);
#ifdef __cplusplus
}
#endif
#endif /* HAL_I2C_H */
@@ -0,0 +1,28 @@
/* PWM 输出。本项目只有风扇一路(计划 A16 的闭环),留 4 个通道够用。
占空比单位是**千分比**(0~1000),不是硬件码值 ——
应用层不该知道 LEDC 配了几位分辨率。 */
#ifndef HAL_PWM_H
#define HAL_PWM_H
#include <stdint.h>
#include "hal_err.h"
#ifdef __cplusplus
extern "C" {
#endif
#define HAL_PWM_CH_MAX 4
hal_err_t hal_pwm_config(int ch, int pin, uint32_t freq_hz);
hal_err_t hal_pwm_set_permille(int ch, uint16_t permille);
/* 回读的是**固件设过的值**,不是量出来的波形。风扇实际转不转看 TACH。 */
hal_err_t hal_pwm_get_permille(int ch, uint16_t *permille);
#ifdef __cplusplus
}
#endif
#endif /* HAL_PWM_H */
@@ -0,0 +1,28 @@
/* 单调时钟与延时。sim 下是一个**虚拟时钟**:不真的睡,只把时间往前拨,
所以状态机的时序测试(A9 的 17 ms 上电时序等)跑起来是零耗时的。 */
#ifndef HAL_TIME_H
#define HAL_TIME_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* 单调递增,不受对时影响。上电起算。 */
uint64_t hal_time_now_us(void);
/* 32 位毫秒会在 49.7 天回绕 —— 比较两个时刻一律用 hal_time_elapsed_ms(),
不要写 `now - then > x`(农场是长稳运行的,回绕一定会发生)。 */
uint32_t hal_time_now_ms(void);
/* 从 since_ms 到现在过了多久,回绕安全。 */
uint32_t hal_time_elapsed_ms(uint32_t since_ms);
void hal_time_delay_ms(uint32_t ms);
#ifdef __cplusplus
}
#endif
#endif /* HAL_TIME_H */