feat(protect): B7 定值持久化 + 全局软压板 + L5-1 竞态收口(W1 代码腿完成)
- prot_set:新增 PS_GLOBAL_EN(枚举尾追加);值区双缓冲 + 单字交换, prot_set_set_sealed 原子写+封存;selfcheck 改 seqlock 式复核 (提交撞见 = 本轮跳过)→ 收口 B5 登记的 µs 级误锁窗口 - 持久化:新增 App/cfg/prot_set_store.c/h,/prot_set.bin blob (magic+版本+计数+值区+sum32 正/反码对,144 B)走 cfg_store_write_atomic; uvprojx 注册 + 模拟器豁免点名(check_registration 同步) - 写路径:C_SE/YK12 执行段置脏 → iec104Task 投递 SYS_EVT_PROT_SET_SAVE (事件位 bit7)→ defaultTask(配置引擎属主)落盘,写失败脏回置 = 天然 重试;装载 = STORAGE_READY 屏障前整表原子 parse(损坏保出厂默认 + 自检留痕 prot set corrupt;文件缺失 = 首启静默) - YK12 转真:两段式(选择段校验 DPC / 执行段 set_sealed),protectTask 下一拍读退出态 → 复位元件 + 停判据(global plate OUT/IN 边沿日志); 本地定值项 comm_addr=0 显式不可寻址(防 IOA=0 误命中首个本地项) - 单测:prot_set 63 断言(+33:软压板/原子写/脏握手/blob 往返/损坏拒绝/ 伪造自洽越界对抗样本)、iec104 94 断言(+7:YK12 两段转真/置脏/本地项 C_RD EVI) - 文档:IEC104 设计说明 §3.3/§3.4/§7、保护规划 §3.5 L5-1/§7 P4/变更记录、 App/algo 与 App/cfg README、W1 任务卡 B7→🟦(台架腿 🔴 待窗口,随 B6 同窗) - 验证:@build 0 错 0 警(Code=205500,ZI +280 = 双缓冲+blob 缓冲); ci_local 全绿(L0 27 钩子 + L0.5 13 套件 826 断言);ram_baseline 台账 受管区与内存快照已同步;19 个改动文件无 BOM / LF / 零乱码
This commit is contained in:
+8
-5
@@ -154,16 +154,19 @@ flowchart TD
|
||||
### 3.5 `prot_set.c` / `prot_set.h` — 保护定值模型(P1 / L5-1 载体)
|
||||
|
||||
**解决的问题**:保护判据的"输入宪法"。参考系统把定值放 EEPROM + `tagPRS` 常量表
|
||||
校验和自检;本层先做 **RAM 模型**(cfg_file 持久化属 P4),把编号 / 通讯地址 /
|
||||
范围 / 默认值 / 校验和闭环钉死。
|
||||
校验和自检;本层做 **RAM 模型 + 持久化纯逻辑层**(blob 序列化/解析;文件胶水在
|
||||
`App/cfg/prot_set_store.c`,B7),把编号 / 通讯地址 / 范围 / 默认值 / 校验和闭环钉死。
|
||||
|
||||
| API | 干什么 | 注意 |
|
||||
|---|---|---|
|
||||
| `prot_set_init()` | 装默认值 + 封存校验和(幂等) | 内核启动前可调 |
|
||||
| `prot_set_load_raw(vals)` | 整表装载(P4 起 cfg_file 调用),逐项范围校验 | **all-or-nothing**:任一项越界整表不装载,`prot_set_bad_mask()` 指认越界项 |
|
||||
| `prot_set_set(id, q10)` | 单项写入(范围校验) | **不自动重封存**——批量改完必须 `prot_set_seal()`,否则 selfcheck 判"非法改写" |
|
||||
| `prot_set_load_raw(vals)` | 整表装载(B7 由 `prot_set_store_parse` 内部走同一路径),逐项范围校验 | **all-or-nothing**:任一项越界整表不装载,`prot_set_bad_mask()` 指认越界项;影子区 + 单字交换,读侧无中间态 |
|
||||
| `prot_set_set(id, q10)` | 单项写入(范围校验,批处理路径) | **不自动重封存**——批量改完必须 `prot_set_seal()`,否则 selfcheck 判"非法改写";板端单项写一律用下行 |
|
||||
| `prot_set_set_sealed(id, q10)` | **原子写+封存(B7)**:影子区改好重封后单字交换 | selfcheck 永不见中间态(收口 B5 竞态窗口);置脏标志 |
|
||||
| `prot_set_dirty_peek/take/mark()` | 脏标志三件套(B7 落盘握手) | peek=iec104Task 通知;take=属主落盘前消费;mark=写失败回置重试 |
|
||||
| `prot_set_store_serialize/parse(buf,len)` | 持久化 blob(`PSET`+版本+值区+sum32 正/反码对) | parse **整表原子**:magic/版本/长度/校验对/范围任一失败 = 一字不动 |
|
||||
| `prot_set_get_f()` / `get_ctl()` | 工程量浮点 / 投退读取 | 单字读原子 |
|
||||
| `prot_set_selfcheck()` | 重算 sum32 与封存正/反码比对,失配 → 闭锁 | L5-1;`prot_set_seal()` 是解锁的唯一合法路径 |
|
||||
| `prot_set_selfcheck()` | 重算 sum32 与封存正/反码比对,失配 → 闭锁 | L5-1;seqlock 式复核:提交撞见 = 本轮跳过不误锁;`prot_set_seal()` 是解锁的唯一合法路径 |
|
||||
|
||||
**要点**
|
||||
|
||||
|
||||
+228
-19
@@ -9,6 +9,8 @@
|
||||
*/
|
||||
#include "prot_set.h"
|
||||
|
||||
#include <string.h> /* memcpy(set_sealed 影子区拷贝,B7) */
|
||||
|
||||
/* 元数据表:顺序必须与 prot_set_id_t 严格一致(逐行带地址注释,同 phasor
|
||||
* k_inst 的朴素行式风格) */
|
||||
const prot_set_meta_t g_prot_set_meta[PS_ID_COUNT] = {
|
||||
@@ -44,25 +46,30 @@ const prot_set_meta_t g_prot_set_meta[PS_ID_COUNT] = {
|
||||
{ 0x8821, 0u, PROT_SET_Q10(100.0), PROT_SET_Q10(1.0), PROT_SET_Q10(200.0) },
|
||||
{ 0x8825, 0u, PROT_SET_Q10(100.0), PROT_SET_Q10(1.0), PROT_SET_Q10(10000.0) },
|
||||
{ 0x8826, 0u, PROT_SET_Q10(5.0), PROT_SET_Q10(1.0), PROT_SET_Q10(10.0) },
|
||||
{ 0x0000, 1u, PROT_SET_Q10(1), 0, PROT_SET_Q10(1) }, /* 全局软压板投退(YK12,B7) */
|
||||
};
|
||||
|
||||
static uint32_t s_val[PS_ID_COUNT]; /* RAM 值区(uint32 承载 int32,校验和按字覆盖) */
|
||||
static uint32_t s_val[2][PS_ID_COUNT]; /* 双缓冲值区(uint32 承载 int32,校验和按字覆盖)
|
||||
* B7:写者改影子区后单字交换 s_act,
|
||||
* 读侧(protectTask selfcheck/get)永远看到一致快照 */
|
||||
static volatile uint8_t s_act; /* 活跃区下标(0/1);交换 = 单字存储 */
|
||||
static uint32_t s_sum; /* 封存的正码 */
|
||||
static uint32_t s_sumn; /* 封存的反码 */
|
||||
static uint32_t s_bad_mask; /* load_raw 越界位图 */
|
||||
static uint32_t s_bad_mask; /* load_raw/parse 越界位图 */
|
||||
static uint8_t s_locked; /* 定值非法 → 闭锁(L5-1) */
|
||||
static uint8_t s_dirty; /* 未落盘定值改动(B7 持久化脏标志) */
|
||||
|
||||
/*
|
||||
* 函数功能:sum32 覆盖 RAM 值区(参考 Fun_CalSum32 同款:32 位字累加自然回绕)
|
||||
* 入口参数:无
|
||||
* 函数功能:sum32 覆盖指定值区(参考 Fun_CalSum32 同款:32 位字累加自然回绕)
|
||||
* 入口参数:vals - PS_ID_COUNT 个字的值区
|
||||
* 返 回 值:校验和正码
|
||||
*/
|
||||
static uint32_t prot_sum32(void) {
|
||||
static uint32_t prot_sum_buf(const uint32_t *vals) {
|
||||
uint32_t sum = 0u;
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
|
||||
sum += s_val[i];
|
||||
sum += vals[i];
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
@@ -76,7 +83,7 @@ static void prot_load_defaults(void) {
|
||||
uint32_t i;
|
||||
|
||||
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
|
||||
s_val[i] = (uint32_t)g_prot_set_meta[i].def;
|
||||
s_val[s_act][i] = (uint32_t)g_prot_set_meta[i].def;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,6 +96,7 @@ void prot_set_init(void) {
|
||||
prot_load_defaults();
|
||||
s_bad_mask = 0u;
|
||||
s_locked = 0u;
|
||||
s_dirty = 0u;
|
||||
prot_set_seal();
|
||||
}
|
||||
|
||||
@@ -110,6 +118,8 @@ uint32_t prot_set_bad_mask(void) {
|
||||
*/
|
||||
int prot_set_load_raw(const int32_t *vals) {
|
||||
uint32_t mask = 0u;
|
||||
uint32_t cur_act;
|
||||
uint32_t *shadow;
|
||||
uint32_t i;
|
||||
|
||||
if (vals == 0) {
|
||||
@@ -124,10 +134,16 @@ int prot_set_load_raw(const int32_t *vals) {
|
||||
if (mask != 0u) {
|
||||
return -1;
|
||||
}
|
||||
/* 原子装载(B7):写影子区 → 封存 → 单字交换;读侧无中间态 */
|
||||
cur_act = s_act;
|
||||
shadow = s_val[cur_act ^ 1u];
|
||||
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
|
||||
s_val[i] = (uint32_t)vals[i];
|
||||
shadow[i] = (uint32_t)vals[i];
|
||||
}
|
||||
prot_set_seal();
|
||||
s_sum = prot_sum_buf(shadow);
|
||||
s_sumn = ~s_sum;
|
||||
s_locked = 0u;
|
||||
s_act = (uint8_t)(cur_act ^ 1u);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -145,10 +161,64 @@ int prot_set_set(prot_set_id_t id, int32_t q10) {
|
||||
if ((q10 < g_prot_set_meta[id].min) || (q10 > g_prot_set_meta[id].max)) {
|
||||
return -1;
|
||||
}
|
||||
s_val[(uint32_t)id] = (uint32_t)q10;
|
||||
s_val[s_act][(uint32_t)id] = (uint32_t)q10;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:单项**原子**写入+封存(B7,收口 B5 竞态窗口)
|
||||
* 入口参数:id - 定值项编号;q10 - Q10 定点值
|
||||
* 返 回 值:0 / -1 越界或非法编号拒写(原值不动)
|
||||
* 函数说明:影子区拷贝 → 改影子 → 按影子重封校验和 → 单字交换活跃区。
|
||||
* selfcheck 任何时刻看到的都是"值区与封存一致"的完整快照
|
||||
* (区别于 prot_set_set+seal 批处理路径;两路径勿在一次批处理中交错)。
|
||||
* 置脏标志 → 104 任务通知 defaultTask(配置引擎属主)落盘。
|
||||
*/
|
||||
int prot_set_set_sealed(prot_set_id_t id, int32_t q10) {
|
||||
uint32_t cur_act;
|
||||
uint32_t *shadow;
|
||||
|
||||
if (id >= PS_ID_COUNT) {
|
||||
return -1;
|
||||
}
|
||||
if ((q10 < g_prot_set_meta[id].min) || (q10 > g_prot_set_meta[id].max)) {
|
||||
return -1;
|
||||
}
|
||||
cur_act = s_act;
|
||||
shadow = s_val[cur_act ^ 1u];
|
||||
(void)memcpy(shadow, s_val[cur_act], sizeof(s_val[0]));
|
||||
shadow[(uint32_t)id] = (uint32_t)q10;
|
||||
s_sum = prot_sum_buf(shadow);
|
||||
s_sumn = ~s_sum;
|
||||
s_locked = 0u;
|
||||
s_act = (uint8_t)(cur_act ^ 1u); /* 单字交换:读侧原子切换 */
|
||||
s_dirty = 1u;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:脏标志三件套(B7 持久化握手)
|
||||
* 入口参数:无
|
||||
* 返 回 值:peek/take = 1 存在未落盘改动 / 0 干净;mark 无返回
|
||||
* 函数说明:peek 非消费(iec104Task 每轮通知,直到落盘成功);
|
||||
* take 消费(defaultTask 落盘前);mark 写盘失败回置(等下轮重试)。
|
||||
* 单字节标志,多读者安全;写者只有 set_sealed(置)与属主任务(清/置)。
|
||||
*/
|
||||
uint8_t prot_set_dirty_peek(void) {
|
||||
return s_dirty;
|
||||
}
|
||||
|
||||
uint8_t prot_set_dirty_take(void) {
|
||||
uint8_t d = s_dirty;
|
||||
|
||||
s_dirty = 0u;
|
||||
return d;
|
||||
}
|
||||
|
||||
void prot_set_dirty_mark(void) {
|
||||
s_dirty = 1u;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:读定值 Q10 原值
|
||||
* 入口参数:id - 定值项编号
|
||||
@@ -158,7 +228,7 @@ int32_t prot_set_get(prot_set_id_t id) {
|
||||
if (id >= PS_ID_COUNT) {
|
||||
return 0;
|
||||
}
|
||||
return (int32_t)s_val[(uint32_t)id];
|
||||
return (int32_t)s_val[s_act][(uint32_t)id];
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -185,7 +255,7 @@ uint8_t prot_set_get_ctl(prot_set_id_t id) {
|
||||
* 返 回 值:sum32 正码
|
||||
*/
|
||||
uint32_t prot_set_checksum(void) {
|
||||
return prot_sum32();
|
||||
return prot_sum_buf(s_val[s_act]);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -193,9 +263,12 @@ uint32_t prot_set_checksum(void) {
|
||||
* 入口参数:无
|
||||
* 返 回 值:无
|
||||
* 函数说明:封存同时解除闭锁——selfcheck 失配后的唯一合法解锁路径。
|
||||
* ⚠ 批处理路径(prot_set_set 多次 + 本函数收尾)在 set 与 seal
|
||||
* 之间存在"已改值未封存"窗口,selfcheck 会如实判失配(防篡改语义);
|
||||
* 板端单项写一律走 prot_set_set_sealed(原子,无此窗口)。
|
||||
*/
|
||||
void prot_set_seal(void) {
|
||||
s_sum = prot_sum32();
|
||||
s_sum = prot_sum_buf(s_val[s_act]);
|
||||
s_sumn = ~s_sum;
|
||||
s_locked = 0u;
|
||||
}
|
||||
@@ -206,16 +279,23 @@ void prot_set_seal(void) {
|
||||
* 返 回 值:0 通过 / -1 失配(已置闭锁)
|
||||
* 函数说明:失配 → s_locked 置位并保持,直到下一次 seal;
|
||||
* 元件层每拍读 prot_set_locked() 决定整体复位(联动闭锁)。
|
||||
* B7 双缓冲 + seqlock 式复核:先读封存和、再读值区、再读封存和,
|
||||
* 两次不同 = 检查期间发生了原子提交 → 本轮不作数(下拍再查),
|
||||
* 杜绝"提交瞬间被巡检撞见"的误锁(收口 B5 竞态窗口)。
|
||||
*/
|
||||
int prot_set_selfcheck(void) {
|
||||
if (prot_sum32() != s_sum) {
|
||||
uint32_t sum_a = s_sum;
|
||||
uint32_t calc = prot_sum_buf(s_val[s_act]);
|
||||
uint32_t sum_b = s_sum;
|
||||
|
||||
if (sum_a != sum_b) {
|
||||
/* 检查期间有 set_sealed/load_raw 提交:快照不一致,本轮跳过 */
|
||||
return (s_locked != 0u) ? -1 : 0;
|
||||
}
|
||||
if ((calc != sum_a) || (sum_a != ~s_sumn)) {
|
||||
s_locked = 1u; /* 值区与封存不符:定值被非法改写 → 闭锁 */
|
||||
}
|
||||
if ((s_sum != (~s_sumn)) || (s_locked != 0u)) {
|
||||
s_locked = 1u;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
return (s_locked != 0u) ? -1 : 0;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -226,3 +306,132 @@ int prot_set_selfcheck(void) {
|
||||
uint8_t prot_set_locked(void) {
|
||||
return s_locked;
|
||||
}
|
||||
|
||||
/* ==================== 持久化 blob(B7,纯逻辑) ========================= */
|
||||
|
||||
/* 小端编解码(blob 跨端一致;主机单测与 MCU 同一份字节口径) */
|
||||
|
||||
/*
|
||||
* 函数功能:16 位值按小端写入缓冲
|
||||
* 入口参数:p - 写位置(≥2 字节余量);v - 值
|
||||
* 返 回 值:无
|
||||
*/
|
||||
static void s_put16(uint8_t *p, uint16_t v) {
|
||||
p[0] = (uint8_t)(v & 0xFFu);
|
||||
p[1] = (uint8_t)(v >> 8);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:32 位值按小端写入缓冲
|
||||
* 入口参数:p - 写位置(≥4 字节余量);v - 值
|
||||
* 返 回 值:无
|
||||
*/
|
||||
static void s_put32(uint8_t *p, uint32_t v) {
|
||||
p[0] = (uint8_t)(v & 0xFFu);
|
||||
p[1] = (uint8_t)((v >> 8) & 0xFFu);
|
||||
p[2] = (uint8_t)((v >> 16) & 0xFFu);
|
||||
p[3] = (uint8_t)(v >> 24);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:从小端缓冲读 16 位值
|
||||
* 入口参数:p - 读位置
|
||||
* 返 回 值:值 uint16_t
|
||||
*/
|
||||
static uint16_t s_get16(const uint8_t *p) {
|
||||
return (uint16_t)((uint32_t)p[0] | ((uint32_t)p[1] << 8));
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:从小端缓冲读 32 位值
|
||||
* 入口参数:p - 读位置
|
||||
* 返 回 值:值 uint32_t
|
||||
*/
|
||||
static uint32_t s_get32(const uint8_t *p) {
|
||||
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16)
|
||||
| ((uint32_t)p[3] << 24);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:把当前值区序列化为 blob(校验和按值区现算,不依赖封存值)
|
||||
* 入口参数:p_buf - 输出缓冲;cap - 容量;p_len - 实际长度输出
|
||||
* 返 回 值:0 / -1 参数非法或容量不足
|
||||
*/
|
||||
int prot_set_store_serialize(uint8_t *p_buf, uint32_t cap, uint32_t *p_len) {
|
||||
const uint32_t *vals = s_val[s_act];
|
||||
uint32_t sum = 0u;
|
||||
uint32_t i;
|
||||
|
||||
if ((p_buf == 0) || (p_len == 0) || (cap < PROT_SET_BLOB_SZ)) {
|
||||
return -1;
|
||||
}
|
||||
s_put32(p_buf, PROT_SET_BLOB_MAGIC);
|
||||
s_put16(p_buf + 4u, (uint16_t)PROT_SET_BLOB_VER);
|
||||
s_put16(p_buf + 6u, (uint16_t)PS_ID_COUNT);
|
||||
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
|
||||
s_put32(p_buf + PROT_SET_BLOB_HDR_SZ + 4u * i, vals[i]);
|
||||
sum += vals[i];
|
||||
}
|
||||
s_put32(p_buf + PROT_SET_BLOB_SZ - 8u, sum);
|
||||
s_put32(p_buf + PROT_SET_BLOB_SZ - 4u, ~sum);
|
||||
*p_len = PROT_SET_BLOB_SZ;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:解析 blob 并**整表原子**装载(all-or-nothing)
|
||||
* 入口参数:p_buf/len - blob 字节流
|
||||
* 返 回 值:0 已装载并封存 / -1 拒绝(magic/版本/长度/校验对不符或任一项
|
||||
* 越界 → 活跃值区一字不动;越界项经 prot_set_bad_mask() 指认)
|
||||
* 函数说明:解码/校验全在影子区进行,只有全部通过才交换活跃区——
|
||||
* 损坏 blob 不可能污染在用的定值(掉电/写坏/版本换代同一路径)。
|
||||
*/
|
||||
int prot_set_store_parse(const uint8_t *p_buf, uint32_t len) {
|
||||
uint32_t cur_act;
|
||||
uint32_t *shadow;
|
||||
uint32_t sum = 0u;
|
||||
uint32_t mask = 0u;
|
||||
uint32_t i;
|
||||
|
||||
if ((p_buf == 0) || (len != PROT_SET_BLOB_SZ)) {
|
||||
return -1;
|
||||
}
|
||||
if (s_get32(p_buf) != PROT_SET_BLOB_MAGIC) {
|
||||
return -1;
|
||||
}
|
||||
if (s_get16(p_buf + 4u) != (uint16_t)PROT_SET_BLOB_VER) {
|
||||
return -1;
|
||||
}
|
||||
if (s_get16(p_buf + 6u) != (uint16_t)PS_ID_COUNT) {
|
||||
return -1;
|
||||
}
|
||||
cur_act = s_act;
|
||||
shadow = s_val[cur_act ^ 1u];
|
||||
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
|
||||
uint32_t v = s_get32(p_buf + PROT_SET_BLOB_HDR_SZ + 4u * i);
|
||||
|
||||
shadow[i] = v;
|
||||
sum += v;
|
||||
}
|
||||
if (sum != s_get32(p_buf + PROT_SET_BLOB_SZ - 8u)) {
|
||||
return -1; /* 校验和正码不符(影子区未交换前不可见,无害) */
|
||||
}
|
||||
if (~sum != s_get32(p_buf + PROT_SET_BLOB_SZ - 4u)) {
|
||||
return -1; /* 反码对不符 */
|
||||
}
|
||||
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
|
||||
if (((int32_t)shadow[i] < g_prot_set_meta[i].min)
|
||||
|| ((int32_t)shadow[i] > g_prot_set_meta[i].max)) {
|
||||
mask |= (1uL << i);
|
||||
}
|
||||
}
|
||||
s_bad_mask = mask;
|
||||
if (mask != 0u) {
|
||||
return -1; /* 校验对一致但含越界项:同样整表拒绝 */
|
||||
}
|
||||
s_sum = sum;
|
||||
s_sumn = ~sum;
|
||||
s_locked = 0u;
|
||||
s_act = (uint8_t)(cur_act ^ 1u);
|
||||
return 0;
|
||||
}
|
||||
|
||||
+50
-5
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* 模块名称:保护定值模型(P1,L5-1 载体)
|
||||
* 模块功能:保护定值的 RAM 模型——编号 / 通讯地址 / 范围 / 默认值 / 校验和自检。
|
||||
* 纯逻辑(仅 stdint.h),主机侧可离线单测;持久化(cfg_file 定值段)
|
||||
* 属 P4,本层只提供 load_raw/validate 的装载接口。
|
||||
* 模块功能:保护定值的 RAM 模型——编号 / 通讯地址 / 范围 / 默认值 / 校验和自检,
|
||||
* 以及 B7 定值持久化的**纯逻辑层**(blob 序列化/解析 + 原子装载)。
|
||||
* 文件读写胶水在 App/cfg/prot_set_store.c(MCU 端)。
|
||||
*
|
||||
* 设计口径(docs/算法/继电保护功能复现规划.md §5):
|
||||
* - 定点存储:Q10(×1024),与参考系统定点系数 2^10 一致(§5.3);
|
||||
@@ -13,8 +13,11 @@
|
||||
* - ⚠ 默认值/范围为**拟定值**:参考 EEPROM 出厂默认表未在本仓库,
|
||||
* 待对照后逐项修订(规划 §10.3 待补项);范围先按 DTU 馈线典型量级。
|
||||
*
|
||||
* 线程约定:装载(init/load_raw)在 defaultTask(P4 起);读在保护消费者任务。
|
||||
* 读接口返回快照值(单个 int32 读取原子);写与封存必须同一任务(P4 定)。
|
||||
* 线程约定(B7 定):装载(init/store_parse)在 defaultTask(存储 bring-up,
|
||||
* STORAGE_READY 前);单项写在 iec104Task(C_SE/YK12,经 set_sealed 原子路径);
|
||||
* 落盘在 defaultTask(配置引擎属主)。读在保护消费者任务(快照值原子)。
|
||||
* 双缓冲交换 = 单字写,读侧永远看到一致快照;写者之间靠启动时序串行
|
||||
* (装载先于 NET_READY,104 写在其后)。
|
||||
*
|
||||
* 创建日期:2026-10-11
|
||||
*/
|
||||
@@ -65,6 +68,8 @@ typedef enum {
|
||||
PS_PT_SEC, /* 0x8821 PT 二次额定(V) */
|
||||
PS_CT_PRI, /* 0x8825 CT 一次额定(A) */
|
||||
PS_CT_SEC, /* 0x8826 CT 二次额定(A) */
|
||||
/* --- 全局软压板(B7):104 YK12 两段式投退;0 = 保护退出服务 --- */
|
||||
PS_GLOBAL_EN, /* 本地项(无通讯地址,经 YK12 写、C_RD 不可读) */
|
||||
PS_ID_COUNT
|
||||
} prot_set_id_t;
|
||||
|
||||
@@ -113,4 +118,44 @@ void prot_set_seal(void);
|
||||
int prot_set_selfcheck(void);
|
||||
uint8_t prot_set_locked(void);
|
||||
|
||||
/*
|
||||
* 函数功能:单项**原子**写入+封存(B7,收口 B5 竞态窗口)
|
||||
* 入口参数:id - 定值项编号;q10 - Q10 定点值
|
||||
* 返 回 值:0 / -1 越界或非法编号拒写(原值不动)
|
||||
* 函数说明:影子缓冲拷贝 → 改影子 → 重封校验和 → 单字交换活跃区。
|
||||
* selfcheck 永远看到"值区与封存一致"的完整快照,不存在
|
||||
* "已改值未封存"中间态(区别于 prot_set_set+seal 的批处理路径,
|
||||
* 两条路径不得在一次批处理中途交错)。置脏标志(待持久化)。
|
||||
*/
|
||||
int prot_set_set_sealed(prot_set_id_t id, int32_t q10);
|
||||
|
||||
/* 脏标志(存在未落盘的定值改动):set_sealed 置位;load/init 不置 */
|
||||
uint8_t prot_set_dirty_peek(void); /* 非消费(生产者用它通知属主任务) */
|
||||
uint8_t prot_set_dirty_take(void); /* 消费(属主任务落盘前取) */
|
||||
void prot_set_dirty_mark(void); /* 写盘失败回置(等下轮重试) */
|
||||
|
||||
/* ---------------- 持久化 blob(B7;纯逻辑,主机可单测) ----------------
|
||||
* 格式(小端):magic 'PSET'(4B) + version u16 + count u16
|
||||
* + values u32×count + sum32 u32 + ~sum32 u32
|
||||
* 版本 1;count 必须等于 PS_ID_COUNT(定值表增项 → 旧 blob 整体作废,
|
||||
* 编号不回收但格式换代,宁可用出厂默认也不半装载)。 */
|
||||
#define PROT_SET_BLOB_MAGIC 0x50534554uL /* 字节序 P,S,E,T */
|
||||
#define PROT_SET_BLOB_VER 1u
|
||||
#define PROT_SET_BLOB_HDR_SZ 8u /* magic(4)+ver(2)+cnt(2) */
|
||||
#define PROT_SET_BLOB_SZ (PROT_SET_BLOB_HDR_SZ + 4u * (uint32_t)PS_ID_COUNT + 8u)
|
||||
|
||||
/*
|
||||
* 函数功能:把当前值区序列化为 blob(校验和按值区现算,不依赖封存值)
|
||||
* 返 回 值:0 / -1 缓冲不足或参数非法;*p_len 输出实际长度
|
||||
*/
|
||||
int prot_set_store_serialize(uint8_t *p_buf, uint32_t cap, uint32_t *p_len);
|
||||
|
||||
/*
|
||||
* 函数功能:解析 blob 并**整表原子**装载(all-or-nothing)
|
||||
* 入口参数:p_buf/len - blob 字节流
|
||||
* 返 回 值:0 已装载并封存 / -1 拒绝(magic/版本/长度/校验对不符或
|
||||
* 任一项越界 → 值区一字不动;越界项经 prot_set_bad_mask() 指认)
|
||||
*/
|
||||
int prot_set_store_parse(const uint8_t *p_buf, uint32_t len);
|
||||
|
||||
#endif /* PROT_SET_H */
|
||||
|
||||
@@ -31,6 +31,9 @@ extern osEventFlagsId_t sys_evtHandle;
|
||||
#define SYS_EVT_CFG_SAVE (1u << 5) /* UI:网口配置保存请求(defaultTask = 配置引擎属主,消费并写盘) */
|
||||
#define SYS_EVT_CONSOLE_TX (1u << 6) /* 控制台单写者:生产者入环形缓冲后置位,defaultTask 消费并发送
|
||||
* (消费型命令位,非就绪位——就绪位必须 osFlagsNoClear) */
|
||||
#define SYS_EVT_PROT_SET_SAVE (1u << 7) /* 保护定值落盘请求(B7):iec104Task 检测到定值脏置位,
|
||||
* defaultTask(配置引擎属主)消费并写 /prot_set.bin
|
||||
* (消费型命令位;写盘失败脏标志保留 → 下轮再通知重试) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
+3
-1
@@ -10,11 +10,13 @@
|
||||
| `cfg_file.c/h` | **配置引擎核心**:解析 / 查询 / 保存 / 2s 周期 `cfg_file_poll` |
|
||||
| `cfg_file_store.h` | **存储后端抽象接口**(MCU=FreeRTOS 堆 / 模拟器=宿主堆,共享核心零分配符号) |
|
||||
| `cfg_file_store_mcu.c` | **MCU 端后端实现**:`/config.ini`(临时名由目标路径同目录推导 + `f_rename` 原子写) |
|
||||
| `prot_set_store.c/h` | **保护定值持久化胶水(B7)**:`/prot_set.bin` blob 读写(blob 格式/校验/原子装载在 `App/algo/prot_set.c` 纯逻辑层);装载 = storage bring-up(屏障前),落盘 = defaultTask 消费 `SYS_EVT_PROT_SET_SAVE` |
|
||||
|
||||
## 设计要点
|
||||
|
||||
- **单任务收敛**:`cfg_file` 为静态态,只有 defaultTask 操作;UI 保存请求经
|
||||
`SYS_EVT_CFG_SAVE` 事件唤醒 defaultTask 执行(netTask/uiTask 不得直接调用)
|
||||
`SYS_EVT_CFG_SAVE` 事件唤醒 defaultTask 执行(netTask/uiTask 不得直接调用);
|
||||
定值落盘同构——iec104Task 只置事件位(`SYS_EVT_PROT_SET_SAVE`),不碰文件系统
|
||||
- **零分配共享核心**:核心逻辑不含任何分配符号,动态内存收进平台后端
|
||||
(`cfg_store_alloc/free`:MCU→`pvPortMalloc`,模拟器→宿主堆)
|
||||
- **原子写**:写 `/config.TMP` 后 `f_rename` 替换,避免掉电损坏
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 模块名称:prot_set_store(B7 定值持久化)——实现
|
||||
* 模块功能:见 prot_set_store.h。blob 格式/校验/原子装载全在 prot_set.c
|
||||
* (纯逻辑,主机单测覆盖),本文件只做"读文件→parse、
|
||||
* serialize→原子写"的平台胶水。
|
||||
* 文件口径:"/prot_set.bin"(卷根,8.3 合规,FF_USE_LFN=0 约束);
|
||||
* 原子写临时名由 cfg_store_write_atomic 按目标目录推导
|
||||
* (固定 config.TMP),与 /config.ini 的保存同在 defaultTask
|
||||
* 串行执行,无并发竞争。
|
||||
* 创建日期:2026-10-12(B7)
|
||||
*/
|
||||
#include "prot_set_store.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "cfg_file_store.h"
|
||||
#include "prot_set.h"
|
||||
|
||||
#define PROT_SET_STORE_PATH "/prot_set.bin"
|
||||
|
||||
/* blob 收发缓冲(静态:defaultTask 栈深已是全项目最深链,勿再压 144 B 局部) */
|
||||
static uint8_t s_blob[PROT_SET_BLOB_SZ];
|
||||
|
||||
/*
|
||||
* 函数功能:开机装载定值 blob
|
||||
* 入口参数:无
|
||||
* 返 回 值:0 已装载 / 1 文件缺失或读失败(保持默认)/ -1 损坏被拒
|
||||
* 函数说明:读失败与"文件不存在"同归 1——启动期卷锁无竞争(本任务独占
|
||||
* bring-up),try-lock 失败即真 I/O 故障,此时保持出厂默认并让
|
||||
* 调用方按"无档"处理是安全侧(定值仍封存自洽,L5-1 不误锁)。
|
||||
*/
|
||||
int prot_set_store_load(void) {
|
||||
size_t nread = 0u;
|
||||
|
||||
if (cfg_store_read(PROT_SET_STORE_PATH, s_blob, sizeof(s_blob), &nread) != 0) {
|
||||
return 1;
|
||||
}
|
||||
if (prot_set_store_parse(s_blob, (uint32_t)nread) != 0) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:落盘当前定值(消费脏标志;失败回置等重试)
|
||||
* 入口参数:无
|
||||
* 返 回 值:0 无待写或成功 / -1 写失败
|
||||
*/
|
||||
int prot_set_store_save(void) {
|
||||
uint32_t len = 0u;
|
||||
|
||||
if (prot_set_dirty_take() == 0u) {
|
||||
return 0;
|
||||
}
|
||||
if (prot_set_store_serialize(s_blob, (uint32_t)sizeof(s_blob), &len) != 0) {
|
||||
prot_set_dirty_mark();
|
||||
return -1;
|
||||
}
|
||||
if (cfg_store_write_atomic(PROT_SET_STORE_PATH, s_blob, (size_t)len) != 0) {
|
||||
prot_set_dirty_mark(); /* 卷忙/写失败:脏保留,iec104Task 下轮再通知 */
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* 模块名称:prot_set_store
|
||||
* 模块功能:保护定值持久化胶水(B7)——把 prot_set 的纯逻辑 blob
|
||||
* (prot_set_store_serialize/parse)落到 cfg 存储后端的
|
||||
* "/prot_set.bin"(MCU = NAND/FatFs 原子写,模拟器 = 宿主文件系统)。
|
||||
* 装载在 defaultTask 存储 bring-up(STORAGE_READY 前);
|
||||
* 落盘在 defaultTask(配置引擎属主,iec104Task 经
|
||||
* SYS_EVT_PROT_SET_SAVE 投递请求,本模块只被属主调用)。
|
||||
* 适用平台:双端接口(MCU 与模拟器共用 cfg_file_store 抽象)
|
||||
* 创建日期:2026-10-12(B7)
|
||||
*/
|
||||
#ifndef PROT_SET_STORE_H
|
||||
#define PROT_SET_STORE_H
|
||||
|
||||
/*
|
||||
* 函数功能:开机装载定值 blob(整表原子,all-or-nothing)
|
||||
* 返回值:0 - 已装载;1 - 文件缺失(首启正常,保持出厂默认);
|
||||
* -1 - blob 损坏/越界被拒(保持出厂默认,调用方留痕)
|
||||
* 限定条件:仅 defaultTask、卷已挂载(storage_bring_up 内调用)
|
||||
*/
|
||||
int prot_set_store_load(void);
|
||||
|
||||
/*
|
||||
* 函数功能:落盘当前定值(内部先消费脏标志;无改动 = 空操作返回 0)
|
||||
* 返回值:0 - 无待写或写成功;-1 - 写失败(脏标志已回置,等下轮重试)
|
||||
* 限定条件:仅 defaultTask 调用(配置引擎属主;原子写 = 临时文件 + rename,
|
||||
* 与 /config.ini 共用同目录 config.TMP——两者都在本任务串行执行,无并发)
|
||||
*/
|
||||
int prot_set_store_save(void);
|
||||
|
||||
#endif /* PROT_SET_STORE_H */
|
||||
@@ -358,19 +358,32 @@ static uint64_t s_src_yx_valid(void) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:板端默认源 - 遥控(V0.1 仅 YK11 复归 → prot_elm_request_reset)
|
||||
* 返 回 值:0 / -1 无出口硬件或未知 IOA(YK1~4/12~14 负向确认)
|
||||
* 函数功能:板端默认源 - 遥控(YK11 复归 → prot_elm_request_reset;
|
||||
* YK12 全局软压板 → PS_GLOBAL_EN 两段式转真,B7)
|
||||
* 返 回 值:0 / -1 无出口硬件或未知 IOA(YK1~4/13/14 负向确认)
|
||||
* 函数说明:本回调跑在 iec104Task 上下文,**不得**直接 prot_elm_init()
|
||||
* (与 protectTask 的 eval 竞态)→ 落请求,下一拍 eval 头部消费(B5)。
|
||||
* YK12 走 prot_set_set_sealed(原子写+封存,B7);protectTask 下一拍
|
||||
* 读到压板态即复位元件并停判据(protect_task.c)。
|
||||
*/
|
||||
static int s_src_yk_exec(uint32_t ioa, uint8_t dpc, uint8_t select) {
|
||||
(void)dpc; /* 本期仅 YK11 与双点值无关(复归) */
|
||||
switch (ioa) {
|
||||
case 0x600Au: /* YK11 信号复归 → 元件状态复位(异步一拍) */
|
||||
(void)dpc; /* 复归与双点值无关 */
|
||||
if (select == 0u) {
|
||||
prot_elm_request_reset();
|
||||
}
|
||||
return 0;
|
||||
case 0x600Bu: /* YK12 全局软压板(PS_GLOBAL_EN)两段式 */
|
||||
if ((dpc != (uint8_t)IEC104_DPC_ON) && (dpc != (uint8_t)IEC104_DPC_OFF)) {
|
||||
return -1; /* 非法双点命令:选择段即拒 */
|
||||
}
|
||||
if (select != 0u) {
|
||||
return 0; /* 选择段 = 只校验 */
|
||||
}
|
||||
return prot_set_set_sealed(PS_GLOBAL_EN,
|
||||
(dpc == (uint8_t)IEC104_DPC_ON)
|
||||
? PROT_SET_Q10(1) : PROT_SET_Q10(0));
|
||||
default:
|
||||
return -1; /* 其余 YK:无出口硬件/无对应能力 → 拒绝 */
|
||||
}
|
||||
@@ -378,7 +391,7 @@ static int s_src_yk_exec(uint32_t ioa, uint8_t dpc, uint8_t select) {
|
||||
|
||||
/*
|
||||
* 函数功能:板端默认源 - 定值读(扫 g_prot_set_meta 通讯地址映射)
|
||||
* 返 回 值:0 / -1 未知地址
|
||||
* 返 回 值:0 / -1 未知地址(本地项 comm_addr=0 不可寻址,B7 起显式排除)
|
||||
*/
|
||||
static int s_src_set_read(uint32_t addr, float *out) {
|
||||
uint32_t i;
|
||||
@@ -387,7 +400,8 @@ static int s_src_set_read(uint32_t addr, float *out) {
|
||||
return -1;
|
||||
}
|
||||
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
|
||||
if (g_prot_set_meta[i].comm_addr == (uint16_t)addr) {
|
||||
if ((g_prot_set_meta[i].comm_addr != 0u)
|
||||
&& (g_prot_set_meta[i].comm_addr == (uint16_t)addr)) {
|
||||
*out = prot_set_get_f((prot_set_id_t)i);
|
||||
return 0;
|
||||
}
|
||||
@@ -396,8 +410,9 @@ static int s_src_set_read(uint32_t addr, float *out) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:板端默认源 - 定值写(Q10 范围校验;执行段 prot_set_set + seal)
|
||||
* 返 回 值:0 / -1 越界或未知地址
|
||||
* 函数功能:板端默认源 - 定值写(Q10 范围校验;执行段 prot_set_set_sealed
|
||||
* 原子写+封存,B7 收口 B5 竞态窗口;置脏 → 104 任务通知落盘)
|
||||
* 返 回 值:0 / -1 越界或未知地址(本地项 comm_addr=0 不可寻址)
|
||||
*/
|
||||
static int s_src_set_write(uint32_t addr, float v, uint8_t select) {
|
||||
uint32_t i;
|
||||
@@ -406,7 +421,7 @@ static int s_src_set_write(uint32_t addr, float v, uint8_t select) {
|
||||
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
|
||||
const prot_set_meta_t *meta = &g_prot_set_meta[i];
|
||||
|
||||
if (meta->comm_addr != (uint16_t)addr) {
|
||||
if ((meta->comm_addr == 0u) || (meta->comm_addr != (uint16_t)addr)) {
|
||||
continue;
|
||||
}
|
||||
q10 = PROT_SET_Q10(v);
|
||||
@@ -416,11 +431,7 @@ static int s_src_set_write(uint32_t addr, float v, uint8_t select) {
|
||||
if (select != 0u) {
|
||||
return 0; /* 选择段 = 只做范围校验 */
|
||||
}
|
||||
if (prot_set_set((prot_set_id_t)i, q10) != 0) {
|
||||
return -1;
|
||||
}
|
||||
prot_set_seal(); /* 执行段:写入 + 封存校验和(L5-1) */
|
||||
return 0;
|
||||
return prot_set_set_sealed((prot_set_id_t)i, q10);
|
||||
}
|
||||
return -1; /* 未知地址 */
|
||||
}
|
||||
|
||||
@@ -10,10 +10,13 @@
|
||||
* (无效位),频率随 L1-7 落地前同样置 IV;
|
||||
* - YX:IOA 1~33(§1.5 表(3));双点 = 开关位置类(YX1~4,本期无硬件 → IV),
|
||||
* 保护动作类 YX15~23 映射 prot_elm 状态,闭锁类 YX31~33 映射 prot_set;
|
||||
* - YK:IOA 0x6000+(§1.5 表(3));V0.1 无出口硬件(规划 R7)——除
|
||||
* YK11 信号复归(→ prot_elm_init)外一律否定确认(COT|PN),如实回;
|
||||
* - 定值:IOA = prot_set 元数据 comm_addr(0x8820 基址 + 告警段 0x8027~0x802A),
|
||||
* 读经 C_RD、写经 C_SE_NC(选择/执行两段,执行完成统一 seal 校验和);
|
||||
* - YK:IOA 0x6000+(§1.5 表(3));V0.1 无出口硬件(规划 R7)——
|
||||
* YK11 信号复归(→ prot_elm_request_reset)与 YK12 全局软压板
|
||||
* (→ PS_GLOBAL_EN 两段式转真,B7)转真执行,其余一律否定确认(COT|PN),如实回;
|
||||
* - 定值:IOA = prot_set 元数据 comm_addr(0x8820 基址 + 告警段 0x8027~0x802A;
|
||||
* comm_addr=0 本地项不可寻址),
|
||||
* 读经 C_RD、写经 C_SE_NC(选择/执行两段,执行段原子写+封存并置脏,
|
||||
* 持久化 /prot_set.bin 由 defaultTask 执行,B7);
|
||||
* - 固有参数 8001H~800AH:本期**不实现**(字符串类无法用短浮点上送),
|
||||
* 按未知 IOA(COT 14 + EVI)应答,登记为待办(设计文档)。
|
||||
* 线程约定:init/bind 内核启动前或建服务前;cov_check/read 仅 104 服务任务上下文。
|
||||
|
||||
+20
-1
@@ -35,6 +35,7 @@
|
||||
#include "nand_ftl.h"
|
||||
#include "net_cfg.h"
|
||||
#include "cfg_file.h"
|
||||
#include "prot_set_store.h"
|
||||
#include "sys_log.h"
|
||||
#include "sys_events.h"
|
||||
/* 深调用链模块(本任务 = 存储 bring-up + 配置引擎属主 + syslog 唯一写者,
|
||||
@@ -221,7 +222,8 @@ void default_task_func(void *argument) {
|
||||
task_wd_seg(TASK_WD_SEG_EVTWAIT);
|
||||
if (sys_evtHandle != NULL) {
|
||||
(void)osEventFlagsWait(sys_evtHandle,
|
||||
SYS_EVT_CFG_SAVE | SYS_EVT_CONSOLE_TX,
|
||||
SYS_EVT_CFG_SAVE | SYS_EVT_CONSOLE_TX
|
||||
| SYS_EVT_PROT_SET_SAVE,
|
||||
osFlagsWaitAny, 50u);
|
||||
}
|
||||
/* 控制台唯一发送点:把环形缓冲中的日志按块发出(无待发时立即返回) */
|
||||
@@ -242,6 +244,9 @@ void default_task_func(void *argument) {
|
||||
#if !defined(TEST_SUITE_STORAGE)
|
||||
task_wd_seg(TASK_WD_SEG_CFGSAVE);
|
||||
net_cfg_save_run();
|
||||
/* 保护定值落盘(B7):内部消费脏标志,无改动 = 空操作;
|
||||
* 写失败脏回置 → iec104Task 下轮 poll 再投递事件 = 天然重试 */
|
||||
(void)prot_set_store_save();
|
||||
#endif
|
||||
/* 配置文件变更轮询(2s 周期;沉降去抖在引擎内) */
|
||||
if ((cfg_poll_cnt++ % 40u) == 0u) {
|
||||
@@ -527,6 +532,20 @@ static FRESULT storage_bring_up(FATFS *fs) {
|
||||
* 文件缺失/损坏 = 工厂默认,不阻塞组网)。须在置位 STORAGE_READY 前,
|
||||
* 使"存储就绪"屏障蕴含"配置已就绪",netTask 组网时配置恒有效 */
|
||||
(void)net_cfg_load();
|
||||
/* 装载保护定值持久化档(B7):文件缺失 = 出厂默认(首启正常,
|
||||
* 静默);blob 损坏/越界 = 整表拒绝保持默认 + 自检留痕
|
||||
* (all-or-nothing,prot_set_store_parse 保证一字不半装载)。
|
||||
* 须在 STORAGE_READY 前——netTask 等此屏障,104 定值写必在装载后 */
|
||||
{
|
||||
int ps_rc = prot_set_store_load();
|
||||
|
||||
if (ps_rc == 0) {
|
||||
DBG_INFO("[CFG] prot set loaded (/prot_set.bin)");
|
||||
} else if (ps_rc < 0) {
|
||||
DBG_ERROR("[CFG] prot set blob CORRUPT, defaults in use");
|
||||
syslog_post(SYSLOG_SELFTEST, "prot set corrupt");
|
||||
}
|
||||
}
|
||||
/* 广播"存储就绪"(= 卷挂载 + 配置加载完成),唤醒依赖文件系统的任务 */
|
||||
if (sys_evtHandle != NULL) {
|
||||
osEventFlagsSet(sys_evtHandle, SYS_EVT_STORAGE_READY);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "iec104_link.h"
|
||||
#include "iec104_pts.h"
|
||||
#include "net_socket.h"
|
||||
#include "prot_set.h" /* 定值脏标志 peek(B7 落盘通知) */
|
||||
#include "sys_clock.h"
|
||||
#include "sys_events.h"
|
||||
#include "task_watchdog.h"
|
||||
@@ -290,6 +291,12 @@ void iec104_serv_task(void *argument) {
|
||||
for (;;) {
|
||||
task_wd_beat(TASK_WD_IEC104);
|
||||
s_serv_poll();
|
||||
/* 定值改动(C_SE 执行段 / YK12)→ 通知属主任务落盘 /prot_set.bin(B7)。
|
||||
* peek 非消费:写盘失败时脏标志仍在,下一轮再投递 = 天然重试;
|
||||
* 本任务不碰文件系统(配置引擎属主 = defaultTask,模块约定) */
|
||||
if ((prot_set_dirty_peek() != 0u) && (sys_evtHandle != NULL)) {
|
||||
(void)osEventFlagsSet(sys_evtHandle, SYS_EVT_PROT_SET_SAVE);
|
||||
}
|
||||
osDelay(IEC104_POLL_MS);
|
||||
}
|
||||
}
|
||||
|
||||
+22
-5
@@ -5,7 +5,9 @@
|
||||
* 复位(防陈旧判据)+ 本拍不 eval;
|
||||
* ② prot_set_selfcheck()(L5-1 定值完整性巡检,31 项 sum32,开销
|
||||
* 可忽略;失配 → prot_set 内部闭锁,eval 自动复位全元件);
|
||||
* ③ prot_elm_run()(phasor rms → 判据步进)+ 动作位图边沿日志。
|
||||
* ③ prot_elm_run()(phasor rms → 判据步进)+ 动作位图边沿日志;
|
||||
* ④ 全局软压板 PS_GLOBAL_EN(104 YK12 两段式投退,B7):退出 =
|
||||
* 复位元件 + 停判据(与采样闭锁同构,独立边沿日志)。
|
||||
* 定值/元件初始化在 app_main_init(内核前,B5);本任务只消费。
|
||||
* 线程约定:prot_elm_run / prot_set_selfcheck 仅本任务上下文(prot_elm.h);
|
||||
* 跨上下文复归(104 YK11)经 prot_elm_request_reset 落请求。
|
||||
@@ -31,6 +33,7 @@
|
||||
|
||||
static uint32_t s_prev_active; /* 上拍动作位图(边沿日志用) */
|
||||
static uint8_t s_prev_hf_lock; /* 上拍采样闭锁态(边沿日志用) */
|
||||
static uint8_t s_prev_plate_out; /* 上拍全局软压板退出态(边沿日志用,B7) */
|
||||
|
||||
/*
|
||||
* 函数功能:动作位图变化沿日志(逐变化元件打名字与状态)
|
||||
@@ -90,11 +93,25 @@ void protect_task(void *argument) {
|
||||
s_prev_hf_lock = 0u;
|
||||
}
|
||||
/* L5-1 巡检:定值区与封存校验和不符 → prot_set 闭锁(下一拍起
|
||||
* eval 自动复位全元件)。⚠ 与 104 定值写(iec104Task set+seal)
|
||||
* 的竞态窗口为 µs 级:selfcheck 误锁后同一次写的 seal 即清锁
|
||||
* (prot_set.c),V0.1 如实登记,P4 定值持久化时收口。 */
|
||||
* eval 自动复位全元件)。B7 收口:104 定值写改走
|
||||
* prot_set_set_sealed(影子区写+封存后单字交换),selfcheck 另有
|
||||
* seqlock 式复核(提交撞见 = 本轮跳过),误锁窗口已消除。 */
|
||||
(void)prot_set_selfcheck();
|
||||
prot_elm_run(now);
|
||||
/* 全局软压板(PS_GLOBAL_EN,104 YK12 两段式投退,B7):退出 =
|
||||
* 保护停止判据并复位元件状态(重新投入从干净快照起步) */
|
||||
if (prot_set_get_ctl(PS_GLOBAL_EN) == 0u) {
|
||||
if (s_prev_plate_out == 0u) {
|
||||
DBG_INFO("global plate OUT -> protection disabled");
|
||||
s_prev_plate_out = 1u;
|
||||
}
|
||||
prot_elm_init();
|
||||
} else {
|
||||
if (s_prev_plate_out != 0u) {
|
||||
DBG_INFO("global plate IN -> protection enabled");
|
||||
s_prev_plate_out = 0u;
|
||||
}
|
||||
prot_elm_run(now);
|
||||
}
|
||||
}
|
||||
|
||||
s_log_active_edge(prot_elm_active_mask());
|
||||
|
||||
Reference in New Issue
Block a user