A8:任务骨架与调度(5 个任务、两把锁)
需求 §10.3 落地。四件事: 1. 五任务表(tasks.c):周期与优先级写在一张 const 表里,开机自检查的是 相对顺序(safety 唯一最高、log 唯一最低、thermal 与 actuator 同级), 外加「巡检周期必须长于 INA226 一帧转换 8.8 ms」。不过就 abort。 2. 节拍器(sched.c):记下一次该醒的绝对时刻,回绕安全(int32_t 差判先后)。 过载时跳过错过的整周期、保住相位,不补跑。 3. 两把锁(hal_lock):I²C 与 74HC595,禁止嵌套持有 —— 这块板上两者没有交集, 「拿着 A 去拿 B」永远是写错了。ESP 侧用 mutex 取优先级继承。 4. t_safety 的一个周期:拿锁 → 6 片 INA226 的 V+I + PCA9555 两端口 → 放锁。 不死等锁(5 ms 超时就跳过这圈),一片读失败不中断整圈、保留上一圈的值。 I²C 占用由 HAL 按线上真实字节数记账(两个目标同一份代码),静态预算与实测 必须相等:一次寄存器读 48 位,一圈 13 次事务 = 1560 µs / 20 ms = 7.8 %, 与 §10.3 手算的「约 1.5 ms / 约 8 %」相符(本节的 46 位没数 STOP 和重复 START)。 sim 侧配套两样:假 I²C 事务真的把虚拟时钟花掉(否则时序测试是空话), 以及可强制的加锁纪律检查(配一条反向测试,防止检查本身失灵)。 11 条 ctest(161 例)全绿,idf.py build 通过;16 个注错实验全部变红。 Co-Authored-By: Claude Opus 5 <[email protected]>
这个提交包含在:
@@ -1,174 +1,193 @@
|
||||
/* 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;
|
||||
}
|
||||
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 ((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;
|
||||
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);
|
||||
}
|
||||
/* 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;
|
||||
static bool s_bus_time = true;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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; }
|
||||
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 = 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;
|
||||
}
|
||||
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 ((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);
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户