feat(soe): B9 动作记录 + 时标 SOE(L4-4/L4-5)+ 104 M_SP_TB_1 自发
- 新增 App/log/soe_rec.{c,h}:64B 定长 SOE 记录纯编解码(CP56Time2a
时标 + 类型 + 元件号 + 单调序号 + 值 + sum8 撕裂校验),零 I/O 零 OS
依赖,主机 L0.5 覆盖(32 断言,含与 iec104 CP56 解码器交叉互证)
- 新增 App/log/soe_store.{c,h}:protectTask 边沿投递(取时标 + 组记录 +
SPSC 待发环/消息队列,零文件系统);defaultTask 经 syslog_drain 尾部
排空落 /log/soe.log(卷锁 try / FA_WRITE|FA_OPEN_APPEND / 校验字节数 /
容量满 unlink 重建,全套照 run.log 实证纪律;无卷损坏归类路径)
- protect_task.c:元件动作/返回、软压板投退、采样闭锁/恢复六类边沿
→ soe_store_post;post 内禁 DBG_*(protect 静态最坏链 872 B 不变,
改前/改后 stack_report 九任务逐字节一致),丢弃留痕挪 drain 侧
- iec104:单点 YX 自发改 M_SP_TB_1(30) 带 CP56 时标(总召/周期保持
M_SP_NA_1,单测两种都断言);link cfg 新增 epoch_ms 时标源回调
(NULL/无效 → IV 位如实置位);serv 绑 sys_clock(秒域时基,
ms 域 = 秒×1000,不造假亚秒)
- 主机单测:test/host/soe/(32 断言)+ iec104 94→97(TB 时标/IV);
iec104_master.py 解析 type 30(B6/B7 流不回归);decode_soe.py
台架解码断言工具(seq 单调/校验和/UTC 交叉)
- 注册:uvprojx + check_registration SIM_EXEMPT 点名(MCU-only 胶水)
- 文档(我方属主区):`App/log/README.md` 增 soe 节(64B 记录/单写者/
post 禁 DBG/UTC 交叉坑);`docs/子系统设计/IEC104上行设计说明.md`
§1 范围行 + §3.2 时标注 + §7 台架行 + §8 第 2 条销项 + 变更记录行
台架腿(bench 锁 B9,release --bench-pass):run_boot_test PASS;
--b7-pre 21/21、--b7-post 13/13(B6/B7 回归 + 确定性动作边沿);
自发 YX 上送 M_SP_TB_1 带 ts(11:00:51 动作 / 11:00:53 复归);
FTP 下载 /log/soe.log 30 条记录 decode PASS(seq 0..28 单调、
ELM_ASSERT 14 条、UTC 交叉一致);定值/压板已恢复默认。
台账 +1088 B(SPSC 环 512 + 静态 FIL 528 + 计数器)随合并由 Lead
回填(ram_baseline --write + check_mem_numbers --write)。
gate: only mem-budget/mem-numbers pending lead ledger backfill
(pre-commit --all-files: 29 Passed / 2 Failed / 0 Skipped;
hygiene self-check OK: no BOM/CRLF/mojibake/trailws/non-ASCII literal)
sync 追平 v2-on-ui(B8 freq + N33 抬栈)后复跑:@build 0/0
(Code=209492 RW=1104 ZI=154184);stack_report 九任务全 PASS、
protectTask 872 B/57% 不变;ci_local L0.5 15 runner 951/951
(freq 46 / soe 32 / iec104 97 / prot_set 90 / prot_elm 47)。
顺带修 protect_task.c 陈旧注释:定值巡检 31 项 → 40 项(B8 后)。
This commit is contained in:
@@ -10,6 +10,8 @@
|
||||
| `sys_log.h` | **日志接口(平台无关)**:定长 64B 记录写入 `/log/run.log`·`selftest.log` |
|
||||
| `sys_log_mcu.c` | `sys_log` 的 MCU 端后端实现(NAND + FatFs) |
|
||||
| `console_log.c/h` | **控制台单写者日志**:多任务日志经队列汇入唯一写者,避免串口输出交错 |
|
||||
| `soe_rec.c/h` | **SOE 记录纯编解码**(B9):64B 定长记录(CP56Time2a 时标 + 类型 + 元件号 + 单调序号 + 值 + sum8 撕裂校验),零 I/O 零 OS 依赖,主机单测覆盖 |
|
||||
| `soe_store.c/h` | **SOE 落盘胶水**(B9):`/log/soe.log`,protectTask 投递(零文件系统)→ defaultTask 排空落盘 |
|
||||
|
||||
## 关键机制
|
||||
|
||||
@@ -27,6 +29,27 @@
|
||||
- 多任务的生产者 → **单写者消费者**,消除任务间日志行交错
|
||||
- 设计详见 `docs/子系统设计/控制台单写者日志设计说明.md`
|
||||
|
||||
### soe(SOE 事件顺序记录,B9)
|
||||
|
||||
- **记录 = 64 B 定长二进制**(`soe_rec.h` 布局),落 `/log/soe.log`;
|
||||
与 run.log 分文件——run.log 是文本记录、有 UI/主机读取方,二进制 SOE
|
||||
混入会破坏读取器;分文件保持"定长 64B + 条数=fsize/64 + 满删重建 +
|
||||
FTP 下载 + 主机解码"同一套先例
|
||||
- **生产者 = protectTask**(元件动作/返回、软压板投退、采样闭锁/恢复六类
|
||||
边沿):`soe_store_post()` 纯 RAM(取时标 + 组记录 + 队列/待发环),
|
||||
**零 NAND I/O**;⚠️ **post 内禁 `DBG_*`**——会把 protectTask 静态最坏链
|
||||
垫高一帧(实证 872→1192 B FAIL),丢弃留痕由 drain(defaultTask)差值上报
|
||||
- **唯一写者 = defaultTask**:挂 `syslog_drain()` 尾部(继承其卷就绪门),
|
||||
50 ms 节拍 → SOE 落盘延迟 ≤50 ms;写盘纪律全套照 syslog(卷锁 try /
|
||||
`FA_WRITE|FA_OPEN_APPEND` / 校验字节数 / 满删重建 / 失败留痕去重),
|
||||
**绝不把名字/参数类错误码归入卷损坏触发集**
|
||||
- **时标口径**:sys_clock 秒域时基 → CP56 ms 域 = 秒×1000(亚秒恒 0,
|
||||
不造假);墙钟未建 → IV 位如实置位;seq 仅上电生命周期内单调
|
||||
(容量重建不重置,仅复位重置——主机解码按 boot 段分段断言)
|
||||
- 主机侧:`test/host/soe/decode_soe.py` 解码 + 断言(校验和/seq 单调/
|
||||
CP56↔epoch UTC 交叉);⚠️ 交叉校验用 `calendar.timegm`,不是本地时区的
|
||||
`dt.timestamp()`(+08 机差 28800 s,首跑实证坑)
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `docs/子系统设计/控制台单写者日志设计说明.md`
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 模块名称:SOE Record — 事件顺序记录编解码(实现)
|
||||
* 模块功能:实现 soe_rec.h——64B 定长记录的构建(epoch→CP56Time2a,Hinnant
|
||||
* civil 日历算法,与 iec104_asdu.c 同一算法族但本模块零依赖)与
|
||||
* 解析(版本 + sum8 校验和硬门槛)。
|
||||
* 线程约定:全部纯函数,无状态 → 任意上下文可调(生产者 = protectTask,
|
||||
* 落盘/读取 = defaultTask / 主机工具)。
|
||||
* 创建日期:2026-10-12(B9)
|
||||
*/
|
||||
#include "soe_rec.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* 2000-01-01 00:00:00 UTC 的 epoch 秒(CP56 年字段下限,钳位用) */
|
||||
#define EPOCH_2000_S 946684800uL
|
||||
/* 2127-12-31 23:59:59 UTC(年字段 7bit 上限 2000+127,钳位用;
|
||||
* = 2128-01-01(4986057600) - 1,含 2000..2124 共 32 个闰日) */
|
||||
#define EPOCH_2127_END_S 4986057599uLL
|
||||
|
||||
/*
|
||||
* 函数功能:epoch 秒 → 小端 32 位写入
|
||||
*/
|
||||
static void s_soe_put_u32(uint8_t *buf, uint32_t v) {
|
||||
buf[0] = (uint8_t)(v & 0xFFu);
|
||||
buf[1] = (uint8_t)((v >> 8) & 0xFFu);
|
||||
buf[2] = (uint8_t)((v >> 16) & 0xFFu);
|
||||
buf[3] = (uint8_t)((v >> 24) & 0xFFu);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:小端 32 位读取
|
||||
*/
|
||||
static uint32_t s_soe_get_u32(const uint8_t *buf) {
|
||||
return (uint32_t)buf[0] | ((uint32_t)buf[1] << 8)
|
||||
| ((uint32_t)buf[2] << 16) | ((uint32_t)buf[3] << 24);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:距 1970-01-01 天数 → 公历(Hinnant civil_from_days,与
|
||||
* iec104_asdu.c 同一算法;纯整数,ARMCC/MSVC 双端可移植)
|
||||
* 入口参数:z_in = 天数;y/m/d = 输出
|
||||
*/
|
||||
static void s_soe_civil_from_days(uint32_t z_in, uint32_t *y, uint32_t *m, uint32_t *d) {
|
||||
uint32_t z = z_in + 719468u;
|
||||
uint32_t era = z / 146097u;
|
||||
uint32_t doe = z - (era * 146097u);
|
||||
uint32_t yoe = (doe - (doe / 1460u) + (doe / 36524u) - (doe / 146096u)) / 365u;
|
||||
uint32_t yy = yoe + (era * 400u);
|
||||
uint32_t doy = doe - ((365u * yoe) + (yoe / 4u) - (yoe / 100u));
|
||||
uint32_t mp = ((5u * doy) + 2u) / 153u;
|
||||
uint32_t mon = mp + ((mp < 10u) ? 3u : 0u);
|
||||
|
||||
*d = doy - (((153u * mp) + 2u) / 5u) + 1u;
|
||||
if (mp >= 10u) {
|
||||
mon = mp - 9u;
|
||||
}
|
||||
*m = mon;
|
||||
*y = yy + ((mon <= 2u) ? 1u : 0u);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:epoch 秒 → CP56Time2a(7B,UTC 分解)
|
||||
* 入口参数:epoch_s - Unix 秒;buf - 目标 7B;iv - 1 = 置 IV 位(时间无效)
|
||||
* 返回值:无
|
||||
* 函数说明:钳位 [2000-01-01, 2127-12-31](CP56 年字段 7bit 域;epoch_s 为
|
||||
* uint32 → 上界钳位是防御性死分支,2106 后域扩展时生效);
|
||||
* ms 域 = 秒×1000(秒域时基如实编码,亚秒恒 0,不造假)。
|
||||
*/
|
||||
static void s_soe_epoch_to_cp56(uint32_t epoch_s, uint8_t *buf, uint8_t iv) {
|
||||
uint64_t clamped;
|
||||
uint32_t days, rem, y, m, d, ms_min;
|
||||
|
||||
clamped = epoch_s;
|
||||
if (clamped < (uint64_t)EPOCH_2000_S) {
|
||||
clamped = (uint64_t)EPOCH_2000_S;
|
||||
}
|
||||
if (clamped > EPOCH_2127_END_S) {
|
||||
clamped = EPOCH_2127_END_S;
|
||||
}
|
||||
days = (uint32_t)(clamped / 86400uLL);
|
||||
rem = (uint32_t)(clamped % 86400uLL);
|
||||
s_soe_civil_from_days(days, &y, &m, &d);
|
||||
if (y > 2127u) { /* 钳位上界的日历化(防 epoch 上界跨日) */
|
||||
y = 2127u;
|
||||
m = 12u;
|
||||
d = 31u;
|
||||
rem = 86399u;
|
||||
}
|
||||
ms_min = (rem % 60u) * 1000u; /* 秒域时基:秒如实入 ms 域(×1000),
|
||||
* 亚秒部分恒 0——不造假(B9 口径) */
|
||||
buf[0] = (uint8_t)(ms_min & 0xFFu);
|
||||
buf[1] = (uint8_t)(ms_min >> 8);
|
||||
buf[2] = (uint8_t)(((rem / 60u) % 60u) & 0x3Fu); /* min;bit7 IV 下方置 */
|
||||
buf[3] = (uint8_t)((rem / 3600u) % 24u); /* hour */
|
||||
buf[4] = (uint8_t)(d & 0x1Fu); /* day */
|
||||
buf[5] = (uint8_t)(m & 0x0Fu); /* month */
|
||||
buf[6] = (uint8_t)((y - 2000u) & 0x7Fu); /* year */
|
||||
if (iv != 0u) {
|
||||
buf[2] |= 0x80u; /* IV:时间无效 */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:sum8([0..62]) 校验和
|
||||
*/
|
||||
static uint8_t s_soe_sum8(const uint8_t *rec) {
|
||||
uint8_t s = 0u;
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0u; i < SOE_OFF_SUM; i++) {
|
||||
s = (uint8_t)(s + rec[i]);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:构建一条 64B SOE 记录(见 soe_rec.h 声明注释)
|
||||
*/
|
||||
void soe_rec_build(uint8_t *rec, uint8_t type, uint8_t elm_id, uint8_t flags,
|
||||
uint32_t seq, uint32_t epoch_s, uint32_t value) {
|
||||
if (rec == NULL) {
|
||||
return;
|
||||
}
|
||||
(void)memset(rec, 0, SOE_REC_SIZE);
|
||||
rec[SOE_OFF_VER] = (uint8_t)SOE_REC_VER;
|
||||
rec[SOE_OFF_TYPE] = type;
|
||||
rec[SOE_OFF_ELM] = elm_id;
|
||||
rec[SOE_OFF_FLAGS] = flags;
|
||||
s_soe_put_u32(rec + SOE_OFF_SEQ, seq);
|
||||
s_soe_epoch_to_cp56(epoch_s, rec + SOE_OFF_CP56,
|
||||
((flags & SOE_FLG_IV) != 0u) ? 1u : 0u);
|
||||
s_soe_put_u32(rec + SOE_OFF_EPOCH,
|
||||
((flags & SOE_FLG_IV) != 0u) ? 0u : epoch_s);
|
||||
s_soe_put_u32(rec + SOE_OFF_VALUE, value);
|
||||
rec[SOE_OFF_SUM] = s_soe_sum8(rec);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:解析一条 64B SOE 记录(见 soe_rec.h 声明注释)
|
||||
*/
|
||||
int soe_rec_parse(const uint8_t *rec, soe_rec_t *out) {
|
||||
if ((rec == NULL) || (out == NULL)) {
|
||||
return -1;
|
||||
}
|
||||
if (rec[SOE_OFF_VER] != (uint8_t)SOE_REC_VER) {
|
||||
return -2;
|
||||
}
|
||||
if (rec[SOE_OFF_SUM] != s_soe_sum8(rec)) {
|
||||
return -3;
|
||||
}
|
||||
out->ver = rec[SOE_OFF_VER];
|
||||
out->type = rec[SOE_OFF_TYPE];
|
||||
out->elm_id = rec[SOE_OFF_ELM];
|
||||
out->flags = rec[SOE_OFF_FLAGS];
|
||||
out->seq = s_soe_get_u32(rec + SOE_OFF_SEQ);
|
||||
(void)memcpy(out->cp56, rec + SOE_OFF_CP56, sizeof(out->cp56));
|
||||
out->epoch_s = s_soe_get_u32(rec + SOE_OFF_EPOCH);
|
||||
out->value = s_soe_get_u32(rec + SOE_OFF_VALUE);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 模块名称:SOE Record — 事件顺序记录编解码(纯逻辑,双端可测)
|
||||
* 模块功能:定长 64B SOE(Sequence-of-Events)记录的构建与解析:
|
||||
* CP56Time2a 时标 + 记录类型 + 元件号 + 单调序号 + 值 + 校验和。
|
||||
* **零 I/O、零 OS 依赖**(仅 stdint/string)→ 主机侧 L0.5 离线单测
|
||||
* (落实登记簿 A-4 约束 1"协议/记录与硬件 I/O 分离")。
|
||||
* 记录口径(docs/算法/继电保护功能复现规划.md §3.4 L4-4/L4-5,B9):
|
||||
* 落盘文件 = /log/soe.log(8.3 合规;FF_USE_LFN=0 约束),定长 64B 一条
|
||||
* (照 run.log 先例:count = filesize/64 O(1)、容量满删除重建)。
|
||||
* 独立于 run.log 的理由:run.log 是文本记录(时间戳+epoch+UTF-8 描述,
|
||||
* syslog_get_record/UI 按文本读),SOE 是二进制结构化记录(时标/类型/
|
||||
* 序号/值),混入同一文件会破坏现有文本读者。
|
||||
* 记录布局(64B,多字节字段一律小端):
|
||||
* [0] 版本 SOE_REC_VER
|
||||
* [1] 类型 soe_type_t
|
||||
* [2] 元件号(prot_elm_id_t;SOE_ELM_NONE=0xFF = 非元件事件)
|
||||
* [3] 标志位(bit7 IV = 记录时时钟无效,CP56 IV 位同步置位)
|
||||
* [4..7] 序号 seq(uint32 小端;上电内单调,跨复位重启计数——见 soe_store.h)
|
||||
* [8..14] CP56Time2a(7B,IEC 60870-5-4 §5.1.6;秒域时基 → ms 字段恒 0)
|
||||
* [15..18] epoch 秒(uint32 小端;IV 时置 0,主机解码交叉校验用)
|
||||
* [19..22] 值 value(uint32 小端;语义随类型,见 soe_type_t 注释)
|
||||
* [23..62] 保留(恒 0)
|
||||
* [63] 校验和 = sum8([0..62])(撕裂/半写检测,解析侧拒绝不匹配)
|
||||
* 创建日期:2026-10-12(B9)
|
||||
*/
|
||||
#ifndef SOE_REC_H
|
||||
#define SOE_REC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* 记录尺寸与版本(定长;改布局必须升 SOE_REC_VER) */
|
||||
#define SOE_REC_SIZE 64u
|
||||
#define SOE_REC_VER 1u
|
||||
|
||||
/* 字段偏移(解析/主机解码工具共用口径) */
|
||||
#define SOE_OFF_VER 0u
|
||||
#define SOE_OFF_TYPE 1u
|
||||
#define SOE_OFF_ELM 2u
|
||||
#define SOE_OFF_FLAGS 3u
|
||||
#define SOE_OFF_SEQ 4u /* 4B */
|
||||
#define SOE_OFF_CP56 8u /* 7B */
|
||||
#define SOE_OFF_EPOCH 15u /* 4B */
|
||||
#define SOE_OFF_VALUE 19u /* 4B */
|
||||
#define SOE_OFF_SUM 63u
|
||||
|
||||
/* 标志位 */
|
||||
#define SOE_FLG_IV 0x80u /* bit7:记录时时钟无效(CP56 IV 位同步置位) */
|
||||
|
||||
/* 非元件事件的元件号 */
|
||||
#define SOE_ELM_NONE 0xFFu
|
||||
|
||||
/*
|
||||
* 记录类型(值语义随类型):
|
||||
* 1 元件动作 value = 元件状态打包(bit0 asserted / bit1 picked / bit2 enabled)
|
||||
* 2 元件返回 value = 同上(返回瞬间的旧状态快照)
|
||||
* 3 软压板退出 value = 0
|
||||
* 4 软压板投入 value = 1
|
||||
* 5 采样闭锁 value = adc_health 闭锁位图(触发时的 hf & mask)
|
||||
* 6 采样恢复 value = 0
|
||||
*/
|
||||
typedef enum {
|
||||
SOE_T_ELM_ASSERT = 1u,
|
||||
SOE_T_ELM_RETURN,
|
||||
SOE_T_PLATE_OUT,
|
||||
SOE_T_PLATE_IN,
|
||||
SOE_T_SAMPLE_BLOCK,
|
||||
SOE_T_SAMPLE_RESUME,
|
||||
SOE_T_COUNT /* 哨兵值,非有效类型 */
|
||||
} soe_type_t;
|
||||
|
||||
/* 解析产物(字段原样拷贝;CP56 不在此层换算,主机工具解码) */
|
||||
typedef struct {
|
||||
uint8_t ver;
|
||||
uint8_t type;
|
||||
uint8_t elm_id;
|
||||
uint8_t flags;
|
||||
uint32_t seq;
|
||||
uint8_t cp56[7];
|
||||
uint32_t epoch_s;
|
||||
uint32_t value;
|
||||
} soe_rec_t;
|
||||
|
||||
/*
|
||||
* 函数功能:构建一条 64B SOE 记录(纯计算,无 I/O;任意上下文可调)
|
||||
* 入口参数:rec - 输出缓冲(SOE_REC_SIZE 字节,不可为 NULL) uint8_t *
|
||||
* type - 记录类型 soe_type_t(越界按 SOE_T_COUNT 处理为保留 0)
|
||||
* elm_id - 元件号(非元件事件传 SOE_ELM_NONE) uint8_t
|
||||
* flags - 标志位(SOE_FLG_IV = 时钟无效) uint8_t
|
||||
* seq - 单调序号 uint32_t
|
||||
* epoch_s - 墙钟 epoch 秒(IV 时传 0;同时 flags 须带 SOE_FLG_IV)
|
||||
* value - 类型相关值 uint32_t
|
||||
* 返回值:无
|
||||
* 函数说明:epoch_s → CP56Time2a(UTC 分解,Hinnant civil 日历算法,与
|
||||
* iec104_asdu.c 同一算法族;钳位 [2000-01-01, 2127-12-31],
|
||||
* ms 字段恒 0——sys_clock 秒域时基,如实登记)。
|
||||
* flags 带 IV 时 CP56 byte2 bit7 置位(标准"时间无效"语义)。
|
||||
*/
|
||||
void soe_rec_build(uint8_t *rec, uint8_t type, uint8_t elm_id, uint8_t flags,
|
||||
uint32_t seq, uint32_t epoch_s, uint32_t value);
|
||||
|
||||
/*
|
||||
* 函数功能:解析一条 64B SOE 记录(校验版本 + 校验和)
|
||||
* 入口参数:rec - 原始记录(SOE_REC_SIZE 字节,不可为 NULL) const uint8_t *
|
||||
* out - 解析输出(不可为 NULL) soe_rec_t *
|
||||
* 返回值:0 成功 / -1 参数错 / -2 版本不符 / -3 校验和不符(撕裂/半写) int
|
||||
* 函数说明:只读,不改动 rec。类型/标志不做强校验(前向兼容:新版本类型
|
||||
* 旧解析器可读),版本与校验和是硬门槛。
|
||||
*/
|
||||
int soe_rec_parse(const uint8_t *rec, soe_rec_t *out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SOE_REC_H */
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* 模块名称:SOE Store — 事件顺序记录落盘(实现)
|
||||
* 模块功能:实现 soe_store.h——protectTask 投递(取时标 + soe_rec_build +
|
||||
* SPSC 待发环 / 消息队列,零 FS);defaultTask(syslog_drain 尾部)
|
||||
* 排空落 /log/soe.log(64B 定长,容量满删除重建)。
|
||||
* 创建日期:2026-10-12(B9)
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
#include <string.h>
|
||||
|
||||
#define DBG_TAG "[SOE]"
|
||||
#include "dbg_log.h"
|
||||
|
||||
#include "soe_store.h"
|
||||
#include "soe_rec.h"
|
||||
#include "ff.h"
|
||||
#include "cmsis_os.h"
|
||||
#include "sys_clock.h"
|
||||
#include "vol_lock.h"
|
||||
|
||||
/* 静态变量定义区 */
|
||||
|
||||
#define SOE_PATH "/log/soe.log"
|
||||
#define SOE_QUEUE_MAX 8u
|
||||
#define SOE_RING_MAX 8u /* 待发环容量(须为 2 的幂) */
|
||||
#define SOE_RING_MASK (SOE_RING_MAX - 1u)
|
||||
|
||||
/* 待发环(SPSC 无锁):队列惰性创建(defaultTask 首次 drain)之前的窗口
|
||||
* 兜底。生产者 = protectTask(只写 head 侧),消费者 = defaultTask(只写
|
||||
* tail 侧)——单核 Cortex-M + volatile 下按 console_log 环形缓冲同一模型,
|
||||
* 免锁安全。条目 = 完整 64B 记录(post 侧已组好,drain 侧原样落盘)。 */
|
||||
static uint8_t s_ring[SOE_RING_MAX][SOE_REC_SIZE];
|
||||
static volatile uint8_t s_ring_head; /* 生产者写位 */
|
||||
static volatile uint8_t s_ring_tail; /* 消费者读位 */
|
||||
|
||||
static osMessageQueueId_t s_queue; /* 运行期队列(drain 首次调用惰性创建) */
|
||||
static const osMessageQueueAttr_t s_queue_attr = { .name = "soe" };
|
||||
|
||||
static volatile uint32_t s_seq; /* 单调序号(RAM 生命周期内;仅复位重置) */
|
||||
static volatile uint32_t s_drop_cnt; /* 丢弃计数(队列满/环满) */
|
||||
static uint32_t s_drop_logged; /* 丢弃留痕水位(drain 侧差值上报) */
|
||||
static uint8_t s_fault_reported; /* 写故障留痕去重(成功写复位) */
|
||||
|
||||
/* 函数定义区 */
|
||||
|
||||
/*
|
||||
* 函数功能:投递一条 SOE 记录(见 soe_store.h 声明注释)
|
||||
* 函数说明:时标 = sys_clock_get()(纯 RAM,无 I/O);UINT32_MAX(墙钟未建)
|
||||
* → flags 置 IV、epoch 字段 0、CP56 IV 位如实置位(B9 口径:
|
||||
* 不造假时标)。
|
||||
* ⚠ 本函数**不得调用 DBG_***:protectTask 静态最坏链 = 经
|
||||
* s_log_active_edge 的 console 尾链(872 B,B5 实测),post 内任何
|
||||
* 日志调用点都会把该尾链再垫高一帧(stack_report 实证 +320 B →
|
||||
* FAIL)。丢弃只累加计数,留痕由 drain(defaultTask)差值上报。
|
||||
*/
|
||||
void soe_store_post(uint8_t type, uint8_t elm_id, uint32_t value) {
|
||||
uint8_t rec[SOE_REC_SIZE]; /* 64B:protectTask 栈(链对比见 B9) */
|
||||
uint32_t epoch = sys_clock_get();
|
||||
uint8_t flags = 0u;
|
||||
uint32_t seq;
|
||||
|
||||
if (epoch == UINT32_MAX) {
|
||||
epoch = 0u;
|
||||
flags = SOE_FLG_IV;
|
||||
}
|
||||
seq = s_seq;
|
||||
s_seq = seq + 1u;
|
||||
soe_rec_build(rec, type, elm_id, flags, seq, epoch, value);
|
||||
|
||||
if ((s_queue != NULL) && (osKernelGetState() == osKernelRunning)) {
|
||||
if (osMessageQueuePut(s_queue, rec, 0u, 0u) == osOK) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
uint8_t head; /* 队列未建窗口(内核启动 → defaultTask
|
||||
* 首次 drain):SPSC 待发环 */
|
||||
head = s_ring_head;
|
||||
if ((uint8_t)(head - s_ring_tail) < SOE_RING_MAX) {
|
||||
(void)memcpy(s_ring[head & SOE_RING_MASK], rec, SOE_REC_SIZE);
|
||||
s_ring_head = (uint8_t)(head + 1u);
|
||||
return;
|
||||
}
|
||||
}
|
||||
/* 队列满 / 环满:丢弃 + 计数(留痕在 drain 侧,见函数头警告) */
|
||||
if (s_drop_cnt < UINT32_MAX) {
|
||||
s_drop_cnt++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:追加一条 64B SOE 记录到 /log/soe.log
|
||||
* 入口参数:rec - 完整记录(SOE_REC_SIZE 字节) const uint8_t *
|
||||
* 返回值:0 - 成功;-1 - 写失败;-2 - 卷被占用(顺延,非故障) int
|
||||
* 限定条件:仅 defaultTask(唯一写者)调用;卷已挂载、/log 已建
|
||||
* 函数说明:照 sys_log_mcu.c s_append 的台架实证纪律——
|
||||
* ① vol_lock try(本任务 = 看门狗喂狗点,绝不阻塞等锁);
|
||||
* ② FA_WRITE|FA_OPEN_APPEND(本移植 OPEN_APPEND 不含 WRITE,
|
||||
* 缺写位 f_write 恒 FR_DENIED,2026-09-20 实证);
|
||||
* ③ 判失败必须校验写回字节数(FR_OK+短写 = 失败,簇分配无错误码);
|
||||
* ④ 容量满 unlink 重建;打开/写入结构性错误 → unlink 重试一次
|
||||
* (L1 文件级自愈,同 run.log)。
|
||||
* ⚠ 本模块**没有**任何"卷损坏"归类/重格式化路径:unlink 失败、
|
||||
* 名字/参数类错误码一律只留痕(存储架构 §10.1 教训),条目丢弃
|
||||
* 计数,绝不升级成卷操作。
|
||||
*/
|
||||
static int s_soe_append(const uint8_t *rec) {
|
||||
static FIL f; /* static:FF_FS_TINY=0 → FIL=528B 栈;
|
||||
* 全程在 vol_lock 区间内,跨任务由卷锁串行化 */
|
||||
FRESULT fr;
|
||||
UINT bw = 0u;
|
||||
FILINFO finfo;
|
||||
uint32_t cnt;
|
||||
int rc = 0;
|
||||
|
||||
if (vol_lock_wait(0u) == 0u) {
|
||||
return -2;
|
||||
}
|
||||
/* 容量护栏:满 → 删除重建(seq 不重置:RAM 计数继续,主机按 boot 段看) */
|
||||
cnt = (f_stat(SOE_PATH, &finfo) == FR_OK)
|
||||
? ((uint32_t)finfo.fsize / SOE_REC_SIZE) : 0u;
|
||||
if (cnt >= SOE_STORE_MAX) {
|
||||
(void)f_unlink(SOE_PATH);
|
||||
DBG_INFO("cap full, recreate " SOE_PATH);
|
||||
}
|
||||
|
||||
fr = f_open(&f, SOE_PATH, FA_WRITE | FA_OPEN_APPEND);
|
||||
if (fr == FR_OK) {
|
||||
fr = f_write(&f, rec, SOE_REC_SIZE, &bw);
|
||||
(void)f_close(&f);
|
||||
if ((fr != FR_OK) || (bw != SOE_REC_SIZE)) {
|
||||
/* L1 文件级自愈:陈旧/损坏条目 → 删除重建、重试一次(同 run.log;
|
||||
* unlink 返回值刻意不检查——失败也只是重试失败 → 留痕,不升级) */
|
||||
(void)f_unlink(SOE_PATH);
|
||||
bw = 0u;
|
||||
fr = f_open(&f, SOE_PATH, FA_WRITE | FA_OPEN_APPEND);
|
||||
if (fr == FR_OK) {
|
||||
fr = f_write(&f, rec, SOE_REC_SIZE, &bw);
|
||||
(void)f_close(&f);
|
||||
}
|
||||
if ((fr != FR_OK) || (bw != SOE_REC_SIZE)) {
|
||||
rc = -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rc = -1;
|
||||
}
|
||||
if (rc == 0) {
|
||||
s_fault_reported = 0u;
|
||||
} else if (s_fault_reported == 0u) {
|
||||
s_fault_reported = 1u;
|
||||
DBG_ERROR("write " SOE_PATH " fail (%d)", (int)fr);
|
||||
}
|
||||
vol_lock_release();
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:排空 SOE 待发(待发环 + 队列,FIFO)并落盘
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:仅 defaultTask 调用(syslog_drain 尾部,卷就绪门之后);
|
||||
* 首次调用惰性创建消息队列(内核对象创建在 osKernelStart 之后)
|
||||
*/
|
||||
void soe_store_drain(void) {
|
||||
uint8_t rec[SOE_REC_SIZE];
|
||||
|
||||
/* 丢弃留痕(defaultTask 上下文 = 日志安全区):post 侧禁 DBG(protect 链),
|
||||
* 此处按差值上报一次,不刷屏 */
|
||||
if (s_drop_cnt != s_drop_logged) {
|
||||
s_drop_logged = s_drop_cnt;
|
||||
DBG_ERROR("soe dropped total=%lu (queue/ring full)",
|
||||
(unsigned long)s_drop_cnt);
|
||||
}
|
||||
if (s_queue == NULL) {
|
||||
s_queue = osMessageQueueNew(SOE_QUEUE_MAX, SOE_REC_SIZE, &s_queue_attr);
|
||||
if (s_queue == NULL) {
|
||||
DBG_ERROR("queue create fail"); /* 条目滞留待发环,下轮再试 */
|
||||
}
|
||||
}
|
||||
/* 待发环优先(保时序:环内条目早于队列条目) */
|
||||
while (s_ring_tail != s_ring_head) {
|
||||
uint8_t tail = s_ring_tail;
|
||||
(void)memcpy(rec, s_ring[tail & SOE_RING_MASK], SOE_REC_SIZE);
|
||||
if (s_soe_append(rec) != 0) {
|
||||
return; /* 写失败/卷忙:不推进 tail,下轮重试 */
|
||||
}
|
||||
s_ring_tail = (uint8_t)(tail + 1u);
|
||||
}
|
||||
if (s_queue == NULL) {
|
||||
return;
|
||||
}
|
||||
for (;;) {
|
||||
if (osMessageQueueGet(s_queue, rec, 0u, 0u) != osOK) {
|
||||
break; /* 队列空 */
|
||||
}
|
||||
if (s_soe_append(rec) == -2) {
|
||||
break; /* 卷被占用:本轮顺延(条目已出队,尽力而为) */
|
||||
}
|
||||
/* rc==-1 写失败:s_soe_append 已留痕;继续排空(SOE 尽力而为语义,
|
||||
* 单条坏记录不卡死后续事件) */
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:累计丢弃计数(见 soe_store.h 声明注释)
|
||||
*/
|
||||
uint32_t soe_store_dropped(void) {
|
||||
return s_drop_cnt;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 模块名称:SOE Store — 事件顺序记录落盘(MCU 胶水,单写者)
|
||||
* 模块功能:protectTask 动作/复归边沿 → soe_store_post() 投递(纯 RAM:
|
||||
* sys_clock 取时 + soe_rec_build + 消息队列,**零文件系统**);
|
||||
* defaultTask(/log 唯一写者,经 syslog_drain 尾部挂接)消费落盘
|
||||
* /log/soe.log(定长 64B,容量满删除重建,照 run.log 先例)。
|
||||
* 记录格式/类型:见 soe_rec.h(纯编解码在 soe_rec.c,主机单测覆盖)。
|
||||
* 线程约定:post = protectTask(唯一生产者;内核启动后任意任务上下文亦可,
|
||||
* 队列未建时走静态待发数组,照 syslog_post 先例);
|
||||
* drain = 仅 defaultTask(syslog_drain 尾部调用,继承其卷就绪门)。
|
||||
* seq 口径:RAM 计数器,上电内单调;容量满 unlink 重建**不重置**(计数继续),
|
||||
* 仅设备复位后重置(主机解码断言单调性须按"同一 boot 段"分段看)。
|
||||
* 创建日期:2026-10-12(B9)
|
||||
*/
|
||||
#ifndef SOE_STORE_H
|
||||
#define SOE_STORE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "soe_rec.h" /* 记录类型/标志/SOE_ELM_NONE 常量(post 入参口径) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* 容量上限(条数;满时删除重建,照 SYSLOG_RUN_MAX 先例):1024 × 64B = 64KB */
|
||||
#define SOE_STORE_MAX 1024u
|
||||
|
||||
/*
|
||||
* 函数功能:投递一条 SOE 记录(构建 + 入队;非阻塞,队列满则丢弃并计数)
|
||||
* 入口参数:type - 记录类型 soe_type_t uint8_t
|
||||
* elm_id - 元件号(非元件事件传 SOE_ELM_NONE) uint8_t
|
||||
* value - 类型相关值(见 soe_rec.h 类型表) uint32_t
|
||||
* 返回值:无
|
||||
* 限定条件:内核启动后调用(protectTask 上下文);**绝不触碰文件系统**——
|
||||
* 取时标(sys_clock 纯 RAM)+ 组记录 + osMessageQueuePut(0 超时)。
|
||||
* 队列未创建(defaultTask 首次 drain 前)走静态待发数组;数组满
|
||||
* = 丢弃 + 计数 + 首次留痕日志(绝不阻塞、绝不崩)。
|
||||
*/
|
||||
void soe_store_post(uint8_t type, uint8_t elm_id, uint32_t value);
|
||||
|
||||
/*
|
||||
* 函数功能:排空 SOE 待发(静态数组 + 队列,FIFO)并落 /log/soe.log
|
||||
* 入口参数:无
|
||||
* 返回值:无
|
||||
* 限定条件:仅 defaultTask(唯一写者)调用,且由 syslog_drain 在"卷就绪"门
|
||||
* 之后挂接(继承 s_fs_ready 语义,勿单独调用——/log 目录可能未建)
|
||||
* 函数说明:写路径照 sys_log_mcu.c s_append 全套实证纪律:vol_lock try(busy
|
||||
* = -2 顺延)、FA_WRITE|FA_OPEN_APPEND(本移植 OPEN_APPEND 不含
|
||||
* WRITE)、校验写回字节数(FR_OK+短写=失败)、容量满 unlink 重建。
|
||||
* unlink/名字类错误码**严禁**归入任何"卷损坏"触发集(存储架构
|
||||
* §10.1 教训)——本模块无任何重格式化路径,故障只留痕不升级。
|
||||
*/
|
||||
void soe_store_drain(void);
|
||||
|
||||
/*
|
||||
* 函数功能:累计丢弃计数(队列满/待发数组满;可观测性)
|
||||
* 返回值:丢弃条数 uint32_t
|
||||
*/
|
||||
uint32_t soe_store_dropped(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SOE_STORE_H */
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "dbg_log.h"
|
||||
|
||||
#include "sys_log.h"
|
||||
#include "soe_store.h" /* B9:SOE 落盘挂本函数尾部(同写者、同卷就绪门) */
|
||||
#include "ff.h"
|
||||
#include "cmsis_os.h"
|
||||
#include "sys_clock.h"
|
||||
@@ -307,6 +308,10 @@ void syslog_drain(void) {
|
||||
}
|
||||
}
|
||||
}
|
||||
/* B9:SOE 落盘挂本函数尾部——同一唯一写者(defaultTask)、同一卷就绪门
|
||||
(上方 s_fs_ready 早退蕴含 /log 已建)、同一卷锁 try 语义;protectTask
|
||||
只投递不写盘(动作路径零 NAND I/O,DoD 硬约束)。 */
|
||||
soe_store_drain();
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
/* ---------------- 类型标识(DL/T 634.5104,仅定义本期子集) ---------------- */
|
||||
#define IEC104_T_M_SP_NA_1 1U /* 单点遥信 SIQ */
|
||||
#define IEC104_T_M_DP_NA_1 3U /* 双点遥信 DIQ */
|
||||
#define IEC104_T_M_SP_TB_1 30U /* 单点遥信+CP56时标 SIQ+7B(B9 SOE 自发) */
|
||||
#define IEC104_T_M_ME_NC_1 13U /* 遥测短浮点 float+QDS(§1.5 口径) */
|
||||
#define IEC104_T_C_SE_NC_1 50U /* 定值写(短浮点)float+QDS */
|
||||
#define IEC104_T_C_DC_NA_1 52U /* 双点遥控 QDC(82H 选 / 02H 执行) */
|
||||
|
||||
@@ -270,11 +270,18 @@ static int s_scan_step(iec104_link_t *lk, uint8_t *budget, uint8_t periodic) {
|
||||
|
||||
/*
|
||||
* 函数功能:突发(COV)上送——pend 暂存逐点 SQ=0 COT=3,发成功才清位
|
||||
* 函数说明:单点遥信(idx≥4)自发用 **M_SP_TB_1**(SIQ + CP56Time2a,B9:
|
||||
* 变位带时标 = SOE 上送路径);双点(idx<4,V0.1 恒 IV)保持
|
||||
* M_DP_NA_1;总召/周期扫描仍走 s_scan_step 的 M_SP_NA_1/M_DP_NA_1
|
||||
* (时标只用于自发,标准语义——防"全量换型")。
|
||||
* 时标 = cfg->epoch_ms(ctx);NULL/UINT64_MAX → CP56 IV 位如实置位。
|
||||
*/
|
||||
static void s_spont_step(iec104_link_t *lk, uint8_t *budget) {
|
||||
uint8_t body[5];
|
||||
uint8_t body[8]; /* 最大 = SIQ + CP56(7)(M_SP_TB_1) */
|
||||
uint8_t i;
|
||||
uint8_t d;
|
||||
uint8_t blen;
|
||||
uint8_t type;
|
||||
int rc;
|
||||
|
||||
while ((*budget > 0u) && ((lk->pend_yx != 0u) || (lk->pend_yc != 0u))) {
|
||||
@@ -288,11 +295,24 @@ static void s_spont_step(iec104_link_t *lk, uint8_t *budget) {
|
||||
}
|
||||
iec104_pts_yx_read(i, &d);
|
||||
body[0] = d;
|
||||
rc = s_send_asdu(lk,
|
||||
(i < 4u) ? IEC104_T_M_DP_NA_1
|
||||
: IEC104_T_M_SP_NA_1,
|
||||
1u, IEC104_COT_SPONT, 0u, 1u,
|
||||
(uint32_t)i + 1u, body, 1u, 0u);
|
||||
blen = 1u;
|
||||
if (i < 4u) {
|
||||
type = (uint8_t)IEC104_T_M_DP_NA_1;
|
||||
} else {
|
||||
uint64_t ems = (lk->cfg->epoch_ms != NULL)
|
||||
? lk->cfg->epoch_ms(lk->ctx)
|
||||
: UINT64_MAX;
|
||||
|
||||
type = (uint8_t)IEC104_T_M_SP_TB_1;
|
||||
(void)iec104_epoch_ms_to_cp56((ems == UINT64_MAX) ? 0u : ems,
|
||||
&body[1]);
|
||||
if (ems == UINT64_MAX) {
|
||||
body[1 + 2] |= 0x80u; /* CP56 byte2 bit7 IV:时钟无效如实上送 */
|
||||
}
|
||||
blen = 8u;
|
||||
}
|
||||
rc = s_send_asdu(lk, type, 1u, IEC104_COT_SPONT, 0u, 1u,
|
||||
(uint32_t)i + 1u, body, blen, 0u);
|
||||
if (rc != 0) {
|
||||
return; /* k 窗满:pend 位保留,下次续发 */
|
||||
}
|
||||
|
||||
@@ -56,6 +56,10 @@ typedef struct {
|
||||
void (*send_apdu)(void *ctx, const uint8_t *apdu, uint16_t len);
|
||||
/* C_CS 时钟同步:epoch 毫秒;NULL 或返回 -1 → 否定确认 */
|
||||
int (*clock_sync)(void *ctx, uint64_t epoch_ms);
|
||||
/* 时标源(B9):当前 epoch 毫秒(M_SP_TB_1 自发用)。
|
||||
* NULL 或返回 UINT64_MAX = 时钟无效 → CP56 IV 位如实置位。
|
||||
* 纯读(sys_clock 推算),不得阻塞、不得有副作用。 */
|
||||
uint64_t (*epoch_ms)(void *ctx);
|
||||
} iec104_link_cfg_t;
|
||||
|
||||
/* 链路实例(胶水层静态持有,零堆) */
|
||||
|
||||
+19
-1
@@ -48,9 +48,10 @@ static uint8_t s_conn_dead; /* 发送连败 → 本轮 link 调用返回
|
||||
/* 前向声明(cfg 初始化引用) */
|
||||
static void s_send_apdu(void *ctx, const uint8_t *apdu, uint16_t len);
|
||||
static int s_clock_sync(void *ctx, uint64_t epoch_ms);
|
||||
static uint64_t s_epoch_ms(void *ctx);
|
||||
|
||||
/* V0.1 公共地址固定 1(点表口径;可配置项挂二期) */
|
||||
static const iec104_link_cfg_t s_cfg = { 1u, s_send_apdu, s_clock_sync };
|
||||
static const iec104_link_cfg_t s_cfg = { 1u, s_send_apdu, s_clock_sync, s_epoch_ms };
|
||||
|
||||
/*
|
||||
* 函数功能:link 层发送回调——经 net_send 串行 RPC 发出一个 APDU
|
||||
@@ -98,6 +99,23 @@ static int s_clock_sync(void *ctx, uint64_t epoch_ms) {
|
||||
return sys_clock_set_unix((uint32_t)secs);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:link 层时标源——当前墙钟 epoch 毫秒(B9:M_SP_TB_1 自发用)
|
||||
* 入口参数:ctx 未用
|
||||
* 返回值:epoch 毫秒;墙钟未建(UINT32_MAX)→ UINT64_MAX(link 置 CP56 IV)
|
||||
* 函数说明:sys_clock 秒域时基 → ms 字段恒 0(不造假亚秒,B9 口径);
|
||||
* 纯 RAM 推算,无 I/O、无副作用。
|
||||
*/
|
||||
static uint64_t s_epoch_ms(void *ctx) {
|
||||
uint32_t s = sys_clock_get();
|
||||
|
||||
(void)ctx;
|
||||
if (s == UINT32_MAX) {
|
||||
return UINT64_MAX;
|
||||
}
|
||||
return (uint64_t)s * 1000ULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:创建并监听 2404(socket+bind+listen,抄 ui_net_sink bind_listen)
|
||||
* 返回值:0 成功(s_listen 写回)/ -1 失败
|
||||
|
||||
+23
-3
@@ -3,7 +3,8 @@
|
||||
* 模块功能:见 protect_task.h。每拍三件事:
|
||||
* ① L5-4 采样故障闭锁:adc_health_flags() 电平位命中 → 元件状态
|
||||
* 复位(防陈旧判据)+ 本拍不 eval;
|
||||
* ② prot_set_selfcheck()(L5-1 定值完整性巡检,31 项 sum32,开销
|
||||
* ② prot_set_selfcheck()(L5-1 定值完整性巡检,40 项 sum32——
|
||||
* B8 后 PS_ID_COUNT=40,开销
|
||||
* 可忽略;失配 → prot_set 内部闭锁,eval 自动复位全元件);
|
||||
* ③ prot_elm_run()(phasor rms → 判据步进)+ 动作位图边沿日志;
|
||||
* ④ 全局软压板 PS_GLOBAL_EN(104 YK12 两段式投退,B7):退出 =
|
||||
@@ -25,6 +26,7 @@
|
||||
#include "console_log.h" /* dbg_console_writer_ready():启动锚点行门控(B6) */
|
||||
#include "prot_elm.h"
|
||||
#include "prot_set.h"
|
||||
#include "soe_store.h" /* B9:动作/复归边沿 → SOE 投递(零 FS,队列异步) */
|
||||
#include "task_watchdog.h"
|
||||
|
||||
/* L5-4 采样故障电平闭锁位:stall(可自愈)+ backward(锁存)都须停判据。
|
||||
@@ -37,9 +39,12 @@ static uint8_t s_prev_hf_lock; /* 上拍采样闭锁态(边沿日志用)
|
||||
static uint8_t s_prev_plate_out; /* 上拍全局软压板退出态(边沿日志用,B7) */
|
||||
|
||||
/*
|
||||
* 函数功能:动作位图变化沿日志(逐变化元件打名字与状态)
|
||||
* 函数功能:动作位图变化沿日志 + SOE 投递(逐变化元件)
|
||||
* 入口参数:active - 本拍 prot_elm_active_mask()
|
||||
* 函数说明:V0.1 无出口硬件(规划 R7),动作 = 遥信 + 日志;SOE 落盘随 L4。
|
||||
* 函数说明:V0.1 无出口硬件(规划 R7),动作 = 遥信 + 日志 + SOE。
|
||||
* SOE 走 soe_store_post(纯 RAM 投递,落盘由 defaultTask 异步执行,
|
||||
* 本任务零 NAND I/O——protect 静态最坏链不变,B9 DoD 硬约束)。
|
||||
* value = 元件状态打包(bit0 asserted / bit1 picked / bit2 enabled)。
|
||||
*/
|
||||
static void s_log_active_edge(uint32_t active) {
|
||||
uint32_t chg = active ^ s_prev_active;
|
||||
@@ -56,6 +61,12 @@ static void s_log_active_edge(uint32_t active) {
|
||||
DBG_INFO("elm %s asserted=%u picked=%u",
|
||||
prot_elm_name((prot_elm_id_t)i),
|
||||
(unsigned)st.asserted, (unsigned)st.picked);
|
||||
(void)soe_store_post(
|
||||
((active & bit) != 0u) ? (uint8_t)SOE_T_ELM_ASSERT
|
||||
: (uint8_t)SOE_T_ELM_RETURN,
|
||||
(uint8_t)i,
|
||||
(uint32_t)st.asserted | ((uint32_t)st.picked << 1)
|
||||
| ((uint32_t)st.enabled << 2));
|
||||
}
|
||||
}
|
||||
s_prev_active = active;
|
||||
@@ -94,12 +105,17 @@ void protect_task(void *argument) {
|
||||
if (s_prev_hf_lock == 0u) {
|
||||
DBG_ERROR("sampling FAULT -> protection blocked (hf=0x%08lX)",
|
||||
(unsigned long)(hf & PROT_HF_LOCK_MASK));
|
||||
(void)soe_store_post((uint8_t)SOE_T_SAMPLE_BLOCK,
|
||||
SOE_ELM_NONE,
|
||||
hf & (uint32_t)PROT_HF_LOCK_MASK);
|
||||
}
|
||||
s_prev_hf_lock = 1u;
|
||||
prot_elm_init();
|
||||
} else {
|
||||
if (s_prev_hf_lock != 0u) {
|
||||
DBG_INFO("sampling recovered -> protection resumed");
|
||||
(void)soe_store_post((uint8_t)SOE_T_SAMPLE_RESUME,
|
||||
SOE_ELM_NONE, 0u);
|
||||
s_prev_hf_lock = 0u;
|
||||
}
|
||||
/* L5-1 巡检:定值区与封存校验和不符 → prot_set 闭锁(下一拍起
|
||||
@@ -112,12 +128,16 @@ void protect_task(void *argument) {
|
||||
if (prot_set_get_ctl(PS_GLOBAL_EN) == 0u) {
|
||||
if (s_prev_plate_out == 0u) {
|
||||
DBG_INFO("global plate OUT -> protection disabled");
|
||||
(void)soe_store_post((uint8_t)SOE_T_PLATE_OUT,
|
||||
SOE_ELM_NONE, 0u);
|
||||
s_prev_plate_out = 1u;
|
||||
}
|
||||
prot_elm_init();
|
||||
} else {
|
||||
if (s_prev_plate_out != 0u) {
|
||||
DBG_INFO("global plate IN -> protection enabled");
|
||||
(void)soe_store_post((uint8_t)SOE_T_PLATE_IN,
|
||||
SOE_ELM_NONE, 1u);
|
||||
s_prev_plate_out = 0u;
|
||||
}
|
||||
prot_elm_run(now);
|
||||
|
||||
Reference in New Issue
Block a user