diff --git a/App/protocol/iec104/iec104_pts.c b/App/protocol/iec104/iec104_pts.c index 8269fdbd..dfe91a78 100644 --- a/App/protocol/iec104/iec104_pts.c +++ b/App/protocol/iec104/iec104_pts.c @@ -1,8 +1,9 @@ /* * 模块名称:IEC 60870-5-104 点表与数据源适配(实现) * 模块功能:实现 iec104_pts.h——YC/YX/YK/定值点表(ROM)、板端默认源 - * (phasor / prot_elm / prot_set)、COV 快照比对(遥信变位 + - * 遥测死区)。点表口径见 iec104_pts.h 文件头与设计文档 §3。 + * (**L2 遥测快照**(卡 9)/ prot_elm / prot_set)、COV 比对(遥信变位 + + * 遥测死区 2 点确认 + 品质变化)。点表口径见 iec104_pts.h 文件头与 + * 设计文档 §3。 * 线程约定:init/bind 建服务前;其余仅 104 服务任务上下文(快照非原子)。 * 创建日期:2026-10-12(B4) * 修改记录: @@ -10,47 +11,71 @@ * 二次值工程量域(额定 0.5 % 规则),判据依据见 * k_yc 表头注释;数值随卡 9 迁运行参数,本表将退化为 * ROM 默认载体。 + * 2026-10-25 DTU 项目组 卡 9/卡 11(「数据可信」,道D):YC 板端源改读 + * L2 遥测快照(App/algo/meas.h,meas_get 无锁双区); + * 削顶五路由快照品质 CLIPPED 驱动线上 IV(不再绑死 -1, + * V0.2/N-18 零改动自动启用);0x401d 绑频率源(卡 11); + * 死区改运行时副本 + set/get 入口;COV 遥测腿 = 死区 + * 连续 2 点确认(板端按快照 seq 去重)+ 品质翻转即报。 + * 单测 = test/host/iec104 TC-IEC104-025~028。 */ #include "iec104_pts.h" #include #include "iec104_asdu.h" /* QDS/DIQ 品质位常量 */ -#include "phasor.h" /* 板端源:遥测 = 基波有效值 */ +#include "meas.h" /* 板端源:遥测 = L2 遥测快照(卡 9;品质域位同源) */ #include "prot_elm.h" /* 板端源:遥信 = 元件状态 */ #include "prot_set.h" /* 板端源:定值 + 闭锁 */ #include "adc_input.h" /* adc_semantic_t(phasor 实例按语义绑定) */ -/* ---------------- YC 表(§1.5 点号 4011+;死区 = 二次值工程量域) --------- */ +/* ---------------- YC 表(§1.5 点号 4011+;死区列 = ROM 默认值) ------------ */ +/* 源类型(「数据可信」卡 11:YC 表加 src 类型区分相量/频率) */ +#define PTS_SRC_MEAS 0u /* L2 快照槽 sem(电气量,yc/qds 直读) */ +#define PTS_SRC_FREQ 1u /* 快照频率(freq_valid 门 → IV) */ + typedef struct { uint32_t ioa; - int8_t sem; /* 绑定的 phasor 语义通道(-1 = 无 phasor 源,恒无效) */ - float deadband; /* 死区(二次值工程量绝对值,「数据可信」卡 6 重定) */ + int8_t sem; /* 快照 yc 槽 = 语义通道(-1 = 非测量量点,当前仅频率) */ + float db_default; /* 死区 ROM 默认(运行时生效值 = s_yc_db,卡 10 覆盖) */ + uint8_t src; /* PTS_SRC_* 源类型 */ } pts_yc_def_t; /* 死区规则 = 额定二次值的 0.5 %(「数据可信」§2.3 表第 2 行建议规则,卡 6 采纳; * 额定基准 100 V / 1 A 与 App/algo/prot_set.c 头部同源,勿在此另立口径): * 电压类 0.5 % × 100 V = 0.5 V 电流类 0.5 % × 1 A = 0.005 A * 频率点不适用"额定百分比"(额定附近 0.5 Hz 与旧值同量级,维持 0.5 并随 - * 卡 10 运行参数段可调)。IV 品质点(sem=-1)数值仅为将来启用预留同型。 + * 卡 10 运行参数段可调)。自卡 9 起本表**只作 ROM 默认载体**:运行时死区 = + * s_yc_db 副本;削顶五路不再 sem=-1 绑死,而是绑快照槽、由品质 CLIPPED + * 驱动线上 IV——V0.2(N-18)削顶消失后点位零改动自动启用(docs/任务规划/ + * 数据可信.md §10 的 N-18 行)。 * ⚠ 过渡期(eng=0 引脚电压域,卡 12 标定前):上送值本身非二次值,死区 - * 按本域判定无意义但也不会漏报——104 侧 IV/数据可信度语义由 M5 快照层 - * (卡 8/9)统一处理,本卡只把"量纲错位"这一处常量债还掉。 */ + * 数字按本域偏保守(迟报不漏报);标定落地后复核(「数据可信」§2.3 表 + * 第 2 行)。判定 = 死区连续 2 点确认(卡 9),见 iec104_pts.h COV 说明。 */ static const pts_yc_def_t k_yc[] = { - { 0x4011u, (int8_t)ADC_SEM_UA, 0.5f }, /* 1Ua:0.5 % × 100 V */ - { 0x4012u, -1, 0.5f }, /* 1Ub:V0.1 削顶 → IV */ - { 0x4013u, -1, 0.5f }, /* 1Uc:同上 */ - { 0x4014u, (int8_t)ADC_SEM_UN, 0.5f }, /* 1U0(外部零序专用通道) */ - { 0x4015u, (int8_t)ADC_SEM_IA, 0.005f }, /* 1Ia:0.5 % × 1 A */ - { 0x4016u, -1, 0.005f }, /* 1Ib */ - { 0x4017u, -1, 0.005f }, /* 1Ic */ - { 0x4018u, -1, 0.005f }, /* 1I0 */ - { 0x401Du, -1, 0.5f }, /* 1f:Hz 绝对死区(非额定比)→ IV */ + { 0x4011u, (int8_t)ADC_SEM_UA, 0.5f, PTS_SRC_MEAS }, /* 1Ua:0.5 % × 100 V */ + { 0x4012u, (int8_t)ADC_SEM_UB, 0.5f, PTS_SRC_MEAS }, /* 1Ub:削顶 → 品质驱动 IV */ + { 0x4013u, (int8_t)ADC_SEM_UC, 0.5f, PTS_SRC_MEAS }, /* 1Uc:同上 */ + { 0x4014u, (int8_t)ADC_SEM_UN, 0.5f, PTS_SRC_MEAS }, /* 1U0(外部零序专用通道) */ + { 0x4015u, (int8_t)ADC_SEM_IA, 0.005f, PTS_SRC_MEAS }, /* 1Ia:0.5 % × 1 A */ + { 0x4016u, (int8_t)ADC_SEM_IB, 0.005f, PTS_SRC_MEAS }, /* 1Ib:削顶 → 品质驱动 IV */ + { 0x4017u, (int8_t)ADC_SEM_IC, 0.005f, PTS_SRC_MEAS }, /* 1Ic:同上 */ + { 0x4018u, (int8_t)ADC_SEM_IN, 0.005f, PTS_SRC_MEAS }, /* 1I0:同上 */ + { 0x401Du, -1, 0.5f, PTS_SRC_FREQ }, /* 1f:卡 11 绑频率源, + * freq_valid=0 → IV */ }; #define YC_N ((uint8_t)(sizeof(k_yc) / sizeof(k_yc[0]))) -/* phasor 实例下标缓存(init 时按 sem+harm==1 查表;-1 = 未找到) */ -static int8_t s_yc_phasor_idx[YC_N]; +/* ===== 遥测 COV 运行时状态(卡 9;仅 iec104Task 上下文访问,无并发) ===== + * 判据链:新测量点 → |v − 上次上送值| > 死区?→ 连续 2 点确认 → 置变化位。 + * 基准 = **上次上送值**(非上次读值——否则慢漂移永不出死区,参考系统同款); + * 品质(线上 qds)翻转 = 事件:不参与 2 点计数、当拍直报。 */ +static float s_yc_db[YC_N]; /* 死区运行时副本(ROM 默认 = k_yc 列) */ +static float s_yc_rep[YC_N]; /* 上次上送值(建线/确认上送时更新) */ +static uint8_t s_yc_repq[YC_N]; /* 上次上送的线上 qds */ +static uint8_t s_yc_cnf[YC_N]; /* 越死区连续计数(1 = 待确认;2 = 确认) */ +static uint32_t s_yc_seq; /* COV 已消费到的快照 seq(板端源测点去重) */ +static uint8_t s_yc_seqmode; /* 1 = 板端默认源(测点 = meas 换帧) */ /* ---------------- YX 表(IOA = 1..33;双点 = 开关位置类) ------------------ */ #define YX_N 33u @@ -61,63 +86,65 @@ static int8_t s_yc_phasor_idx[YC_N]; | (1uLL << 17u) | (1uLL << 22u) | (1uLL << 30u))) /* ---------------- 数据源(默认 = 板端;bind_src 可整体替换) -------------- */ -static int s_src_yc_read(uint8_t idx, float *out); +static int s_src_yc_read_ex(uint8_t idx, float *out, uint16_t *qds); static uint64_t s_src_yx_bits(void); static uint64_t s_src_yx_valid(void); static int s_src_yk_exec(uint32_t ioa, uint8_t dpc, uint8_t select); static int s_src_set_read(uint32_t addr, float *out); static int s_src_set_write(uint32_t addr, float v, uint8_t select); +/* 板端默认源:遥测走**带品质**的 ex 通道(legacy yc_read 槽置 NULL—— + * s_yc_point 优先 ex;注入源两种槽位均可,见 iec104_pts.h) */ static const iec104_pts_src_t k_default_src = { - s_src_yc_read, s_src_yx_bits, s_src_yx_valid, - s_src_yk_exec, s_src_set_read, s_src_set_write + NULL, s_src_yx_bits, s_src_yx_valid, + s_src_yk_exec, s_src_set_read, s_src_set_write, s_src_yc_read_ex }; static const iec104_pts_src_t *s_src; -/* ---------------- COV 快照 ---------------- */ -static float s_yc_last[YC_N]; -static uint16_t s_yc_have; /* bit i = 已有上次快照 */ +/* ---------------- COV 快照(遥信侧;遥测侧状态见上方 s_yc_* 块) --------- */ +static uint16_t s_yc_have; /* bit i = 已建立上送基线 */ static uint64_t s_yx_last; static uint8_t s_yx_snap_ok; /* ============================ 公开接口 ================================== */ /* - * 函数功能:初始化点表模块(扫 phasor 基波实例映射、恢复默认源、清快照) + * 函数功能:初始化点表模块(死区装 ROM 默认、清 COV/快照状态、恢复默认源) + * 限定条件:内核启动前或 104 服务启动前,调一次 + * 函数说明:卡 9 起不再扫 phasor 实例(数据源 = 快照,槽位 = 语义通道,编译期 + * 直接可定);重复调用 = 全状态复位(单测 reset 依赖此语义)。 */ void iec104_pts_init(void) { uint8_t i; - uint8_t n; - (void)memset(s_yc_phasor_idx, -1, sizeof(s_yc_phasor_idx)); - n = phasor_inst_count(); for (i = 0u; i < YC_N; i++) { - uint8_t j; - - if (k_yc[i].sem < 0) { - continue; - } - for (j = 0u; j < n; j++) { - const phasor_inst_t *inst = phasor_inst(j); - - if ((inst != NULL) && ((uint8_t)inst->sem == (uint8_t)k_yc[i].sem) - && (inst->harm == 1u)) { - s_yc_phasor_idx[i] = (int8_t)j; - break; - } - } + s_yc_db[i] = k_yc[i].db_default; } - s_src = &k_default_src; - s_yc_have = 0u; + (void)memset(s_yc_rep, 0, sizeof(s_yc_rep)); + (void)memset(s_yc_repq, 0, sizeof(s_yc_repq)); + (void)memset(s_yc_cnf, 0, sizeof(s_yc_cnf)); + s_yc_have = 0u; + s_yc_seq = 0u; + s_yc_seqmode = 1u; + s_src = &k_default_src; s_yx_snap_ok = 0u; - s_yx_last = 0u; + s_yx_last = 0u; } /* * 函数功能:替换数据源(NULL = 恢复板端默认源;单测注入假源) + * 函数说明:注入源同时把 COV 测点口径切为**每次调用一测点**(假源无快照节拍, + * 步进由调用方控制);默认源 = 测点按 meas 换帧(seq 去重)。换源 + * 不重建基线(基线状态统一由 init 复位,防跨源半截确认序列)。 */ void iec104_pts_bind_src(const iec104_pts_src_t *src) { - s_src = (src != NULL) ? src : &k_default_src; + if (src != NULL) { + s_src = src; + s_yc_seqmode = 0u; + } else { + s_src = &k_default_src; + s_yc_seqmode = 1u; + } } /* @@ -135,35 +162,105 @@ uint32_t iec104_pts_yc_ioa(uint8_t idx) { } /* - * 函数功能:读遥测(值 + 品质 descriptor;源 -2 → NT,其余失败 → IV) - * 返 回 值:0 有效 / -1 无效 + * 函数功能:品质域位映射(uint16 meas 域 → uint8 线上 QDS,卡 9) + * 入口参数:q16 - 源侧品质(低字节与线上同值;高字节为 MEAS_QDS_CLIPPED 域内位) + * 返 回 值:线上 QDS 字节 uint8_t + * 函数说明:CLIPPED(硬件削顶,104 无对应位)映射为 IV——「削顶通道 104 恒 + * IV」口径(docs/任务规划/数据可信.md §8 风险表末行)改由快照品质 + * 驱动,不再靠点表绑死 -1;OV/NT/IV 透传,其余高位丢弃。 */ -int iec104_pts_yc_read(uint8_t idx, float *v, uint8_t *qds) { - int rc; +static uint8_t s_q_map(uint16_t q16) { + uint8_t w = (uint8_t)(q16 & (uint16_t)(IEC104_QDS_OV | IEC104_QDS_NT + | IEC104_QDS_IV)); - if (v != NULL) { - *v = 0.0f; + if ((q16 & (uint16_t)MEAS_QDS_CLIPPED) != 0u) { + w |= (uint8_t)IEC104_QDS_IV; } - if ((idx >= YC_N) || (s_src == NULL) || (s_src->yc_read == NULL)) { - if (qds != NULL) { - *qds = IEC104_QDS_IV; - } + return w; +} + +/* + * 函数功能:读单点遥测(统一源通道 + 品质归一,yc_read 与 COV 共用唯一读径) + * 入口参数:idx - YC 表下标 v/q - 输出(值 / 线上 qds;调用方保证非空) + * 返 回 值:0 有效;-1 IV(含 CLIPPED 映射);-2 NT(未就绪) int + * 函数说明:ex 源在场 → 品质由源给(板端 = 快照槽位透传);否则退 legacy + * yc_read,品质从返回值推导(-1→IV / -2→NT / 0→无位)——旧注入源 + * 行为与卡 9 前**完全一致**(单测假源零改动)。判定顺序 IV > NT: + * 开机首帧 qds=IV|NT 按无效处理(NT 只留给纯暂态)。任何无效 → + * 信息体清零(线上口径:主站只信品质)。 + */ +static int s_yc_point(uint8_t idx, float *v, uint8_t *q) { + int rc; + uint16_t q16 = 0u; + + if ((idx >= YC_N) || (s_src == NULL) + || ((s_src->yc_read == NULL) && (s_src->yc_read_ex == NULL))) { + *v = 0.0f; + *q = (uint8_t)IEC104_QDS_IV; return -1; } - rc = s_src->yc_read(idx, v); - if (rc == 0) { - if (qds != NULL) { - *qds = 0u; - } - return 0; + if (s_src->yc_read_ex != NULL) { + rc = s_src->yc_read_ex(idx, v, &q16); + } else { + rc = s_src->yc_read(idx, v); + q16 = (rc == -2) ? (uint16_t)MEAS_QDS_NT + : ((rc != 0) ? (uint16_t)MEAS_QDS_IV : 0u); } - if (v != NULL) { + (void)rc; /* 有效性由归一后的 q 终裁(IV/NT 即无效),源返回值不直通 */ + *q = s_q_map(q16); + rc = ((*q & (uint8_t)IEC104_QDS_IV) != 0u) ? -1 + : (((*q & (uint8_t)IEC104_QDS_NT) != 0u) ? -2 : 0); + if (rc != 0) { *v = 0.0f; } - if (qds != NULL) { - *qds = (rc == -2) ? IEC104_QDS_NT : IEC104_QDS_IV; + return rc; +} + +/* + * 函数功能:读遥测(值 + 线上品质位;无效值清零,判据见 s_yc_point) + * 返 回 值:0 有效 / -1 无效(NT 亦无效,品质位与 IV 区分) int + */ +int iec104_pts_yc_read(uint8_t idx, float *v, uint8_t *qds) { + float val = 0.0f; + uint8_t q = (uint8_t)IEC104_QDS_IV; + + (void)s_yc_point(idx, &val, &q); + if (v != NULL) { + *v = val; } - return -1; + if (qds != NULL) { + *qds = q; + } + return (q == 0u) ? 0 : -1; +} + +/* + * 函数功能:写死区运行时值(「数据可信」卡 9;卡 10 运行参数段的落点入口) + * 入口参数:idx - YC 表下标 db - 工程量绝对死区(≥0) float + * 返 回 值:0 成功;-1 越界/负值/NaN(RAM 不动) int + * 限定条件:**iec104Task 上下文**(配置属主 = defaultTask,卡 10 接线时经 + * 104 任务落地本调用,勿跨任务直调——本表状态无锁) + * 函数说明:成功时清零该点待确认计数——跨死区档位不保留半截确认序列; + * 不隐式重建基线(上送基准延续,新死区即刻生效)。 + */ +int iec104_pts_yc_set_deadband(uint8_t idx, float db) { + if ((idx >= YC_N) || !(db >= 0.0f) || !(db < 1000000.0f)) { + return -1; /* NaN 被 !(db>=0) 捕获(比较式写法,防 lint 报恒真) */ + } + s_yc_db[idx] = db; + s_yc_cnf[idx] = 0u; + return 0; +} + +/* + * 函数功能:读当前生效死区 + * 入口参数:idx - YC 表下标 + * 返 回 值:运行时死区;idx 越界返 0.0 float + * 限定条件:任意上下文(单字 float 读,Cortex-M 原子) + * 函数说明:UI 回显 / 卡 10 读写往返校验用;ROM 默认查点表,勿在此反推。 + */ +float iec104_pts_yc_deadband(uint8_t idx) { + return (idx < YC_N) ? s_yc_db[idx] : 0.0f; } /* @@ -238,28 +335,72 @@ void iec104_pts_cov_check(iec104_pts_cov_t *cov) { s_yx_snap_ok = 1u; } - /* YC:死区比对(仅对"本次有效 且 上次有快照"的点判定) */ - if ((s_src != NULL) && (s_src->yc_read != NULL)) { - uint8_t i; + /* YC(卡 9):测点推进 = 板端源按快照换帧(100 ms poll vs 200 ms 快照, + * 不去重则同一帧计两次 → "连续 2 点确认"退化成"连续 2 次轮询",单帧 + * 噪声即可被确认,抗噪语义失效);注入源 = 每次调用一测点。 + * 判定 = 死区连续 2 点确认 + 品质翻转直报(口径全文 → iec104_pts.h)。 */ + if ((s_src != NULL) + && ((s_src->yc_read != NULL) || (s_src->yc_read_ex != NULL))) { + uint8_t step = 1u; - for (i = 0u; i < YC_N; i++) { - float v; + if (s_yc_seqmode != 0u) { + uint32_t sq = meas_seq(); - if (s_src->yc_read(i, &v) != 0) { - continue; + if (sq == s_yc_seq) { + step = 0u; /* 未换帧:本 poll 不算新测量点 */ + } else { + s_yc_seq = sq; } - if (((s_yc_have >> i) & 1u) != 0u) { - float d = v - s_yc_last[i]; + } + if (step != 0u) { + uint8_t i; - if (d < 0.0f) { - d = -d; + for (i = 0u; i < YC_N; i++) { + float v; + uint8_t q; + int rc = s_yc_point(i, &v, &q); + + if (((s_yc_have >> i) & 1u) == 0u) { + if (rc == 0) { /* 首个有效读数只建线不上报(初值归总召) */ + s_yc_have |= (uint16_t)(1u << i); + s_yc_rep[i] = v; + s_yc_repq[i] = q; + s_yc_cnf[i] = 0u; + } + continue; } - if (d > k_yc[i].deadband) { + if (q != s_yc_repq[i]) { + /* 品质翻转 = 事件直报(含 0x401d 的 IV 进出,卡 11) */ local.yc_changed |= (uint16_t)(1u << i); + s_yc_repq[i] = q; + s_yc_cnf[i] = 0u; + if (rc == 0) { + s_yc_rep[i] = v; + } + continue; + } + if (rc != 0) { + s_yc_cnf[i] = 0u; /* 无效期间不计数(品质未变 = 状态延续) */ + continue; + } + { + float d = v - s_yc_rep[i]; + + if (d < 0.0f) { + d = -d; + } + if (d > s_yc_db[i]) { + s_yc_cnf[i]++; + if (s_yc_cnf[i] >= 2u) { /* 连续 2 越 → 确认:报 + 移基准 */ + local.yc_changed |= (uint16_t)(1u << i); + s_yc_rep[i] = v; + s_yc_cnf[i] = 0u; + } + } else { + s_yc_cnf[i] = 0u; /* 回摆入死区 → 撤销候选(抗噪核心) */ + } } } - s_yc_last[i] = v; - s_yc_have |= (uint16_t)(1u << i); } } @@ -304,24 +445,44 @@ int iec104_pts_set_write(uint32_t addr, float v, uint8_t select) { /* ============================ 板端默认源 ================================= */ /* - * 函数功能:板端默认源 - 遥测 = 基波相量有效值(fAmSQ) - * 返 回 值:0 / -1 无 phasor 源(削顶通道/频率未实现)/ -2 未满一周波(NT) + * 函数功能:板端默认源 - 遥测 = L2 遥测快照直读(「数据可信」卡 9/卡 11) + * 入口参数:idx - YC 表下标 out/qds - 值 + uint16 域品质(MEAS_QDS_*) + * 返 回 值:0 / -1 越界或频率未新鲜(IV)/ -2 快照从未发布(NT) int + * 限定条件:iec104Task 上下文(meas_get 为双区无锁读,App/algo/meas.h) + * 函数说明:**不做任何本地判断**——电气路值/品质整体透传快照槽(削顶路快照 + * 已置 CLIPPED,s_q_map 映上线 IV;开机首帧 IV|NT 由 s_yc_point 按 + * IV 处理),"新不新、能不能信"全在发布侧一处决定(卡 8 设计意图, + * 两处判断必打架)。频率路(PTS_SRC_FREQ):snap.freq_valid==0 → + * IV(卡 11 DoD 原句);有效时值 = snap.freq_hz,与 freq_get() 同源。 */ -static int s_src_yc_read(uint8_t idx, float *out) { - phasor_t p; - int8_t pi; +static int s_src_yc_read_ex(uint8_t idx, float *out, uint16_t *qds) { + meas_snap_t snap; - if ((idx >= YC_N) || (out == NULL)) { + if ((idx >= YC_N) || (out == NULL) || (qds == NULL)) { return -1; } - pi = s_yc_phasor_idx[idx]; - if (pi < 0) { - return -1; /* 无 phasor 源(削顶通道/频率未实现) */ + if (meas_get(&snap) != 0) { + *out = 0.0f; /* 从未发布(开机 <200 ms)= 暂态 */ + *qds = (uint16_t)(MEAS_QDS_IV | MEAS_QDS_NT); + return -2; } - if (phasor_get((uint8_t)pi, &p) != 0) { - return -2; /* 未满一周波:未就绪(NT) */ + if (k_yc[idx].src == (uint8_t)PTS_SRC_FREQ) { + if (snap.freq_valid == 0u) { + *out = 0.0f; + *qds = (uint16_t)MEAS_QDS_IV; + return -1; + } + *out = snap.freq_hz; + *qds = 0u; + return 0; } - *out = p.fAmSQ; /* 有效值口径(系数已折算) */ + if (k_yc[idx].sem < 0) { + *out = 0.0f; /* 理论不可达:现无未绑测量量点 */ + *qds = (uint16_t)MEAS_QDS_IV; + return -1; + } + *out = snap.yc[(uint8_t)k_yc[idx].sem]; + *qds = snap.qds[(uint8_t)k_yc[idx].sem]; return 0; } diff --git a/App/protocol/iec104/iec104_pts.h b/App/protocol/iec104/iec104_pts.h index d2438bd8..d11a13b6 100644 --- a/App/protocol/iec104/iec104_pts.h +++ b/App/protocol/iec104/iec104_pts.h @@ -5,9 +5,12 @@ * phasor/prot_elm/prot_set 真实源;主机单测绑假源 → 协议栈全链 * (asdu+pts+link)可离线跑(登记簿 A-4 约束 1)。 * 点表口径(docs/算法/继电保护功能复现规划.md §1.5,验收 = 省电科院点表): - * - YC:IOA 0x4011~0x4018 + 0x401d(频率),短浮点、系数 1; - * ⚠ V0.1 有效通道仅 UA/UN/IA(TPAFE 三路,§1.6)——其余点**存在但置 IV** - * (无效位),频率随 L1-7 落地前同样置 IV; + * - YC:IOA 0x4011~0x4018 + 0x401d(频率),短浮点、系数 1;数据源 = + * L2 遥测快照(App/algo/meas.h,「数据可信」卡 9/11):电气 8 路绑快照槽, + * 有效性由快照品质驱动——V0.1 削顶路(docs/算法/继电保护功能复现规划.md + * §1.6)快照置 CLIPPED → 本层映射为线上 IV(V0.2/N-18 后自动启用,绑定零 + * 改动,见 docs/任务规划/数据可信.md §10 行 N-18);0x401d 绑频率源, + * freq_valid()==0 → 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)—— @@ -21,6 +24,11 @@ * 按未知 IOA(COT 14 + EVI)应答,登记为待办(设计文档)。 * 线程约定:init/bind 内核启动前或建服务前;cov_check/read 仅 104 服务任务上下文。 * 创建日期:2026-10-12(B4) + * 修改记录: + * 2026-10-25 DTU 项目组 卡 9/卡 11(「数据可信」,道D):YC 数据源 = L2 遥测 + * 快照(削顶路由品质驱动 IV);死区改运行时副本 + + * set/get 入口;COV 遥测腿 = 死区 2 点确认(seq 去重) + * + 品质变化即报;0x401d 绑频率源(freq_valid → IV)。 */ #ifndef IEC104_PTS_H #define IEC104_PTS_H @@ -42,6 +50,11 @@ typedef struct { int (*set_read)(uint32_t addr, float *out); /* 定值写:select=1 预置校验(范围)/ 0 执行写入。返回 0 / -1 越界或未知 */ int (*set_write)(uint32_t addr, float v, uint8_t select); + /* 遥测读(带品质,卡 9 追加于表尾——旧源 NULL = 走 yc_read 退路,品质由 + * 返回值推导:-1 → IV、-2 → NT、0 → 无位,行为与旧源完全一致)。 + * qds = uint16 域位(App/algo/meas.h MEAS_QDS_*:低字节 = 线上 QDS 同值, + * 高字节 CLIPPED 由本层映射线上 IV);返回 0 / -1 IV / -2 NT */ + int (*yc_read_ex)(uint8_t idx, float *out, uint16_t *qds); } iec104_pts_src_t; /* ---------------- 变化检测结果(cov_check 输出) ---------------- */ @@ -65,8 +78,15 @@ void iec104_pts_bind_src(const iec104_pts_src_t *src); /* ---------------- YC ---------------- */ uint8_t iec104_pts_yc_count(void); uint32_t iec104_pts_yc_ioa(uint8_t idx); -/* 读 YC:v 有效值(无效时为 0);qds 输出品质位(IV/NT 等)。返回 0/无效-1 */ +/* 读 YC:v 有效值(无效时为 0);qds 输出线上品质位(IV/NT/OV,CLIPPED 已映射 + * 为 IV)。返回 0/无效-1(NT 亦无效,品质位区分) */ int iec104_pts_yc_read(uint8_t idx, float *v, uint8_t *qds); +/* 死区运行时写(「数据可信」卡 9;卡 10 运行参数段的落点入口): + * db = 工程量绝对值 ≥0;越界/负值/NaN → -1 且 RAM 不动;成功时清零该点 + * 待确认计数(跨死区基线不残留半截确认)。仅 104 服务任务上下文调用。 */ +int iec104_pts_yc_set_deadband(uint8_t idx, float db); +/* 读当前生效死区(越界 idx 返 0) */ +float iec104_pts_yc_deadband(uint8_t idx); /* ---------------- YX ---------------- */ uint8_t iec104_pts_yx_count(void); @@ -77,10 +97,19 @@ void iec104_pts_yx_read(uint8_t idx, uint8_t *out); /* ---------------- COV(每 poll 一次) ---------------- */ /* - * 函数功能:读全表快照并与上次比对(变位 + 死区),输出变化集 + * 函数功能:读全表并与上次比对(遥信变位 + 遥测死区 2 点确认 + 品质变化), + * 输出变化集 * 入口参数:cov - 输出(可为 NULL = 只刷快照不取变化) - * 函数说明:首次调用只建快照、变化集全 0(突发由初始化/总召覆盖)。 - * 死区 = 点表内置绝对值(§1.5 运行参数 8020H 段本期取内置默认)。 + * 函数说明:遥测判据(「数据可信」卡 9,对标参考系统 CalfYcMea 死区 2 点确认, + * 短板评测口径):**连续 2 个新测量点** |v - 上次上送值| > 死区才置变化位 + * (单点尖峰不确认——噪声回摆即撤销候选);品质位(线上 qds)相对上次 + * 上送值翻转 → 当拍直接置位(不等 2 点,有效性变化必须即时可见, + * 含 0x401d 的 IV 进出)。死区 = 运行时副本(ROM 默认 = 点表列, + * iec104_pts_yc_set_deadband() 覆盖——卡 10 运行参数段的接线点)。 + * 测量点定义:板端默认源 = **快照换帧**(meas seq 前进;100 ms poll 对 + * 200 ms 快照,同一帧重复比对会计数两次 → seq 去重是确认语义成立的 + * 前提);注入源(单测假源)= 每次调用一测点。首次读到有效值只建基线 + * 不上报(突发由总召覆盖)。 */ void iec104_pts_cov_check(iec104_pts_cov_t *cov); diff --git a/docs/子系统设计/IEC104上行设计说明.md b/docs/子系统设计/IEC104上行设计说明.md index 0c397008..295ee810 100644 --- a/docs/子系统设计/IEC104上行设计说明.md +++ b/docs/子系统设计/IEC104上行设计说明.md @@ -36,17 +36,26 @@ | IOA | 点 | 来源 | V0.1 状态 | |---|---|---|---| -| 0x4011 | 1Ua | phasor `C02-UA-F1` fAmSQ | ✅ 有效 | -| 0x4012/0x4013 | 1Ub/1Uc | — | ❌ 发 IV(STM32 侧削顶,`docs/算法/继电保护功能复现规划.md` §1.6) | -| 0x4014 | 1U0 | phasor `C03-UN-F1` | ✅ 有效(外部零序专用通道) | -| 0x4015 | 1Ia | phasor `C01-IA-F1` | ✅ 有效 | -| 0x4016~0x4018 | 1Ib/1Ic/1I0 | — | ❌ 发 IV(同上) | -| 0x401D | 1f 频率 | 软件测频 | ❌ 发 IV(L1-7 未实现) | +| 0x4011 | 1Ua | L2 遥测快照槽 `ADC_SEM_UA`(卡 9) | ✅ 有效 | +| 0x4012/0x4013 | 1Ub/1Uc | 同表槽位;STM32 侧削顶(`docs/算法/继电保护功能复现规划.md` §1.6) | ❌ 发 IV——**品质驱动**(快照 CLIPPED 位映射,非点表绑死;V0.2/N-18 消失后零改动自动启用) | +| 0x4014 | 1U0 | 快照槽 `ADC_SEM_UN`(外部零序专用通道) | ✅ 有效 | +| 0x4015 | 1Ia | 快照槽 `ADC_SEM_IA` | ✅ 有效 | +| 0x4016~0x4018 | 1Ib/1Ic/1I0 | 同 Ub/Uc:绑槽 + 品质驱动 | ❌ 发 IV | +| 0x401D | 1f 频率 | 快照 `freq_hz`(卡 11 绑源;B8 自适应窗测频 `freq_get()` 同源) | ✅ 随测频收敛生效;`freq_valid()==0` → IV(卡 11 DoD) | -- 现读数为**工程量(引脚电压/电流一次折算后的二次口径)**——`App/algo/adc_cal` 标定折算层 - 已于 2026-10-25 落地(卡 6 同步把死区按额定 0.5% 逐点重定:电压 0.5 V / 电流 0.005 A, - 频率维持 0.5 Hz 绝对值);标定系数默认=ROM 引脚 LSB,标准源注入复核(卡 12)仍待台架。 -- 相量未就绪(未满一周波)→ QDS 置 NT(0x20),与 IV(无效通道)语义区分。 +- **数据源 = L2 遥测快照**(`App/algo/meas.h`,「数据可信」卡 8/9):200 ms 基波有效值 + + 每点品质,104 与 UI 共用同一份上游;读数单位 = ADC 引脚电压(eng=0,`App/algo/adc_cal` + 折算层 2026-10-25 落地;二次值等卡 12 标定置 VALID)。槽位值/品质整体透传,本层 + **不再自行判新旧**(快照 `seq` 仅供 COV 去重)。 +- 品质映射:快照 `MEAS_QDS_*` 低字节与线上 QDS 同值直传;**CLIPPED → IV**(「削顶通道 + 恒 IV」口径不变、改由品质驱动,docs/任务规划/数据可信.md §8 风险表末行的单测要求); + 快照从未发布(开机 <200 ms)→ IV|NT;判定顺序 IV 优先于 NT。 +- 死区(卡 9):从「越死区即报」改为**连续 2 个新测量点越限才确认**——板端测量点 = + 快照换帧(100 ms 轮询 vs 200 ms 快照按 `seq` 去重,防同一帧把单点噪声重复计满 2 点; + 基准恒为**上次上送值**,非上次读值——防慢漂移把基准吃掉,参考系统同款);品质翻转 + 当拍直报、不计数。死区数值 = 运行时副本(`iec104_pts_yc_set_deadband`,ROM 默认沿 + 卡 6 额定 0.5% 档:0.5 V / 0.005 A / 0.5 Hz;卡 10 接 8020H 段后主站可读写); + 标准源注入复核(卡 12)待台架。 ### 3.2 遥信 YX(IOA 1~33) @@ -112,7 +121,8 @@ - 总召(C_IC 组 1):actcon → 分片背景扫描(YX 双点组 SQ=1 → YX 单点组 SQ=1 → YC 组 SQ=1 → 频率单点)→ actterm;首召/ RBT 位 → COT=4(初始化),否则 COT=2。 单次 poll 发送预算 4 个 ASDU(防大扫描饿死宿主任务)。 -- 突发:COV = 遥信变位 + 遥测死区(`iec104_pts_cov_check`),COT=3 逐点 SQ=0。 +- 突发:COV = 遥信变位 + 遥测**死区 2 点确认 + 品质翻转直报**(`iec104_pts_cov_check`, + 卡 9 语义见 §3.1 注),COT=3 逐点 SQ=0。 - 周期:RUN 态 60 s 全 YC 上送 COT=1。 - 遥控/定值写:两段式,失配/越界 → COT|PN 否定确认;未知 IOA → COT=14+EVI、 错公共地址 → COT=13+EVI、未知类型 → COT=12+EVI。 @@ -162,7 +172,7 @@ | 层 | 内容 | |---|---| -| L0.5 | `test/host/iec104/`:APCI 重组/坏帧、STARTDT 握手、总召全流程(含 RBT/分片/预算)、COV、k/w 窗、t1/t2/t3(假时基)、遥控两段式与 PN、YK12 软压板两段转真 + 置脏(B7)、定值两段写 + set_sealed 后 selfcheck、C_RD(含本地项不可寻址)、C_CS(含闰年)、错地址/错类型 EVI;`test/host/prot_set/`:blob 往返 + 损坏整表拒绝(all-or-nothing)| +| L0.5 | `test/host/iec104/`:APCI 重组/坏帧、STARTDT 握手、总召全流程(含 RBT/分片/预算)、COV、k/w 窗、t1/t2/t3(假时基)、遥控两段式与 PN、YK12 软压板两段转真 + 置脏(B7)、定值两段写 + set_sealed 后 selfcheck、C_RD(含本地项不可寻址)、C_CS(含闰年)、错地址/错类型 EVI;**板端默认源真实链**(ring→phasor→freq→meas 快照,非桩):值/品质透传、CLIPPED→IV、0x401D 绑频(收敛 ±0.02 Hz / 未新鲜 IV)、死区运行时化 + 2 点确认/回摆撤销、seq 去重(卡 9/11,TC-IEC104-025~028);`test/host/prot_set/`:blob 往返 + 损坏整表拒绝(all-or-nothing)| | L0 | `iec104-unit-test` 钩子 + cppcheck/coding-standard | | 台架 | `@test` 锚点(104 服务启动/监听行);**PC 主站客户端 `test/host/iec104_master.py`(纯标准库,B6 2026-10-12 就绪)**:STARTDT/总召四组+actterm/C_SE 定值两段写→YX15·16 变位(COT=3)/恢复复归/YK11 两段正答/C_RD 读回,台架往返 **18/18 PASS**(证据 `../测试说明/boot_log_data/20261012_B6_iec104_roundtrip_log.txt`);**B7 持久化模式 `--b7-pre/--b7-post/--b7-mark/--b7-check`**(中间 ST-LINK 复位):YK12 两段投退→保护退出/联动、**定值与压板态跨复位保持**(含非默认 7.5 往返),**21/21 + 13/13 + 4/4+4/4 PASS**(证据 `../测试说明/boot_log_data/20261012_B7_iec104_persist_log.txt`);**B9 SOE 腿**(同工具同轮回归):自发 YX 断言 **M_SP_TB_1 带 ts**(工具解析 type 30/1 双型)+ FTP 下载 `/log/soe.log` 经 `test/host/soe/decode_soe.py` 解码断言(校验和/seq 单调/CP56↔epoch UTC 交叉),pre 21/21 + post 13/13 不回归;lib60870 级第三方全流程归 B10 | @@ -173,7 +183,8 @@ 双点带时标不做(V0.1 双点恒 IV,无意义)。 3. YK1~4/13/14 否定确认——出口硬件确认后(R7)转真执行(YK12 已于 B7 转真)。 4. ~~遥测死区 10.0 码值为拟定值~~——工程量死区已于 2026-10-25 卡 6 落地(额定 0.5%); - 死区参数挪入运行参数段(点表 8020H 段)主站可读写,仍归后续(「数据可信」卡 10)。 + 卡 9(2026-10-25)已改**运行时副本 + set/get 入口 + 2 点确认**;主站可读写(8020H 段 + run_par 接线 `iec104_pts_yc_set_deadband`)仍归「数据可信」卡 10。 5. 多主站/双链路冗余不做(单连接语义)。 6. 主站侧验证工具 = `test/host/iec104_master.py`(B6 2026-10-12 就绪,台架往返 18/18,覆盖总召/变位/遥控/定值);**第三方送检环境仍未就绪**——lib60870 级第三方全流程归 B10。 7. **后连优先未实现**(V0.1 = 首到优先):单连接 socket 连接态无法 accept, @@ -190,4 +201,5 @@ | 2026-10-12 | **B7 定值持久化 + 软压板**:YK12 转真(§3.3,两段式 → `PS_GLOBAL_EN`);定值执行段改 `prot_set_set_sealed` 原子写+封存(§3.4,收口 B5 竞态窗口);本地项 `comm_addr=0` 不可寻址;持久化 = 脏标志 → `SYS_EVT_PROT_SET_SAVE` → defaultTask 原子写 `/prot_set.bin`,开机屏障前整表原子装载(损坏拒绝保默认 + 自检留痕);单测 +7(iec104 94 断言)+ prot_set blob 往返/损坏 33 断言 | | 2026-10-12 | **B7 台架腿闭环**(§7 台架行回填):主站工具加 `--b7-pre/--b7-post/--b7-mark/--b7-check` 四模式(中间 ST-LINK 复位);pre 21/21(YK12 OFF→低定值不动作 / ON→YX15·16 即动作)、post 13/13(**压板退出态跨复位保持** + 重启后联动在位)、mark/check 4/4+4/4(**非默认定值 7.5 跨复位保持**);开机日志 `[CFG] prot set loaded (/prot_set.bin)`;证据 `../测试说明/boot_log_data/20261012_B7_*` | | 2026-10-25 | **标定折算落地联动(卡 6)**:遥测读数由原始码值改工程量(§3.1 注已重写);死区按额定 0.5% 逐点重定(0.5 V / 0.005 A / 0.5 Hz),§8 第 4 条改「已落地,8020H 段仍归卡 10」 | +| 2026-10-25 | **卡 9/卡 11(道D,「数据可信」M5/M6)**:YC 板端源改读 L2 遥测快照(削顶五路从点表绑死 -1 改为**槽位绑定 + 快照品质 CLIPPED→IV**,V0.2/N-18 零改动启用);`0x401D` 绑频率源(`freq_valid()==0` → IV);死区改运行时副本 + `iec104_pts_yc_set_deadband()`(卡 10 接线点);COV 遥测腿 = 死区连续 2 点确认(快照 `seq` 去重)+ 品质翻转直报;`iec104_pts_src_t` 表尾追加 `yc_read_ex`(旧源退路语义不变);单测 97→127(TC-IEC104-025~028 走真实 L2 链);台架腿(总召读回一致 / 频率 ±0.01 Hz)待排窗——主站工具已记 val+qds,无需改工具 | | 2026-10-12 | **B9 时标 SOE**:自发单点 YX 换 **M_SP_TB_1(30)**(SIQ+CP56,§3.2 注;总召/周期保持 NA 型);link cfg 增 `epoch_ms` 时标源(NULL/无效 → IV 如实);serv 绑 `sys_clock`(秒域 → ms 域=秒×1000);主站工具解析 type 30/1 双型;单测 iec104 94→97(TB 时标 + IV 路径,两种类型都断言);台架腿见 §7 B9 行;落盘侧见 `App/log/README.md` soe 节 | diff --git a/scripts/id_registry.json b/scripts/id_registry.json index 0a764167..a1a85a06 100644 --- a/scripts/id_registry.json +++ b/scripts/id_registry.json @@ -1810,7 +1810,7 @@ ], "scope": "global", "frozen": false, - "cursor": "TC-IEC104-023", + "cursor": "TC-IEC104-028", "cursor_owner_only": true, "check_dangling": true, "allow_orphans": true, diff --git a/test/host/iec104/iec104_test.c b/test/host/iec104/iec104_test.c index ccd001c0..a8452a15 100644 --- a/test/host/iec104/iec104_test.c +++ b/test/host/iec104/iec104_test.c @@ -17,7 +17,8 @@ * TC-IEC104-005 OPEN 态 I 帧忽略(序号不消费) * TC-IEC104-006 总召全流程:actcon→4 分片(首召 COT=4)→actterm;二召 COT=2;RBT→COT=4 * TC-IEC104-007 遥信变位 COV(COT=3);无效位不触发 - * TC-IEC104-008 遥测死区 COV:死区内不发 / 越死区发 + * TC-IEC104-008 遥测死区 COV(卡 9 起 = 2 点确认):死区内不发 / 首越只挂起 / + * 连续第 2 越点上送 * TC-IEC104-009 k 窗:12 未确认停发;S 确认后恢复 * TC-IEC104-010 w 阈:收满 8 发 S * TC-IEC104-011 t2 到期发 S @@ -33,6 +34,17 @@ * TC-IEC104-021 C_TS / C_RP actcon * TC-IEC104-022 周期上送 COT=1(YC 组 + 频率) * TC-IEC104-023 k 窗满 COV 不丢事件(pend 暂存,窗开后补发) + * —— 以下为「数据可信」卡 9/卡 11(道D,2026-10-25;板端默认源 = 真实 + L2 链 ring→phasor→freq→meas,非桩)—— + * TC-IEC104-025 板端源读快照(024 号被存量 B7 用例的裸标签占用):未发布 + * IV|NT / 值=基波有效值 / 削顶 CLIPPED + * →线上 IV 清零 / 干净路品质 0 / 频率未新鲜 IV + * TC-IEC104-026 0x401D 绑频:收敛后 ±0.02 Hz / freq_valid==0 → IV 清零 + * (卡 11 DoD 原句) + * TC-IEC104-027 死区运行时化:set/get + 非法拒 + 运行档压住旧档步长 + + * 2 点确认/回摆撤销/init 复位 ROM 默认 + * TC-IEC104-028 品质翻转直报(假源)+ 板端 seq 去重(同帧不计两点)+ + * 换帧只变品质时按集合直报 * * 通过:全部 TC PASS(main 返回 0)。 * 创建日期:2026-10-12(B4) @@ -54,6 +66,7 @@ static int s_total = 0, s_passed = 0, s_failed = 0; #include "sample_ring.c" #include "phasor.c" #include "freq.c" +#include "meas.c" /* 卡 9:板端默认 YC 源 = L2 遥测快照(真实链,非桩) */ #include "prot_elm.c" #include "iec104_pts.c" #include "iec104_link.c" @@ -515,11 +528,14 @@ static void tc_cov_yc(void) * 旧拟定码值死区 10 已由「数据可信」卡 6 重定) */ iec104_link_poll(&g_lk, now + 100u); CHECK(cap_i_count() == 0, "TC-008 inside the deadband nothing is uploaded"); - s_f_yc[0] = 101.25f; /* 101.25-100.4=0.85 > 0.5 */ + s_f_yc[0] = 101.25f; /* 101.25-100=1.25 > 0.5,第 1 越点 */ iec104_link_poll(&g_lk, now + 200u); + CHECK(cap_i_count() == 0, "TC-IEC104-008 first deadband crossing only arms (2-point confirm)"); + iec104_link_poll(&g_lk, now + 300u); /* 第 2 个连续测点仍越 -> 确认上送 */ CHECK((cap_i_count() == 1) && (cap_i(0, &c) == 0) && (c.type == 13u) && (c.cot == IEC104_COT_SPONT) && (c.ioa == 0x4011u) - && (iec104_get_float(c.obj) == 101.25f), "TC-008 crossing the deadband uploads"); + && (iec104_get_float(c.obj) == 101.25f), + "TC-IEC104-008 second consecutive crossing uploads (Card 9 confirm)"); } static void feed_rd(uint32_t now) @@ -912,6 +928,178 @@ static void tc_cov_kfull(void) && (c.cot == IEC104_COT_SPONT), "TC-023 once the window opens the backlogged event is sent, none lost"); } +/* ========== 卡 9/卡 11:板端默认源(真实 L2 链:ring→phasor→freq→meas) ========== */ + +/* 与 App/task/adc_task.c 同型的单拍编排:压环 → phasor_run →(可选 freq_run)。 + * 9 路同幅错相正弦(整周波 → 各路基波有效值 = A/√2,与相位无关,期望好算) */ +static double s_ph2 = 0.0; +static void snap_beats(int n, double amp, int run_freq) +{ + float f[ADC_SEM_COUNT]; + int i; + int s; + + for (i = 0; i < n; i++) { + for (s = 0; s < (int)ADC_SEM_TEMP; s++) { + f[s] = (float)(amp * sin(s_ph2 + (double)s * 0.35)); + } + f[ADC_SEM_TEMP] = 1234.0f; + s_ph2 += 2.0 * 3.14159265358979323846 * 50.0 / 1000.0; /* 50 Hz */ + (void)sample_ring_push_frame(f, (uint8_t)ADC_SEM_COUNT); + phasor_run(); + if (run_freq != 0) { + freq_run(); + } + } +} +/* 200 ms 发布拍(读同拍 freq 状态,adc_task v3.2 编排的镜像) */ +static void snap_pub(uint32_t ms, uint32_t clip_mask) +{ + meas_tick(ms, freq_valid(), freq_last(), clip_mask); +} + +static void tc_pts_snap(void) +{ + float v; + uint8_t q; + + reset_all(1000u); + iec104_pts_bind_src(NULL); /* 板端默认源 = 真实快照链 */ + (void)phasor_init(); /* 环 + 相量双区清零 */ + (void)meas_init(); + CHECK((iec104_pts_yc_read(0u, &v, &q) == -1) + && (q == (uint8_t)(IEC104_QDS_IV | IEC104_QDS_NT)) && (v == 0.0f), + "TC-IEC104-025 before first publish -> IV|NT, value zero"); + snap_beats(20, 100.0, 0); + snap_pub(200u, 0xE6u); /* 削顶位图同板端默认构建(cl=0xE6) */ + CHECK((iec104_pts_yc_read(0u, &v, &q) == 0) && (q == 0u) + && (fabsf(v - 70.711f) < 0.1f), + "TC-IEC104-025 UA = snapshot fundamental RMS (engineering domain)"); + CHECK((iec104_pts_yc_read(3u, &v, &q) == 0) && (q == 0u), + "TC-IEC104-025 UN (bit3 not in 0xE6) stays valid"); + CHECK((iec104_pts_yc_read(4u, &v, &q) == 0) && (q == 0u), + "TC-IEC104-025 IA valid"); + CHECK((iec104_pts_yc_read(1u, &v, &q) == -1) && (q == IEC104_QDS_IV) + && (v == 0.0f), "TC-IEC104-025 clipped UB: CLIPPED maps to wire IV, value zero"); + CHECK((iec104_pts_yc_read(2u, &v, &q) == -1) && (q == IEC104_QDS_IV), + "TC-IEC104-025 clipped UC IV"); + CHECK((iec104_pts_yc_read(5u, &v, &q) == -1) && (q == IEC104_QDS_IV), + "TC-IEC104-025 clipped IB IV"); + CHECK((iec104_pts_yc_read(8u, &v, &q) == -1) && (q == IEC104_QDS_IV), + "TC-IEC104-025 freq point published but not fresh -> IV (Card 11 rule)"); +} + +static void tc_pts_freq(void) +{ + float v; + uint8_t q; + int k; + + reset_all(1000u); + iec104_pts_bind_src(NULL); + (void)phasor_init(); + (void)freq_init(); + (void)meas_init(); + for (k = 0; k < 5; k++) { /* 100 拍:测频启动收敛需 20 拍 */ + snap_beats(20, 100.0, 1); + snap_pub((uint32_t)(k + 1) * 200u, 0u); + } + CHECK(freq_valid() == 1u, "TC-IEC104-026 drive precondition: freq converged"); + CHECK((iec104_pts_yc_read(8u, &v, &q) == 0) && (q == 0u) + && (fabsf(v - 50.0f) <= 0.02f), + "TC-IEC104-026 0x401D carries freq reading within 0.02 Hz of synthetic 50.000"); + for (k = 0; k < 3; k++) { /* 浮空:幅值门落下 */ + snap_beats(20, 0.0, 1); + snap_pub((uint32_t)(6 + k) * 200u, 0u); + } + CHECK((freq_valid() == 0u) && (iec104_pts_yc_read(8u, &v, &q) == -1) + && (q == IEC104_QDS_IV) && (v == 0.0f), + "TC-IEC104-026 freq_valid==0 -> IV with zeroed value (Card 11 DoD)"); +} + +static void tc_db_runtime(void) +{ + iec104_pts_cov_t cov; + + reset_all(1000u); /* 假源在场 = 每次 cov_check 一测点 */ + CHECK((iec104_pts_yc_set_deadband(0u, 2.0f) == 0) + && (iec104_pts_yc_deadband(0u) == 2.0f), + "TC-IEC104-027 runtime deadband write + readback"); + CHECK((iec104_pts_yc_set_deadband(9u, 1.0f) == -1) + && (iec104_pts_yc_set_deadband(0u, -0.1f) == -1) + && (iec104_pts_yc_deadband(0u) == 2.0f), + "TC-IEC104-027 bad idx / negative rejected, RAM untouched"); + CHECK(iec104_pts_yc_deadband(8u) == 0.5f, "TC-IEC104-027 freq point ROM default kept"); + s_f_yc[0] = 100.0f; + iec104_pts_cov_check(&cov); /* 建线 rep=100(档 2.0) */ + s_f_yc[0] = 101.0f; /* 越旧档 0.5、未越运行档 2.0 */ + iec104_pts_cov_check(&cov); + CHECK((cov.yc_changed & 0x0001u) == 0u, "TC-IEC104-027 runtime db gates the old step"); + iec104_pts_cov_check(&cov); + CHECK((cov.yc_changed & 0x0001u) == 0u, "TC-IEC104-027 2nd point under 2.0 still silent"); + + iec104_pts_init(); /* 恢复 ROM 默认档(0.5) */ + iec104_pts_bind_src(&g_fake); + s_f_yc[0] = 100.0f; + iec104_pts_cov_check(&cov); /* 建线 */ + s_f_yc[0] = 100.6f; /* d=0.6 > 0.5,第 1 越 */ + iec104_pts_cov_check(&cov); + CHECK((cov.yc_changed & 0x0001u) == 0u, "TC-IEC104-027 crossing arms only (2-point)"); + iec104_pts_cov_check(&cov); + CHECK((cov.yc_changed & 0x0001u) != 0u, "TC-IEC104-027 second consecutive crossing confirms"); + s_f_yc[0] = 101.4f; /* 新基准 100.6 起 d=0.8 第 1 越 */ + iec104_pts_cov_check(&cov); + CHECK((cov.yc_changed & 0x0001u) == 0u, "TC-IEC104-027 pullback test step 1 armed"); + s_f_yc[0] = 100.8f; /* 回摆入死区 -> 撤销候选 */ + iec104_pts_cov_check(&cov); + s_f_yc[0] = 101.4f; /* 重新起算:单点不报 */ + iec104_pts_cov_check(&cov); + CHECK((cov.yc_changed & 0x0001u) == 0u, "TC-IEC104-027 pullback cleared pending count"); + iec104_pts_cov_check(&cov); + CHECK((cov.yc_changed & 0x0001u) != 0u, "TC-IEC104-027 two fresh crossings confirm again"); +} + +static void tc_cov_qds_seq(void) +{ + iec104_pts_cov_t cov; + + reset_all(1000u); /* 假源段:品质翻转 */ + s_f_yc[1] = 50.0f; + iec104_pts_cov_check(&cov); + s_f_yc_rc[1] = -1; + iec104_pts_cov_check(&cov); + CHECK((cov.yc_changed & 0x0002u) != 0u, "TC-IEC104-028 valid->IV reported at once"); + iec104_pts_cov_check(&cov); + CHECK((cov.yc_changed & 0x0002u) == 0u, "TC-IEC104-028 IV steady state not repeated"); + s_f_yc_rc[1] = 0; + iec104_pts_cov_check(&cov); + CHECK((cov.yc_changed & 0x0002u) != 0u, "TC-IEC104-028 IV->valid recovery reported at once"); + + iec104_pts_init(); /* 板端段:seq 去重 */ + iec104_pts_bind_src(NULL); + (void)phasor_init(); + (void)meas_init(); + snap_beats(20, 100.0, 0); + snap_pub(200u, 0u); /* seq=1 */ + iec104_pts_cov_check(&cov); /* 建线帧 */ + CHECK(cov.yc_changed == 0u, "TC-IEC104-028 baseline frame silent"); + snap_beats(20, 200.0, 0); + snap_pub(400u, 0u); /* seq=2:第 1 越点 */ + iec104_pts_cov_check(&cov); + CHECK((cov.yc_changed & 0x0001u) == 0u, "TC-IEC104-028 one new frame arms only"); + iec104_pts_cov_check(&cov); + iec104_pts_cov_check(&cov); /* 无换帧:不得计为第 2 点 */ + CHECK((cov.yc_changed & 0x0001u) == 0u, "TC-IEC104-028 same frame never counts twice"); + snap_beats(20, 200.0, 0); + snap_pub(600u, 0u); /* seq=3:连续第 2 越点 */ + iec104_pts_cov_check(&cov); + CHECK((cov.yc_changed & 0x0001u) != 0u, "TC-IEC104-028 second new frame confirms"); + snap_pub(800u, 0xE6u); /* 换帧仅变品质(削顶注入) */ + iec104_pts_cov_check(&cov); + CHECK((cov.yc_changed & 0x00E6u) == 0x00E6u, + "TC-IEC104-028 clipped flip per frame reported as a set"); +} + int main(void) { printf("=== IEC104 Protocol Stack Host Tests ===\n"); @@ -939,6 +1127,10 @@ int main(void) tc_ts_rp(); tc_periodic(); tc_cov_kfull(); + tc_pts_snap(); + tc_pts_freq(); + tc_db_runtime(); + tc_cov_qds_seq(); printf("=== IEC104 Tests: %d/%d PASSED (failed=%d) ===\n", s_passed, s_total, s_failed); return (s_failed == 0) ? 0 : 1;