文件
phonerail/firmware/main/tasks.c
T
zhou yang和Claude Opus 5 ec31891429 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]>
2026-09-07 07:35:39 +08:00

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;
}