Files
STM32F4-Base/App/algo/prot_elm.c
T
wandering1 ffd462ef3c feat(保护标定): 卡5 未标定闭锁 + 卡6 下游三常数重定 + 卡7 量纲回归单测
卡 5(protect_task.c 第⑤闭锁源,「数据可信」§4.3 决策 1=闭锁):
- UA/UN/IA 的 adc_cal VALID 未全置位 → prot_elm_init + 本拍不 eval;每拍现查
  flags、无自有状态(卡 12 标定写入后自动解锁);判据链优先级 = 采样可信在前、
  标定可用在后(数据都是旧的谈不上标定)
- 边沿日志用 DBG_INFO 而非 [ERR]:未标定是出厂常态、非故障,且 @test 禁用
  [ERR](「默认构建保护不动作 = 期望」的台架判据同步项已在 MR 报 lead);
  SOE 分型 SOE_T_CAL_BLOCK/RESUME(soe_rec.h 枚举尾追加,值 6/7——reader 侧
  显示标签映射归道B reader,MR 已报)
- 台架旁路 = 编译期宏 DTU_BENCH(§4.3:不进 cfg 不进 104;本仓所有提交目标
  都不定义它 = 产品恒强制闭锁)

卡 6(下游三常数按工程量重定,依据同源 docs/任务规划/数据可信.md §2.3):
- FREQ_AMP_MIN(freq.h):语义从拟定码值域重定为二次值 V——50 V = 0.5×Un
  (标称 100 V),断保险/PT 空载/浮空必低于之;数值沿用 50.0f 属巧合并在
  注释明写;eng=0 过渡期本门恒失效 → OF/UF 按必不启动填充,与卡 5 同向安全
- k_yc[].deadband(iec104_pts.c):9 点按「额定 0.5 %」规则重定:电压类
  0.5 %×100 V=0.5、电流类 0.5 %×1 A=0.005、频率维持 0.5 Hz 绝对值(非额定
  比,卡 10 运行参数化);标称基准与 prot_set 头部同源,防两处真相
- g_prot_set_meta[](prot_set.c):数值/范围复核结论=不动(0~4×标称自洽),
  文件头补量纲口径块:A/V = 二次值、标称 100 V/1 A、OV120=1.2Un/UV20=.2Un/
  3U0 15=.15Un、OC1/2/3=10/5/2×Ir、OL=1.0×Ir;PS_ID_COUNT/blob VER 未动
- iec104 套件 TC-008 死区用例同步重定标(100.4 带内 / 101.25 越带),97/97

卡 7(test/host/prot_dim 新套件,真实链 adc_cal→ring→phasor→prot_elm):
- 用例从 ADC 码值出发、显式写出 code↔二次值互逆换算(dim_code vs apply),
  折算点被旁路或量纲再错位断言立刻翻转(M4 判据⑥「不该动作时不动作」)
- 合成 ROM 覆写(#undef K_Q16 宏)模拟卡 12 标定后板级口径,走 adc_cal_set
  正规路径置 VALID;b=-30 码全链对消进 rms 断言
- 38/38:基线静默(含 OL 低定值陷阱)、反错配对照(raw 码值灌环复现历史事故
  必假动作 vs 经折算必静默)、OC1/2/3+OL+OV+UV+3U0 七类逐一:越限定值→
  受控启动拍→dly-2 不动作/+5 动作→返回系数带内保持→跌破释放、双件聚合位图
  独立性(撤 IA 只回 OC1)

测试与注册:prot-dim-unit-test 钩子 + TC-HOST-PROTDIM 族(游标 010,B 层口径
同 ADCHEALTH/ADCCAL 先例);无新 MCU 源文件(uvprojx/SIM_EXEMPT 零改动)

gate:@build 0 错 0 警(Code=215640 RO=21388 RW=1220 ZI=131980 vs 基线
215440/19780/1220/131980 → Flash +1808 B、RAM 零增量);protectTask 静态最坏
链 872 B/margin 1176 B(57%)PASS,adcTask 不变;staged 门禁除 mem-numbers/
mem-budget(台账漂移,回填归 lead)外全绿:
gate: only mem-budget/mem-numbers pending lead ledger backfill
BOM/CR/U+FFFD/字面量 ASCII 已逐文件自查(本轮未触碰带 BOM 文件)
2026-10-25 05:22:17 +08:00

273 lines
9.8 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称:保护元件框架(L3,P1 子集)——实现
* 模块功能:见 prot_elm.h。判据核 prot_elm_eval() 为纯逻辑(prot_set RAM +
* 测量数组 → 状态机),prot_elm_run() 是 phasor→测量的薄适配层。
*
* 创建日期:2026-10-11
*/
#include "prot_elm.h"
#include "prot_set.h"
#include "phasor.h"
#include "freq.h"
#include <stddef.h>
/* 实例配置(ROM 常量表,数据驱动——新增元件只加一行,同 phasor k_inst 模式) */
typedef struct {
const char *name; /* 日志名 */
uint8_t over; /* 1 = 过量(越上限动作),0 = 欠量(越下限动作) */
uint8_t src; /* 测量源:0 = phasor 基波有效值,1 = freq 测频 */
prot_set_id_t en_id; /* 投退定值 */
prot_set_id_t set_id; /* 动作定值 */
prot_set_id_t dly_id; /* 延时定值(ms) */
prot_set_id_t ret_id; /* 返回系数定值(RET_OVER / RET_UNDER) */
const char *pprefix; /* 相量实例名前缀("C01"…);src=1 时为 0(不用) */
} pe_cfg_t;
/* 测量源枚举(pe_cfg_t.src) */
#define PE_SRC_PHASOR 0u
#define PE_SRC_FREQ 1u
/* freq 无有效读数时给欠量元件的"必不启动"填充值(远大于任何欠量定值上限) */
#define PE_MEAS_BIG 1e30f
static const pe_cfg_t k_pe[PE_COUNT] = {
{ "OC1-IA", 1u, PE_SRC_PHASOR, PS_OC1_EN, PS_OC1_CUR, PS_OC1_DLY, PS_RET_OVER, "C01" },
{ "OC2-IA", 1u, PE_SRC_PHASOR, PS_OC2_EN, PS_OC2_CUR, PS_OC2_DLY, PS_RET_OVER, "C01" },
{ "OC3-IA", 1u, PE_SRC_PHASOR, PS_OC3_EN, PS_OC3_CUR, PS_OC3_DLY, PS_RET_OVER, "C01" },
{ "OL-IA", 1u, PE_SRC_PHASOR, PS_OL_EN, PS_OL_CUR, PS_OL_DLY, PS_RET_OVER, "C01" },
{ "OV-UA", 1u, PE_SRC_PHASOR, PS_OV_EN, PS_OV_VOL, PS_OV_DLY, PS_RET_OVER, "C02" },
{ "UV-UA", 0u, PE_SRC_PHASOR, PS_UV_EN, PS_UV_VOL, PS_UV_DLY, PS_RET_UNDER, "C02" },
{ "3U0-UN", 1u, PE_SRC_PHASOR, PS_3U0_EN, PS_3U0_VOL, PS_3U0_DLY, PS_RET_OVER, "C03" },
/* 过频/低频(L3-5,B8):测量量 = freq 层测得频率(Hz),返回系数独立
* (RET_OF/RET_UF——频率滞回带与电气量不同量级,勿共用 0.95/1.05) */
{ "OF-F", 1u, PE_SRC_FREQ, PS_OF_EN, PS_OF_HZ, PS_OF_DLY, PS_RET_OF, 0 },
{ "UF-F", 0u, PE_SRC_FREQ, PS_UF_EN, PS_UF_HZ, PS_UF_DLY, PS_RET_UF, 0 },
};
/* 运行时状态(picked/asserted 的对外快照 + 延时起点 + 相量实例索引缓存) */
typedef struct {
uint8_t enabled;
uint8_t picked;
uint8_t asserted;
uint32_t t0; /* picked 起始时基(picked=0 时无意义) */
uint8_t pidx; /* 相量实例索引;0xFF = 未解析/未找到 */
} pe_rt_t;
static pe_rt_t s_rt[PE_COUNT];
/* 跨上下文复归请求(单字标志,volatile;B5:104 YK11 在 iec104Task 落请求) */
static volatile uint8_t s_reset_req;
/*
* 函数功能:按名前缀在 phasor 实例表里找索引(开机解析一次)
* 入口参数:prefix - 相量实例名前缀(如 "C01")
* 返 回 值:实例索引;0xFF = 未找到(run() 对该元件整拍跳过)
*/
static uint8_t pe_find_phasor(const char *prefix) {
uint8_t i, n = phasor_inst_count();
for (i = 0u; i < n; i++) {
const phasor_inst_t *pi = phasor_inst(i);
uint8_t j = 0u, hit = 1u;
if (pi == 0) {
continue;
}
while ((prefix[j] != '\0') && (pi->name[j] != '\0')) {
if (prefix[j] != pi->name[j]) {
hit = 0u;
break;
}
j++;
}
if ((hit != 0u) && (prefix[j] == '\0')) {
return i;
}
}
return 0xFFu;
}
/*
* 函数功能:复位全部元件状态并解析相量索引(定值不动;幂等)
* 入口参数:无
* 返 回 值:无
* 限定条件:须在 phasor_init() 之后调用(否则实例表名查不到 → 全 0xFF)
*/
void prot_elm_init(void) {
uint32_t i;
for (i = 0u; i < (uint32_t)PE_COUNT; i++) {
s_rt[i].enabled = 0u;
s_rt[i].picked = 0u;
s_rt[i].asserted = 0u;
s_rt[i].t0 = 0u;
/* src=FREQ 的行无相量绑定(pprefix=0)→ pidx 置未解析(run 不查它) */
s_rt[i].pidx = (k_pe[i].pprefix != 0) ? pe_find_phasor(k_pe[i].pprefix) : 0xFFu;
}
}
/*
* 函数功能:登记"复归元件状态"请求(任意上下文安全,见 prot_elm.h)
* 入口参数:无
* 返 回 值:无
* 函数说明:只置单字标志;真正的状态复位在下一拍 eval 头部(消费者上下文)执行。
*/
void prot_elm_request_reset(void) {
s_reset_req = 1u;
}
/*
* 函数功能:纯判据步进——按当前定值 + 给定测量值推进所有元件
* 入口参数:now_ms - 单调时基(tick ms,差值法回绕安全)
* meas - PE_COUNT 个测量值(工程量 A/V);NULL 视为全 0
* 返 回 值:无
* 函数说明:投退=0 或定值闭锁(L5-1)→ 状态整体复位、不判据;
* 滞回 = 已启动用返回门槛(定值×系数)、未启动用动作门槛;
* 延时 dly=0 → 启动同拍即动作;返回即时(P4 接出口时再补返回延时)。
*/
void prot_elm_eval(uint32_t now_ms, const float meas[PE_COUNT]) {
uint8_t locked = prot_set_locked();
uint32_t i;
/* 跨上下文复归请求在此消费(YK11 经 request_reset,见 prot_elm.h) */
if (s_reset_req != 0u) {
s_reset_req = 0u;
prot_elm_init();
locked = prot_set_locked();
}
for (i = 0u; i < (uint32_t)PE_COUNT; i++) {
const pe_cfg_t *cfg = &k_pe[i];
uint8_t cond;
float m, set, ret;
s_rt[i].enabled = ((locked == 0u) && (prot_set_get_ctl(cfg->en_id) != 0u)) ? 1u : 0u;
if (s_rt[i].enabled == 0u) {
/* 退出/闭锁:状态整体复位(参考元件闭锁分支的 Rly/Timer 复位) */
s_rt[i].picked = 0u;
s_rt[i].asserted = 0u;
s_rt[i].t0 = 0u;
continue;
}
m = (meas != 0) ? meas[i] : 0.0f;
set = prot_set_get_f(cfg->set_id);
ret = set * prot_set_get_f(cfg->ret_id);
/* 滞回:已启动用返回门槛,未启动用动作门槛(参考 M_GRly_OverRelay) */
if (cfg->over != 0u) {
cond = (s_rt[i].picked != 0u) ? (uint8_t)(m > ret) : (uint8_t)(m > set);
} else {
cond = (s_rt[i].picked != 0u) ? (uint8_t)(m < ret) : (uint8_t)(m < set);
}
if (cond != 0u) {
float dly;
if (s_rt[i].picked == 0u) {
s_rt[i].picked = 1u;
s_rt[i].t0 = now_ms;
}
dly = prot_set_get_f(cfg->dly_id);
if (dly < 0.0f) {
dly = 0.0f;
}
/* 差值法比较,tick 回绕安全;dly=0 → 启动同拍即动作 */
if ((uint32_t)(int32_t)(now_ms - s_rt[i].t0) >= (uint32_t)dly) {
s_rt[i].asserted = 1u;
}
} else {
s_rt[i].picked = 0u;
s_rt[i].asserted = 0u;
s_rt[i].t0 = 0u;
}
}
}
/*
* 函数功能:适配层——按实例表 src 组装测量数组后调判据核
* 入口参数:now_ms - 单调时基(tick ms)
* 返 回 值:无
* 限定条件:仅保护消费者任务上下文;测量已是 L1.5 工程量(未标定域由
* protectTask 卡 5 闭锁挡在门外,本函数不感知标定态)
* 函数说明:src=PHASOR 行任一相量未就绪(未满一周波)→ **整拍跳过**,
* 不引入陈旧/半新测量(B5 口径)。src=FREQ 行:freq 有有效读数
* (started 且幅值门 OK)→ 填测得频率;否则按方向填"必不启动"值
* (过量 0 / 欠量 PE_MEAS_BIG → 滞回比较恒假 → 元件复位),
* 不整拍跳过——频率源缺失不得饿死电流/电压类元件。
*/
void prot_elm_run(uint32_t now_ms) {
float meas[PE_COUNT];
phasor_t p;
uint32_t i;
for (i = 0u; i < (uint32_t)PE_COUNT; i++) {
if (k_pe[i].src == PE_SRC_FREQ) {
float f;
if ((freq_get(&f) == 0) && (freq_valid() != 0u)) {
meas[i] = f;
} else {
meas[i] = (k_pe[i].over != 0u) ? 0.0f : PE_MEAS_BIG;
}
continue;
}
if ((s_rt[i].pidx == 0xFFu) || (phasor_get(s_rt[i].pidx, &p) != 0)) {
return;
}
meas[i] = p.fAmSQ;
}
prot_elm_eval(now_ms, meas);
}
/*
* 函数功能:读单元件状态快照(拷贝语义)
* 入口参数:id - 元件编号;out - 快照输出(NULL 直接返回)
* 返 回 值:无;越界编号输出全零
*/
void prot_elm_state(prot_elm_id_t id, prot_elm_state_t *out) {
if (out == 0) {
return;
}
out->enabled = 0u;
out->picked = 0u;
out->asserted = 0u;
out->pick_ms = 0u;
if (id < PE_COUNT) {
out->enabled = s_rt[(uint32_t)id].enabled;
out->picked = s_rt[(uint32_t)id].picked;
out->asserted = s_rt[(uint32_t)id].asserted;
out->pick_ms = s_rt[(uint32_t)id].t0;
}
}
/*
* 函数功能:聚合动作位图
* 入口参数:无
* 返 回 值:bit i = 元件 i asserted(供 L4 出口 / 心跳日志)
*/
uint32_t prot_elm_active_mask(void) {
uint32_t mask = 0u;
uint32_t i;
for (i = 0u; i < (uint32_t)PE_COUNT; i++) {
if (s_rt[i].asserted != 0u) {
mask |= (1uL << i);
}
}
return mask;
}
/*
* 函数功能:取实例名(日志用)
* 入口参数:id - 元件编号
* 返 回 值:ROM 常量名;越界编号返回 "PE??"
*/
const char *prot_elm_name(prot_elm_id_t id) {
if (id >= PE_COUNT) {
return "PE??";
}
return k_pe[(uint32_t)id].name;
}