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 行删除
+136
查看文件
@@ -0,0 +1,136 @@
/* A2 验收主项:能在假总线上挂一个器件并读回预置值。
顺带把「器件失联 / 偶发 NAK」这两种后面 A15 要用的故障注入也测掉。 */
#include <string.h>
#include "sim_hal.h"
#include "unity.h"
#define INA226_CH1 0x40 /* 设计文档 §8.2 的地址表;A3 才会真的写驱动 */
#define PCA9555 0x20
void setUp(void) { sim_hal_reset(); }
void tearDown(void) {}
static void init_bus(void)
{
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_init(NULL));
}
static void test_use_before_init_is_state_error(void)
{
sim_i2c_add(INA226_CH1, 2);
uint8_t rd[2];
TEST_ASSERT_EQUAL_INT(HAL_ERR_STATE, hal_i2c_read(INA226_CH1, rd, sizeof(rd)));
}
/* ⭐ A2 的验收判据本身 */
static void test_read_back_preset_register(void)
{
init_bus();
sim_i2c_add(INA226_CH1, 2);
sim_i2c_set_reg(INA226_CH1, 0x02, 0x1234); /* INA226 的 Bus Voltage 寄存器 */
const uint8_t ptr = 0x02;
uint8_t rd[2] = { 0, 0 };
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_write_read(INA226_CH1, &ptr, 1, rd, sizeof(rd)));
TEST_ASSERT_EQUAL_HEX8(0x12, rd[0]); /* 大端 */
TEST_ASSERT_EQUAL_HEX8(0x34, rd[1]);
TEST_ASSERT_EQUAL_UINT32(1, sim_i2c_dev(INA226_CH1)->n_read);
}
static void test_write_stores_big_endian_and_logs_bytes(void)
{
init_bus();
sim_i2c_add(INA226_CH1, 2);
const uint8_t wr[3] = { 0x05, 0x12, 0x34 };
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_write(INA226_CH1, wr, sizeof(wr)));
TEST_ASSERT_EQUAL_HEX16(0x1234, sim_i2c_get_reg(INA226_CH1, 0x05));
const sim_i2c_dev_t *d = sim_i2c_dev(INA226_CH1);
TEST_ASSERT_EQUAL_UINT8(3, d->last_write_len);
TEST_ASSERT_EQUAL_HEX8_ARRAY(wr, d->last_write, 3);
}
/* 寄存器指针自增:一次事务连读两个寄存器,真器件就是这个行为。 */
static void test_pointer_auto_increments(void)
{
init_bus();
sim_i2c_add(INA226_CH1, 2);
sim_i2c_set_reg(INA226_CH1, 0x01, 0xAAAA);
sim_i2c_set_reg(INA226_CH1, 0x02, 0xBBBB);
const uint8_t ptr = 0x01;
uint8_t rd[4];
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_write_read(INA226_CH1, &ptr, 1, rd, sizeof(rd)));
TEST_ASSERT_EQUAL_HEX8(0xAA, rd[0]);
TEST_ASSERT_EQUAL_HEX8(0xBB, rd[2]);
}
/* PCA9555 是 8 位寄存器;同一套 HAL 要能同时伺候两种宽度。 */
static void test_byte_wide_device(void)
{
init_bus();
sim_i2c_add(PCA9555, 1);
const uint8_t wr[3] = { 0x06, 0xFF, 0x0F }; /* 配置寄存器 Port0/Port1 */
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_write(PCA9555, wr, sizeof(wr)));
TEST_ASSERT_EQUAL_HEX16(0x00FF, sim_i2c_get_reg(PCA9555, 0x06));
TEST_ASSERT_EQUAL_HEX16(0x000F, sim_i2c_get_reg(PCA9555, 0x07));
}
static void test_absent_device_naks(void)
{
init_bus();
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, hal_i2c_probe(0x77)); /* 总线上没这个地址 */
sim_i2c_add(INA226_CH1, 2);
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_probe(INA226_CH1));
sim_i2c_set_present(INA226_CH1, false); /* 器件失联(A15 降级矩阵) */
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, hal_i2c_probe(INA226_CH1));
}
static void test_transient_nak_then_recovers(void)
{
init_bus();
sim_i2c_add(INA226_CH1, 2);
sim_i2c_inject_nak(INA226_CH1, 2); /* 接下来两次事务失败 */
uint8_t rd[2];
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, hal_i2c_read(INA226_CH1, rd, sizeof(rd)));
TEST_ASSERT_EQUAL_INT(HAL_ERR_NAK, hal_i2c_read(INA226_CH1, rd, sizeof(rd)));
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_i2c_read(INA226_CH1, rd, sizeof(rd)));
/* NAK 掉的事务不该被算成一次成功访问 */
TEST_ASSERT_EQUAL_UINT32(1, sim_i2c_dev(INA226_CH1)->n_read);
}
/* 半个寄存器的长度:真器件上是未定义行为,这里必须当场报错而不是悄悄凑合。 */
static void test_partial_register_length_rejected(void)
{
init_bus();
sim_i2c_add(INA226_CH1, 2);
uint8_t rd[1];
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, hal_i2c_read(INA226_CH1, rd, 1));
const uint8_t wr[2] = { 0x05, 0x12 };
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, hal_i2c_write(INA226_CH1, wr, sizeof(wr)));
}
static void test_address_out_of_range(void)
{
init_bus();
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, hal_i2c_probe(0x80));
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_use_before_init_is_state_error);
RUN_TEST(test_read_back_preset_register);
RUN_TEST(test_write_stores_big_endian_and_logs_bytes);
RUN_TEST(test_pointer_auto_increments);
RUN_TEST(test_byte_wide_device);
RUN_TEST(test_absent_device_naks);
RUN_TEST(test_transient_nak_then_recovers);
RUN_TEST(test_partial_register_length_rejected);
RUN_TEST(test_address_out_of_range);
return UNITY_END();
}
+110
查看文件
@@ -0,0 +1,110 @@
/* GPIO / PWM / 时钟三路 sim 的行为约定。 */
#include "sim_hal.h"
#include "unity.h"
void setUp(void) { sim_hal_reset(); }
void tearDown(void) {}
/* ---- GPIO ---- */
static void test_output_write_and_readback(void)
{
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_gpio_config(5, HAL_GPIO_OUTPUT));
TEST_ASSERT_EQUAL_INT(0, sim_gpio_level(5)); /* 配完默认低 */
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_gpio_write(5, 1));
TEST_ASSERT_EQUAL_INT(1, sim_gpio_level(5));
int level = -1;
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_gpio_read(5, &level));
TEST_ASSERT_EQUAL_INT(1, level);
}
/* 「把输入脚当输出写」在板子上是静默无效的,最难查 —— sim 里必须报错。 */
static void test_write_to_input_pin_is_error(void)
{
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_gpio_config(6, HAL_GPIO_INPUT));
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, hal_gpio_write(6, 1));
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, hal_gpio_write(7, 1)); /* 压根没配过 */
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, hal_gpio_config(SIM_GPIO_PINS, HAL_GPIO_OUTPUT));
}
/* 五路 FLT/PG 是开漏 + 上拉:没有故障时必须读回 1,不能默认 0。 */
static void test_pullup_input_idles_high(void)
{
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_gpio_config(8, HAL_GPIO_INPUT_PULLUP));
int level = -1;
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_gpio_read(8, &level));
TEST_ASSERT_EQUAL_INT(1, level);
sim_gpio_drive(8, 0); /* 器件报故障,把脚拉低 */
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_gpio_read(8, &level));
TEST_ASSERT_EQUAL_INT(0, level);
}
/* 写计数是给 A5「改 CH1 不动 CH2~5」那条断言准备的。 */
static void test_write_counter_is_per_pin(void)
{
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_gpio_config(10, HAL_GPIO_OUTPUT));
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_gpio_config(11, HAL_GPIO_OUTPUT));
hal_gpio_write(10, 1);
hal_gpio_write(10, 0);
TEST_ASSERT_EQUAL_UINT32(2, sim_gpio_writes(10));
TEST_ASSERT_EQUAL_UINT32(0, sim_gpio_writes(11));
}
/* ---- PWM ---- */
static void test_pwm_config_and_duty(void)
{
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, hal_pwm_set_permille(0, 500)); /* 没配就用 */
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_pwm_config(0, 21, 25000)); /* 风扇 25 kHz */
TEST_ASSERT_EQUAL_INT(21, sim_pwm_pin(0));
TEST_ASSERT_EQUAL_UINT32(25000, sim_pwm_freq(0));
TEST_ASSERT_EQUAL_INT(0, sim_pwm_permille(0)); /* 配完默认停转 */
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_pwm_set_permille(0, 400));
uint16_t d = 0;
TEST_ASSERT_EQUAL_INT(HAL_OK, hal_pwm_get_permille(0, &d));
TEST_ASSERT_EQUAL_UINT16(400, d);
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, hal_pwm_set_permille(0, 1001)); /* 千分比上限 */
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, hal_pwm_config(HAL_PWM_CH_MAX, 21, 25000));
TEST_ASSERT_EQUAL_INT(HAL_ERR_ARG, hal_pwm_config(1, 21, 0));
}
/* ---- 时钟 ---- */
static void test_virtual_clock_advances_only_on_demand(void)
{
TEST_ASSERT_EQUAL_UINT64(0, hal_time_now_us());
hal_time_delay_ms(17); /* A9 的上电时序就是这个量级 */
TEST_ASSERT_EQUAL_UINT32(17, hal_time_now_ms());
TEST_ASSERT_EQUAL_UINT32(1, sim_time_delay_calls());
sim_time_advance_us(500);
TEST_ASSERT_EQUAL_UINT64(17500, hal_time_now_us());
TEST_ASSERT_EQUAL_UINT32(17, hal_time_now_ms()); /* 不满 1 ms 不进位 */
}
/* 49.7 天回绕:农场是长稳运行的,这一条迟早会被踩到。 */
static void test_elapsed_survives_wraparound(void)
{
sim_time_set_us(0xFFFFFFF0ull * 1000ull);
uint32_t t0 = hal_time_now_ms();
sim_time_advance_ms(32); /* 跨过 32 位回绕点 */
TEST_ASSERT_TRUE(hal_time_now_ms() < t0);
TEST_ASSERT_EQUAL_UINT32(32, hal_time_elapsed_ms(t0));
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_output_write_and_readback);
RUN_TEST(test_write_to_input_pin_is_error);
RUN_TEST(test_pullup_input_idles_high);
RUN_TEST(test_write_counter_is_per_pin);
RUN_TEST(test_pwm_config_and_duty);
RUN_TEST(test_virtual_clock_advances_only_on_demand);
RUN_TEST(test_elapsed_survives_wraparound);
return UNITY_END();
}