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]>
这个提交包含在:
@@ -0,0 +1,49 @@
|
||||
/* 周期任务的节拍器(需求 §10.3)。
|
||||
|
||||
两个周期任务 —— `t_safety` 20 ms、`t_thermal` 1 s —— 都不能用
|
||||
「干完活睡 20 ms」这种写法:那样周期会变成 `20 ms + 干活时间`,
|
||||
而干活时间本身是变的(I²C 重试、NAK 超时),于是巡检的实际间隔会**漂**,
|
||||
还会跟着总线忙闲一起抖。§10.3 的 20 ms 是**间隔**,不是延时。
|
||||
|
||||
所以这里记的是**下一次该醒的绝对时刻**,每次把它 += 周期,
|
||||
睡「到那个时刻还差多久」。FreeRTOS 的 `vTaskDelayUntil()` 是同一个东西,
|
||||
这里自己写一份的唯一理由是:它得在宿主机上用虚拟时钟跑得起来。
|
||||
|
||||
---- 过载了怎么办 --------------------------------------------------------
|
||||
一个周期没干完就到点了(`sched_periodic_wait()` 算出来该睡 0 ms),
|
||||
**跳过错过的整周期、保住相位**,并计一次 `overruns`。
|
||||
不补跑 —— 补跑的现象是「卡了一下之后连着跑五圈巡检」,把总线瞬间打满、
|
||||
把已经落后的任务推得更后面。巡检读的是**当前**电压电流,补读三个周期前的没有意义。 */
|
||||
#ifndef SCHED_H
|
||||
#define SCHED_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
uint32_t period_ms;
|
||||
uint32_t next_ms; /* 下一次该醒的绝对时刻(32 位 ms,回绕安全) */
|
||||
uint32_t cycles; /* 已经等过几个周期 */
|
||||
uint32_t overruns; /* 其中「到点时活还没干完」的有几个 */
|
||||
uint32_t worst_late_ms; /* 最严重的一次迟到了多久 —— 遥测里报出去 */
|
||||
} sched_periodic_t;
|
||||
|
||||
/* 以「现在」为相位起点。period_ms 为 0 会 abort(那是事件驱动任务,不该用这个)。 */
|
||||
void sched_periodic_init(sched_periodic_t *p, uint32_t period_ms);
|
||||
|
||||
/* 纯计算:now_ms 这一刻还该睡多久,并把下一次的时刻推进。
|
||||
与 `sched_periodic_wait()` 分开是为了能不依赖时钟单独测它 ——
|
||||
回绕、过载、连续过载这三种情况用真时钟很难摆出来。 */
|
||||
uint32_t sched_periodic_delay_from(sched_periodic_t *p, uint32_t now_ms);
|
||||
|
||||
/* 睡到下一个节拍。返回实际睡了多久(0 = 这一圈超时了)。 */
|
||||
uint32_t sched_periodic_wait(sched_periodic_t *p);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SCHED_H */
|
||||
在新工单中引用
屏蔽一个用户