需求 §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]>
78 行
2.6 KiB
C
78 行
2.6 KiB
C
#include "tasks.h"
|
|
|
|
#include <assert.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
|
|
#include "ina226.h"
|
|
|
|
/* 优先级的绝对值本身不重要,**相对顺序**才是 §10.3 定的东西。
|
|
留出空档(6/4/4/3/1)是为了以后插一个任务时不用整体重排 —— 重排的那次
|
|
一定会有人只改一半。 */
|
|
#define PRIO_SAFETY 6
|
|
#define PRIO_THERMAL 4
|
|
#define PRIO_ACTUATOR 4
|
|
#define PRIO_LINK 3
|
|
#define PRIO_LOG 1
|
|
|
|
static const task_desc_t s_tasks[TASK_COUNT] = {
|
|
[TASK_SAFETY] = { "t_safety", 20, PRIO_SAFETY, 4096 },
|
|
[TASK_THERMAL] = { "t_thermal", 1000, PRIO_THERMAL, 3072 },
|
|
/* 执行器要跑三个状态机 + 超时兜底,栈给足;link 要收发帧与 JSON,最费。 */
|
|
[TASK_ACTUATOR] = { "t_actuator", 0, PRIO_ACTUATOR, 4096 },
|
|
[TASK_LINK] = { "t_link", 0, PRIO_LINK, 6144 },
|
|
[TASK_LOG] = { "t_log", 0, PRIO_LOG, 3072 },
|
|
};
|
|
|
|
const task_desc_t *task_desc(task_id_t id)
|
|
{
|
|
assert(id >= 0 && id < TASK_COUNT);
|
|
return &s_tasks[id];
|
|
}
|
|
|
|
bool task_table_ok(void)
|
|
{
|
|
for (int i = 0; i < TASK_COUNT; i++) {
|
|
const task_desc_t *a = &s_tasks[i];
|
|
if (a->name == NULL || a->name[0] == '\0' || a->stack_bytes < 2048) {
|
|
return false;
|
|
}
|
|
for (int j = i + 1; j < TASK_COUNT; j++) {
|
|
if (strcmp(a->name, s_tasks[j].name) == 0) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 周期:§10.3 那一列,逐格钉死。 */
|
|
if (s_tasks[TASK_SAFETY].period_ms != 20 || s_tasks[TASK_THERMAL].period_ms != 1000) {
|
|
return false;
|
|
}
|
|
if (s_tasks[TASK_ACTUATOR].period_ms != 0 || s_tasks[TASK_LINK].period_ms != 0 ||
|
|
s_tasks[TASK_LOG].period_ms != 0) {
|
|
return false;
|
|
}
|
|
|
|
/* 优先级:t_safety 唯一最高、t_log 唯一最低。 */
|
|
for (int i = 0; i < TASK_COUNT; i++) {
|
|
if (i != TASK_SAFETY && s_tasks[i].prio >= s_tasks[TASK_SAFETY].prio) {
|
|
return false;
|
|
}
|
|
if (i != TASK_LOG && s_tasks[i].prio <= s_tasks[TASK_LOG].prio) {
|
|
return false;
|
|
}
|
|
}
|
|
if (s_tasks[TASK_THERMAL].prio != s_tasks[TASK_ACTUATOR].prio) {
|
|
return false; /* §10.3 里两个都是「中」 */
|
|
}
|
|
if (s_tasks[TASK_LINK].prio >= s_tasks[TASK_THERMAL].prio) {
|
|
return false;
|
|
}
|
|
|
|
/* 巡检周期要长于 INA226 的一帧转换时间(见 tasks.h 的最后一条)。 */
|
|
if (s_tasks[TASK_SAFETY].period_ms * 1000u <= (uint32_t)INA226_CONV_US) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|