需求 §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]>
73 行
3.5 KiB
C
73 行
3.5 KiB
C
/* `t_safety` —— 20 ms 巡检任务的**一个周期**(需求 §10.3 第一行)。
|
|
|
|
本包(A8)只做骨架:拿 I²C 锁 → 读 6 片 INA226 的 V+I → 读 PCA9555 两个输入端口
|
|
→ 放锁 → 记账。**判据(SW-2 / SW-8 / 过压 / 反灌)是 A10 的事**,这里一条都没有。
|
|
|
|
分成「一个周期是个函数」而不是直接写成 `while(1)`,是为了让它在宿主机上跑得起来:
|
|
宿主机没有 FreeRTOS,任务体是死循环就一行也测不了。ESP 侧的 `while(1)`
|
|
在 app_main.c 里,只有三行(拿节拍 / 调这个 / 睡到下一拍)。
|
|
|
|
---- 三条不能改的规矩 ----------------------------------------------------
|
|
1. **不许死等锁。** 拿不到就跳过这一周期并计数(`skipped`)。死等的现象是
|
|
巡检整个停摆,而它是优先级最高的那个任务 —— 停摆了没有任何人会发现。
|
|
2. **一个器件读失败不能中断这一圈。** 六片里坏一片,剩下五路的判据照常要出。
|
|
失联 ≠ 断电(§10.2),失联更不等于「别的路也不看了」。
|
|
3. **越界要断电时直接写 `EFUSE_EN` / `VBUS_EN` 引脚**(§10.3 原话:不经任何队列
|
|
或抽象层)。所以 A10 的判据要写在这个函数里,不是发个消息给 t_actuator。 */
|
|
#ifndef T_SAFETY_H
|
|
#define T_SAFETY_H
|
|
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
|
|
#include "hal_err.h"
|
|
#include "ina226.h"
|
|
#include "pca9555.h"
|
|
#include "phonerail.h"
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
/* 等 I²C 锁最多等这么久。取周期的 1/4:等过头这一周期就废了,
|
|
而**废一个周期**(20 ms 后重来)比**赖着不走**(把最高优先级任务钉死)好得多。 */
|
|
#define T_SAFETY_LOCK_TIMEOUT_MS 5
|
|
|
|
typedef struct {
|
|
/* 这一圈读回来的原始量。`ok` 为 false 时对应的值是**上一圈的**,不是 0 ——
|
|
判据(A10)必须先看 ok 再看值,拿一个没更新的电压去判过压是最难查的那种错。 */
|
|
ina226_vi_t rail[PHONERAIL_RAIL_COUNT];
|
|
bool rail_ok[PHONERAIL_RAIL_COUNT];
|
|
ina226_vi_t input; /* 输入侧 U13(0x43),整板功率预算 */
|
|
bool input_ok;
|
|
pca9555_in_t io; /* 五路 FLT# / PGOOD + Buck PG 或 + INA ALERT 或 */
|
|
bool io_ok;
|
|
|
|
uint32_t cycles; /* 跑过几圈(含被跳过的) */
|
|
uint32_t skipped; /* 其中因为拿不到 I²C 锁而整圈跳过的 */
|
|
uint32_t i2c_errors; /* 累计器件级失败次数(一圈里可能不止一次) */
|
|
uint32_t bus_us; /* **上一圈**在 I²C 上真正花掉的时间 */
|
|
uint64_t bus_cursor; /* 内部:位数游标,别在外面改 */
|
|
} safety_snap_t;
|
|
|
|
void t_safety_init(safety_snap_t *s);
|
|
hal_err_t t_safety_step(safety_snap_t *s);
|
|
|
|
/* 按 §10.3 的算式静态算出来的「一圈该占总线多久」,单位 µs。
|
|
它和 `s->bus_us` 应当**一模一样** —— 不一样只有两种可能:巡检里多读/少读了东西,
|
|
或者有别的任务在同一圈里插了事务进来。测试拿这两个数对账。 */
|
|
uint32_t t_safety_budget_us(void);
|
|
|
|
/* 占用率(千分之)。20 ms 周期、1560 µs → 78‰。§10.3 手算的是「约 8 %」。 */
|
|
uint32_t t_safety_load_permille(uint32_t bus_us, uint32_t period_ms);
|
|
|
|
/* 一行人话,给启动日志和遥测用。返回写进去的字符数(不含结尾 0)。 */
|
|
int t_safety_format(const safety_snap_t *s, char *buf, size_t len);
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* T_SAFETY_H */
|