/* * 模块名称:IEC 60870-5-104 点表与数据源适配(实现) * 模块功能:实现 iec104_pts.h——YC/YX/YK/定值点表(ROM)、板端默认源 * (phasor / prot_elm / prot_set)、COV 快照比对(遥信变位 + * 遥测死区)。点表口径见 iec104_pts.h 文件头与设计文档 §3。 * 线程约定:init/bind 建服务前;其余仅 104 服务任务上下文(快照非原子)。 * 创建日期:2026-10-12(B4) */ #include "iec104_pts.h" #include #include "iec104_asdu.h" /* QDS/DIQ 品质位常量 */ #include "phasor.h" /* 板端源:遥测 = 基波有效值 */ #include "prot_elm.h" /* 板端源:遥信 = 元件状态 */ #include "prot_set.h" /* 板端源:定值 + 闭锁 */ #include "adc_input.h" /* adc_semantic_t(phasor 实例按语义绑定) */ /* ---------------- YC 表(§1.5 点号 4011+;死区 = 拟定码值,L1-5 后复核) -- */ typedef struct { uint32_t ioa; int8_t sem; /* 绑定的 phasor 语义通道(-1 = 无 phasor 源,恒无效) */ float deadband; /* 死区(工程量绝对值) */ } pts_yc_def_t; static const pts_yc_def_t k_yc[] = { { 0x4011u, (int8_t)ADC_SEM_UA, 10.0f }, /* 1Ua */ { 0x4012u, -1, 10.0f }, /* 1Ub:V0.1 削顶 → IV */ { 0x4013u, -1, 10.0f }, /* 1Uc:同上 */ { 0x4014u, (int8_t)ADC_SEM_UN, 10.0f }, /* 1U0(外部零序专用通道) */ { 0x4015u, (int8_t)ADC_SEM_IA, 10.0f }, /* 1Ia */ { 0x4016u, -1, 10.0f }, /* 1Ib */ { 0x4017u, -1, 10.0f }, /* 1Ic */ { 0x4018u, -1, 10.0f }, /* 1I0 */ { 0x401Du, -1, 0.5f }, /* 1f:L1-7 未实现 → 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]; /* ---------------- YX 表(IOA = 1..33;双点 = 开关位置类) ------------------ */ #define YX_N 33u #define YX_DP_N 4u /* idx 0..3 = 双点(开关位置,V0.1 无硬件 → IV) */ /* 有效位图:YX15~18(idx14~17)+ YX23(idx22)+ YX31(idx30) */ #define YX_VALID_MASK (((1uLL << 14u) | (1uLL << 15u) | (1uLL << 16u) \ | (1uLL << 17u) | (1uLL << 22u) | (1uLL << 30u))) /* ---------------- 数据源(默认 = 板端;bind_src 可整体替换) -------------- */ static int s_src_yc_read(uint8_t idx, float *out); 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); 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 }; static const iec104_pts_src_t *s_src; /* ---------------- COV 快照 ---------------- */ static float s_yc_last[YC_N]; static uint16_t s_yc_have; /* bit i = 已有上次快照 */ static uint64_t s_yx_last; static uint8_t s_yx_snap_ok; /* ============================ 公开接口 ================================== */ /* * 函数功能:初始化点表模块(扫 phasor 基波实例映射、恢复默认源、清快照) */ 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_src = &k_default_src; s_yc_have = 0u; s_yx_snap_ok = 0u; s_yx_last = 0u; } /* * 函数功能:替换数据源(NULL = 恢复板端默认源;单测注入假源) */ void iec104_pts_bind_src(const iec104_pts_src_t *src) { s_src = (src != NULL) ? src : &k_default_src; } /* * 函数功能:遥测点数 */ uint8_t iec104_pts_yc_count(void) { return YC_N; } /* * 函数功能:遥测点 IOA(按下标;越界 → 0) */ uint32_t iec104_pts_yc_ioa(uint8_t idx) { return (idx < YC_N) ? k_yc[idx].ioa : 0u; } /* * 函数功能:读遥测(值 + 品质 descriptor;源 -2 → NT,其余失败 → IV) * 返 回 值:0 有效 / -1 无效 */ int iec104_pts_yc_read(uint8_t idx, float *v, uint8_t *qds) { int rc; if (v != NULL) { *v = 0.0f; } if ((idx >= YC_N) || (s_src == NULL) || (s_src->yc_read == NULL)) { if (qds != NULL) { *qds = IEC104_QDS_IV; } return -1; } rc = s_src->yc_read(idx, v); if (rc == 0) { if (qds != NULL) { *qds = 0u; } return 0; } if (v != NULL) { *v = 0.0f; } if (qds != NULL) { *qds = (rc == -2) ? IEC104_QDS_NT : IEC104_QDS_IV; } return -1; } /* * 函数功能:遥信点数 */ uint8_t iec104_pts_yx_count(void) { return (uint8_t)YX_N; } /* * 函数功能:遥信点 IOA(idx + 1;越界 → 0) */ uint32_t iec104_pts_yx_ioa(uint8_t idx) { return ((uint8_t)idx < YX_N) ? ((uint32_t)idx + 1u) : 0u; } /* * 函数功能:该遥信是否双点型(idx < 4 = 开关位置类) */ uint8_t iec104_pts_yx_is_dp(uint8_t idx) { return ((uint8_t)idx < YX_DP_N) ? 1u : 0u; } /* * 函数功能:读遥信(无效通道 → DIQ_IV;双点 → DPC 编码) */ void iec104_pts_yx_read(uint8_t idx, uint8_t *out) { uint64_t bits; uint64_t valid; uint8_t on; if (out == NULL) { return; } if ((idx >= YX_N) || (s_src == NULL) || (s_src->yx_bits == NULL) || (s_src->yx_valid == NULL)) { *out = IEC104_DIQ_IV; return; } valid = s_src->yx_valid(); if (((valid >> idx) & 1u) == 0u) { *out = IEC104_DIQ_IV; return; } bits = s_src->yx_bits(); on = (uint8_t)((bits >> idx) & 1u); if (iec104_pts_yx_is_dp(idx) != 0u) { *out = (on != 0u) ? (uint8_t)IEC104_DPC_ON : (uint8_t)IEC104_DPC_OFF; } else { *out = on; } } /* * 函数功能:COV 快照比对(遥信 = 有效位翻转;遥测 = 死区) * 入口参数:cov = 输出变位位图(NULL = 只刷快照) */ void iec104_pts_cov_check(iec104_pts_cov_t *cov) { iec104_pts_cov_t local; (void)memset(&local, 0, sizeof(local)); /* YX:变位 = 有效位上的位翻转(无效位不参与,避免 IV 通道抖动成"变位") */ if ((s_src != NULL) && (s_src->yx_bits != NULL) && (s_src->yx_valid != NULL)) { uint64_t bits = s_src->yx_bits() & s_src->yx_valid(); if (s_yx_snap_ok != 0u) { local.yx_changed = bits ^ s_yx_last; } s_yx_last = bits; s_yx_snap_ok = 1u; } /* YC:死区比对(仅对"本次有效 且 上次有快照"的点判定) */ if ((s_src != NULL) && (s_src->yc_read != NULL)) { uint8_t i; for (i = 0u; i < YC_N; i++) { float v; if (s_src->yc_read(i, &v) != 0) { continue; } if (((s_yc_have >> i) & 1u) != 0u) { float d = v - s_yc_last[i]; if (d < 0.0f) { d = -d; } if (d > k_yc[i].deadband) { local.yc_changed |= (uint16_t)(1u << i); } } s_yc_last[i] = v; s_yc_have |= (uint16_t)(1u << i); } } if (cov != NULL) { *cov = local; } } /* * 函数功能:遥控两段式(转发数据源;select=1 选择段 / 0 执行段) * 返 回 值:0 接受 / -1 拒绝(调用方回 PN) */ int iec104_pts_yk_exec(uint32_t ioa, uint8_t dpc, uint8_t select) { if ((s_src == NULL) || (s_src->yk_exec == NULL)) { return -1; } return s_src->yk_exec(ioa, dpc, select); } /* * 函数功能:按通讯地址读定值(工程量浮点) * 返 回 值:0 / -1 未知地址 */ int iec104_pts_set_read(uint32_t addr, float *out) { if ((s_src == NULL) || (s_src->set_read == NULL)) { return -1; } return s_src->set_read(addr, out); } /* * 函数功能:按通讯地址写定值(select=1 只校验;0 执行写入) * 返 回 值:0 / -1 越界或未知地址 */ int iec104_pts_set_write(uint32_t addr, float v, uint8_t select) { if ((s_src == NULL) || (s_src->set_write == NULL)) { return -1; } return s_src->set_write(addr, v, select); } /* ============================ 板端默认源 ================================= */ /* * 函数功能:板端默认源 - 遥测 = 基波相量有效值(fAmSQ) * 返 回 值:0 / -1 无 phasor 源(削顶通道/频率未实现)/ -2 未满一周波(NT) */ static int s_src_yc_read(uint8_t idx, float *out) { phasor_t p; int8_t pi; if ((idx >= YC_N) || (out == NULL)) { return -1; } pi = s_yc_phasor_idx[idx]; if (pi < 0) { return -1; /* 无 phasor 源(削顶通道/频率未实现) */ } if (phasor_get((uint8_t)pi, &p) != 0) { return -2; /* 未满一周波:未就绪(NT) */ } *out = p.fAmSQ; /* 有效值口径(系数已折算) */ return 0; } /* * 函数功能:板端默认源 - 遥信位图(保护元件动作态 + 定值闭锁) */ static uint64_t s_src_yx_bits(void) { prot_elm_state_t oc1; prot_elm_state_t oc2; prot_elm_state_t oc3; prot_elm_state_t ol; prot_elm_state_t u0; uint64_t bits = 0u; prot_elm_state(PE_OC1, &oc1); prot_elm_state(PE_OC2, &oc2); prot_elm_state(PE_OC3, &oc3); prot_elm_state(PE_OL, &ol); prot_elm_state(PE_3U0, &u0); if ((oc1.asserted != 0u) || (oc2.asserted != 0u) || (oc3.asserted != 0u) || (u0.asserted != 0u)) { bits |= (1uLL << 14u); /* YX15 故障总 */ } if (oc1.asserted != 0u) { bits |= (1uLL << 15u); /* YX16 过流 I 段 */ } if (oc2.asserted != 0u) { bits |= (1uLL << 16u); /* YX17 过流 II 段 */ } if (oc3.asserted != 0u) { bits |= (1uLL << 17u); /* YX18 过流 III 段 */ } if (ol.asserted != 0u) { bits |= (1uLL << 22u); /* YX23 过载 */ } if (prot_set_locked() != 0u) { bits |= (1uLL << 30u); /* YX31 保护闭锁 */ } return bits; } /* * 函数功能:板端默认源 - 遥信有效位图(V0.1 = YX15~18/23/31) */ static uint64_t s_src_yx_valid(void) { return YX_VALID_MASK; } /* * 函数功能:板端默认源 - 遥控(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) { 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:无出口硬件/无对应能力 → 拒绝 */ } } /* * 函数功能:板端默认源 - 定值读(扫 g_prot_set_meta 通讯地址映射) * 返 回 值:0 / -1 未知地址(本地项 comm_addr=0 不可寻址,B7 起显式排除) */ static int s_src_set_read(uint32_t addr, float *out) { uint32_t i; if (out == NULL) { return -1; } for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) { 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; } } return -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; int32_t q10; 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 == 0u) || (meta->comm_addr != (uint16_t)addr)) { continue; } q10 = PROT_SET_Q10(v); if ((q10 < meta->min) || (q10 > meta->max)) { return -1; /* 越界:选择段即拒(执行段同样拒) */ } if (select != 0u) { return 0; /* 选择段 = 只做范围校验 */ } return prot_set_set_sealed((prot_set_id_t)i, q10); } return -1; /* 未知地址 */ }