merge: task/runpar-par 合入 v2-on-ui

任务 runpar(1 个提交)经 task-worktree 回收:
- feat(运行参数): 卡10 run_par真源收编+104段0x8020~0x8058读写映射(道D M6-①)
This commit is contained in:
2026-10-25 13:16:45 +08:00
25 changed files with 1310 additions and 63 deletions
+12 -1
View File
@@ -396,6 +396,16 @@ repos:
files: ^(App/algo/meas\.(c|h)|App/algo/(phasor|sample_ring)\.(c|h)|App/task/adc_task\.c|test/host/meas/.*|test/host/common/unit_test_harness\.py)$
pass_filenames: false
# 7h5) 运行参数段(「数据可信」卡 10,道D)主机侧单元测试(2026-10-25 加)
# 死区把守突发上报的量、104 整定段是现场唯一远程写入口:注册契约、
# 分档量程守卫、两段式、系数直通的合法带与生效值回显必须离线钉死。
- id: run-par-unit-test
name: 运行参数段单测(主机侧)
entry: python test/host/run_par/run_unit_test.py
language: system
files: ^(App/algo/run_par\.(c|h)|App/algo/adc_cal\.(c|h)|App/cfg/cfg_file(_store)?\.(c|h)|test/host/run_par/.*|test/host/common/unit_test_harness\.py)$
pass_filenames: false
# 7i) 保护定值模型(P1 / L5-1 载体)主机侧单元测试(2026-10-11 加)
# 定值是保护动作的"输入宪法":范围校验、整表装载 all-or-nothing、
@@ -463,7 +473,8 @@ repos:
name: IEC104 协议栈单测(主机侧)
entry: python test/host/iec104/run_unit_test.py
language: system
files: ^(App/protocol/iec104/iec104_(asdu|pts|link)\.(c|h)|App/algo/(prot_set|prot_elm|phasor|sample_ring|adc_input|freq)\.(c|h)|test/host/iec104/.*|test/host/common/unit_test_harness\.py)$
# 卡 10 起本 TU 链入真实 cfg_file/adc_cal/run_par(板端真源链),触发面同步扩
files: ^(App/protocol/iec104/iec104_(asdu|pts|link)\.(c|h)|App/algo/(prot_set|prot_elm|phasor|sample_ring|adc_input|freq|meas|adc_cal|run_par)\.(c|h)|App/cfg/cfg_file(_store)?\.(c|h)|test/host/iec104/.*|test/host/common/unit_test_harness\.py)$
pass_filenames: false
# 7m2) SOE 动作记录编解码主机侧单元测试(2026-10-12 加,B9)
+23 -1
View File
@@ -18,6 +18,9 @@
* 创建日期:2026-10-25(道A 卡 2/卡 4)
* 修改记录:
* v1.0 2026-10-25 DTU 项目组 创建:Q16 系数/零点两段落盘 + 折算 + 三态 flags
* v1.1 2026-10-25 DTU 项目组 卡 10(道D):新增 adc_cal_b_codes() 生效零点
* 读取口;set() 授权写者扩至 iec104Task 直通路径
* (run_par 0x8040~/0x8048~,契约同 §4.4)
* + 合法带哨兵回退 + 两点标定公式(台架注入待跑)
*/
@@ -295,11 +298,30 @@ float adc_cal_k_uv(adc_semantic_t sem) {
return ((float)k) / 65536.0f;
}
/*
* 函数功能:读通道当前生效零点偏移(码值,浮点)——adc_cal_k_uv 孪生入口
* 入口参数:sem - 语义通道 adc_semantic_t
* 返 回 值:b[码值](越带时 s_eff_kb 已回 0);TEMP/越界返 0 float
* 限定条件:无(任意上下文,纯读)
* 函数说明:卡 10 的 104 b 覆盖回显 + k 单侧改写 RMW 读侧(App/algo/run_par.c)
*/
float adc_cal_b_codes(adc_semantic_t sem) {
int32_t k;
int32_t b;
if ((unsigned)sem >= (unsigned)ADC_SEM_TEMP) {
return 0.0f;
}
(void)s_eff_kb((unsigned)sem, &k, &b);
return ((float)b);
}
/*
* 函数功能:整定入口——写通道系数与零点,并置位 VALID(「数据可信」卡 4)
* 入口参数:sem - 语义通道;k_q16 - 系数(µV/码×65536);b_codes - 零点(码值)
* 返 回 值:0 - 成功;-1 - 参数非法/越出合法带(RAM 不动,[ERR] 留痕) int
* 限定条件:属主上下文(defaultTask 整定/标定链);落盘由属主经 cfg_file_save
* 限定条件:整定路径上下文(defaultTask 整定/标定链;iec104Task 经 run_par
* 直通为卡 10 授权写者,单字原子契约同 §4.4);落盘由属主经 cfg_file_save
* 函数说明:见 adc_cal.h 同名声明
*/
int adc_cal_set(adc_semantic_t sem, int32_t k_q16, int32_t b_codes) {
+14 -1
View File
@@ -23,6 +23,7 @@
* 创建日期:2026-10-25(道A 卡 2/卡 4)
* 修改记录:
* v1.0 2026-10-25 DTU 项目组 创建:Q16 系数/零点 + 折算/flags/集
* v1.1 2026-10-25 DTU 项目组 卡 10:+adc_cal_b_codes()(run_par 回显/RMW 用)
*/
#ifndef ADC_CAL_H
@@ -99,13 +100,25 @@ uint32_t adc_cal_flags(adc_semantic_t sem);
*/
float adc_cal_k_uv(adc_semantic_t sem);
/*
* 函数功能:读通道当前生效零点偏移(码值,浮点)——adc_cal_k_uv 的孪生入口
* 入口参数:sem - 语义通道 adc_semantic_t
* 返 回 值:b[码值](越带时为 0,与折算同视图);TEMP/越界返 0 float
* 限定条件:无
* 函数说明:104 运行参数段(「数据可信」卡 10)b 覆盖回显与 k 单侧改写的 RMW
* 读侧用(App/algo/run_par.c);勿据其反推二次值(看 flags)。
*/
float adc_cal_b_codes(adc_semantic_t sem);
/*
* 函数功能:整定入口——写通道系数与零点,并置位 VALID(「数据可信」卡 4)
* 入口参数:sem - 语义通道(电气 8 路) adc_semantic_t
* k_q16 - 系数,µV/码 × 65536 内 int32_t
* b_codes - 零点偏移,码值 内 int32_t
* 返 回 值:0 - 成功;-1 - 参数非法/越出合法带(RAM 不动,[ERR] 留痕) int
* 限定条件:**属主上下文**(defaultTask 的标定流程/整定链),勿在 adcTask 调用
* 限定条件:**整定路径上下文**(defaultTask 标定流程/整定链;卡 10 起 iec104Task
* 经 App/algo/run_par.c 的 0x8040~/0x8048~ 直通写亦为授权写者——写的仍是
* 引擎对齐 int32 单字,µs 级混窗契约同「数据可信」§4.4)。勿在 adcTask 调用
* 函数说明:只写 RAM 副本;落盘由属主经 cfg_file_save 完成(卡 10 前整定链
* 未接,台架两点标定(卡 12)是首个消费者)
*/
+18 -18
View File
@@ -23,10 +23,10 @@
#include "sample_ring.h" /* sample_ring_window():以本拍为终止的一周波窗 */
/* ===== 双区(计算区 / 使用区),口径同 App/algo/phasor.c 的 L2-7 ===== */
static meas_snap_t s_snap[2];
static volatile uint8_t s_snap_use;
static uint8_t s_snap_ready; /* 使用区是否已产出过有效结果 */
static uint32_t s_snap_seq; /* 只增帧号(写者单写者,读者原子单字读) */
static meas_snap_t s_ms_snap[2];
static volatile uint8_t s_ms_use;
static uint8_t s_ms_ready; /* 使用区是否已产出过有效结果 */
static uint32_t s_ms_seq; /* 只增帧号(写者单写者,读者原子单字读) */
/*
* 函数功能:清零一份快照(含 rsv 与全部数组元素)
@@ -61,11 +61,11 @@ static void meas_zero(meas_snap_t *p) {
* 返 -2(out 清零)、meas_seq() 恒返 0。
*/
int meas_init(void) {
meas_zero(&s_snap[0]);
meas_zero(&s_snap[1]);
s_snap_use = 0u;
s_snap_ready = 0u;
s_snap_seq = 0u;
meas_zero(&s_ms_snap[0]);
meas_zero(&s_ms_snap[1]);
s_ms_use = 0u;
s_ms_ready = 0u;
s_ms_seq = 0u;
return 0;
}
@@ -88,14 +88,14 @@ int meas_init(void) {
*/
void meas_tick(uint32_t tick_ms, uint8_t freq_valid_in, float freq_hz_in,
uint32_t clip_mask) {
meas_snap_t *z = &s_snap[1u - s_snap_use]; /* 计算区 = 非使用区 */
meas_snap_t *z = &s_ms_snap[1u - s_ms_use]; /* 计算区 = 非使用区 */
float win[PHASOR_N];
phasor_t p;
uint8_t all_ok = 1u;
uint8_t s;
meas_zero(z);
z->seq = s_snap_seq + 1u;
z->seq = s_ms_seq + 1u;
z->tick_ms = tick_ms;
for (s = 0u; s < (uint8_t)ADC_SEM_TEMP; s++) {
@@ -130,9 +130,9 @@ void meas_tick(uint32_t tick_ms, uint8_t freq_valid_in, float freq_hz_in,
z->freq_hz = freq_hz_in;
/* 全部字段就绪后才翻索引 + 置就绪(顺序即协议,勿交换) */
s_snap_seq = z->seq;
s_snap_use = (uint8_t)(1u - s_snap_use);
s_snap_ready = 1u;
s_ms_seq = z->seq;
s_ms_use = (uint8_t)(1u - s_ms_use);
s_ms_ready = 1u;
}
/*
@@ -149,12 +149,12 @@ int meas_get(meas_snap_t *out) {
if (out == NULL) {
return -1;
}
if (s_snap_ready == 0u) {
if (s_ms_ready == 0u) {
meas_zero(out);
return -2;
}
z = s_snap_use;
*out = s_snap[z];
z = s_ms_use;
*out = s_ms_snap[z];
return 0;
}
@@ -166,5 +166,5 @@ int meas_get(meas_snap_t *out) {
* 函数说明:轻量"是否换帧"探针,正式取数以 meas_get() 为准。
*/
uint32_t meas_seq(void) {
return s_snap_seq;
return s_ms_seq;
}
+289
View File
@@ -0,0 +1,289 @@
/*
* 模块名称:运行参数段(L2/L3 整定层,「数据可信」卡 10)——实现
* 模块功能:见 run_par.h。工厂默认 = 「数据可信」卡 6 额定 0.5 % 档(电压 500
* mV / 电流 5 mA / 频率 500 mHz,毫单位 int32 定点落盘);[run_par]
* 段 9 键(db_ua…db_freq,均 ≤15 字符)≤ CFG_FILE_MAX_KEYS 不拆段
* (通道系数覆盖**不落本段**——直通 adc_cal 的 [adc_cal]/[adc_cal_ofs],
* 系数唯一真源在那两段,lead 裁②a)。
* 适用平台:STM32F407ZGTx(Cortex-M4F,硬件 FPU)
* 作者:DTU 项目组
* 创建日期:2026-10-25(道D 卡 10)
* 修改记录:
* v1.0 2026-10-25 DTU 项目组 创建:[run_par] 段 + 104 映射 + 直通 adc_cal
* + B7 式脏标志落盘请求
*/
/* 头文件包含区 */
#include <math.h>
#include <stddef.h>
#include <string.h>
#include "run_par.h"
#include "adc_cal.h"
#include "cfg_file.h"
#define DBG_TAG "[PAR]"
#include "dbg_log.h"
/* 常量定义区 */
/* cfg 段名(≤ CFG_FILE_MAX_NAME_LEN);键名 6~7 字符小写 */
#define RUN_PAR_SEC "run_par"
/* 104 运行参数地址(复现规划 §5.1 类内落实;权威逐项表不在仓,送检点表到位
* 后以送检为准替换本常量段——lead 裁①。0x8027~0x802A = prot_set 告警段,永不让位) */
#define RUN_PAR_IOA_DB0 0x8030u /* 9 点:0x8030~0x8038 */
#define RUN_PAR_IOA_K0 0x8040u /* k 覆盖 8 路:0x8040~0x8047 */
#define RUN_PAR_IOA_B0 0x8048u /* b 覆盖 8 路:0x8048~0x804F */
/* 死区合法域上限(毫单位):电压 50 V / 电流 1 A / 频率 5 Hz——守卫挡误写,
* 不限工况;下限统一 0(负死区 = 配置错误) */
#define RUN_PAR_DB_MAX_U 50000L
#define RUN_PAR_DB_MAX_I 1000L
#define RUN_PAR_DB_MAX_F 5000L
/* 静态变量定义区 */
/* [run_par] 段 RAM 副本(引擎原地更新;属主契约与 µs 级混窗见「数据可信」§4.4,
* 同 adc_cal——本层不挂回调,副本即生效值,消费侧实时读) */
typedef struct {
int32_t db[RUN_PAR_DB_N];
} run_par_db_t;
static const run_par_db_t s_db_rom = {
{ 500L, 500L, 500L, 500L, /* UA/UB/UC/UN:0.5 V(卡 6 额定档) */
5L, 5L, 5L, 5L, /* IA/IB/IC/IN:0.005 A */
500L } /* 频率:0.5 Hz(绝对档,非额定百分比) */
};
static run_par_db_t s_db;
/* 键表 offset 与 run_par_db_t 字段一一对应(sizeof(int32_t) 步进) */
static const cfg_key_def_t s_db_keys[] = {
{ "db_ua", CFG_VAL_INT, 0u, 4u },
{ "db_ub", CFG_VAL_INT, 4u, 4u },
{ "db_uc", CFG_VAL_INT, 8u, 4u },
{ "db_un", CFG_VAL_INT, 12u, 4u },
{ "db_ia", CFG_VAL_INT, 16u, 4u },
{ "db_ib", CFG_VAL_INT, 20u, 4u },
{ "db_ic", CFG_VAL_INT, 24u, 4u },
{ "db_in", CFG_VAL_INT, 28u, 4u },
{ "db_freq", CFG_VAL_INT, 32u, 4u }
};
/* 落盘脏标志:104 写成功置位,run_par_store_save 成功清零(B7 prot_set 同款,
* 只置不清 = 写盘失败天然重试);单字节,跨任务 peek 无害 */
static uint8_t s_dirty;
/* 内部函数实现区 */
/*
* 函数功能:死区点量程上限(毫单位)
* 入口参数:idx - 0..8 uint8_t
* 返 回 值:上限;idx 越界返 0 长(一切值皆拒) 内 int32_t
*/
static int32_t s_db_cap_milli(uint8_t idx) {
if (idx < 4u) {
return RUN_PAR_DB_MAX_U;
}
if (idx < RUN_PAR_DB_N - 1u) {
return RUN_PAR_DB_MAX_I;
}
if (idx == (uint8_t)(RUN_PAR_DB_N - 1u)) {
return RUN_PAR_DB_MAX_F;
}
return 0L;
}
/*
* 函数功能:工程量浮点 → 毫单位定点(只舍不入的截断由调用侧范围守卫兜底)
* 入口参数:v - 工程量(须已判有限) float
* 返 回 值:四舍五入最近毫单位 内 int32_t
*/
static int32_t s_milli_of(float v) {
return (int32_t)lrintf(v * 1000.0f);
}
/*
* 函数功能:死区值校验(不提交),set/104 选择段共用同一守卫 = 两路语义同源
* 入口参数:idx - 0..8 uint8_t;db - 工程量 float
* 返 回 值:合法毫单位 ≥0;-1 非法(越界/NaN/负/越程) 内 int32_t
*/
static int32_t s_db_check(uint8_t idx, float db) {
int32_t milli;
if ((idx >= RUN_PAR_DB_N) || !isfinite(db) || !(db >= 0.0f)) {
return -1L;
}
milli = s_milli_of(db);
if ((milli < 0L) || ((long long)milli > (long long)s_db_cap_milli(idx))) {
return -1L;
}
return milli;
}
/* 公共函数实现区 */
/*
* 函数功能:初始化——副本置工厂默认 + 注册 [run_par] 段(幂等,内核前)
* 返 回 值:无
*/
void run_par_init(void) {
cfg_section_reg_t reg;
(void)memcpy(&s_db, &s_db_rom, sizeof(s_db));
s_dirty = 0u;
(void)memset(&reg, 0, sizeof(reg));
reg.name = RUN_PAR_SEC;
reg.p_data = &s_db;
reg.p_defaults = &s_db_rom;
reg.data_size = sizeof(s_db);
reg.p_keys = s_db_keys;
reg.key_count = (uint8_t)(sizeof(s_db_keys) / sizeof(s_db_keys[0]));
reg.p_cb = NULL; /* 不挂回调:RAM 副本即生效值(同 adc_cal) */
if (cfg_file_register(&reg) != 0) {
DBG_ERROR("register %s FAIL (dup/full?) - file values inactive",
RUN_PAR_SEC);
}
}
/*
* 函数功能:读第 idx 路死区(工程量)
* 返 回 值:V / A / Hz;越界返 0.0(消费侧按"无死区"保守处理) float
*/
float run_par_deadband(uint8_t idx) {
if (idx >= RUN_PAR_DB_N) {
return 0.0f;
}
return ((float)s_db.db[idx]) / 1000.0f; /* 毫→工程唯一换算点 */
}
/*
* 函数功能:写第 idx 路死区(守卫通过才提交;成功置脏)
* 返 回 值:0 成功;-1 非法(副本不动) int
*/
int run_par_deadband_set(uint8_t idx, float db) {
int32_t milli = s_db_check(idx, db);
if (milli < 0L) {
return -1;
}
s_db.db[idx] = milli;
s_dirty = 1u;
return 0;
}
/*
* 函数功能:104 运行参数地址读(命中集精确匹配,见 run_par.h 地址表)
* 返 回 值:0 命中;-1 非本段(调用方落 prot_set) int
*/
int run_par_104_read(uint32_t addr, float *out) {
if (out == NULL) {
return -1;
}
if ((addr >= RUN_PAR_IOA_DB0) && (addr < RUN_PAR_IOA_DB0 + RUN_PAR_DB_N)) {
*out = run_par_deadband((uint8_t)(addr - RUN_PAR_IOA_DB0));
return 0;
}
if ((addr >= RUN_PAR_IOA_K0) && (addr < RUN_PAR_IOA_K0 + RUN_PAR_CH_N)) {
*out = adc_cal_k_uv((adc_semantic_t)(addr - RUN_PAR_IOA_K0));
return 0;
}
if ((addr >= RUN_PAR_IOA_B0) && (addr < RUN_PAR_IOA_B0 + RUN_PAR_CH_N)) {
*out = adc_cal_b_codes((adc_semantic_t)(addr - RUN_PAR_IOA_B0));
return 0;
}
return -1;
}
/*
* 函数功能:104 运行参数地址写(两段式;k/b 直通 adc_cal_set——**run_par 不存
* 系数**,系数唯一真源 = [adc_cal]/[adc_cal_ofs],lead 裁②a)
* 返 回 值:0 接受 / -1 拒绝 / -2 非本段(调用方落 prot_set) int
* 函数说明:选择段 = 校验不提交(死区走与 set 同源守卫;k/b 的 adc_cal 合法带
* 由执行段裁决——IEC104 允许执行段 PN,两段结果可不一致,注释在此
* 明说防"选择过了怎么还 PN"的现场困惑);k/b 单侧写 RMW 用**当前
* 生效值**做另一侧(生效值经合法带哨兵,天然满足再写校验)。
*/
int run_par_104_write(uint32_t addr, float v, uint8_t select) {
uint8_t slot; /* 每个命中分支先赋值再使用(无未init读路径) */
int32_t k_q16;
int32_t b_codes;
int rc;
if ((addr >= RUN_PAR_IOA_DB0) && (addr < RUN_PAR_IOA_DB0 + RUN_PAR_DB_N)) {
int32_t milli;
slot = (uint8_t)(addr - RUN_PAR_IOA_DB0);
milli = s_db_check(slot, v);
if (milli < 0L) {
return -1;
}
if (select == 0u) {
s_db.db[slot] = milli;
s_dirty = 1u;
}
return 0;
}
if ((addr >= RUN_PAR_IOA_K0) && (addr < RUN_PAR_IOA_K0 + RUN_PAR_CH_N)) {
slot = (uint8_t)(addr - RUN_PAR_IOA_K0);
if (!isfinite(v) || !(v > 0.0f) || (v > 1.0e9f)) {
return -1;
}
if (select != 0u) {
return 0; /* 粗守过了 ≠ 执行必过:合法带裁决在执行段(见函数头) */
}
k_q16 = (int32_t)lrintf(v * 65536.0f);
b_codes = (int32_t)lrintf(adc_cal_b_codes((adc_semantic_t)slot));
rc = adc_cal_set((adc_semantic_t)slot, k_q16, b_codes);
if (rc == 0) {
s_dirty = 1u;
}
return rc;
}
if ((addr >= RUN_PAR_IOA_B0) && (addr < RUN_PAR_IOA_B0 + RUN_PAR_CH_N)) {
slot = (uint8_t)(addr - RUN_PAR_IOA_B0);
if (!isfinite(v) || (v < -1000000.0f) || (v > 1000000.0f)) {
return -1; /* 粗守 ±1e6 码;±8192 真带由 adc_cal 执行段裁 */
}
if (select != 0u) {
return 0;
}
b_codes = (int32_t)lrintf(v);
k_q16 = (int32_t)lrintf(adc_cal_k_uv((adc_semantic_t)slot) * 65536.0f);
rc = adc_cal_set((adc_semantic_t)slot, k_q16, b_codes);
if (rc == 0) {
s_dirty = 1u;
}
return rc;
}
return -2;
}
/*
* 函数功能:落盘脏探查(非消费)
* 返 回 值:1 有未落盘整定;0 干净 uint8_t
*/
uint8_t run_par_dirty_peek(void) {
return s_dirty;
}
/*
* 函数功能:执行落盘(整档外科保存成功后清脏;失败脏保持 = 下轮重试)
* 返 回 值:0 成功/无脏;-1 写盘失败 int
* 限定条件:defaultTask(配置引擎属主)上下文,SYS_EVT_RUN_PAR_SAVE 驱动
* 函数说明:一次 save 同时覆盖 [run_par] 9 键与刚被 adc_cal_set 改写的
* [adc_cal]/[adc_cal_ofs] 副本(lead 裁②:一条事件位一次 save)。
*/
int run_par_store_save(void) {
if (s_dirty == 0u) {
return 0;
}
if (cfg_file_save() != 0) {
DBG_ERROR("cfg save FAIL - keep dirty, retry next round");
return -1;
}
s_dirty = 0u;
return 0;
}
+130
View File
@@ -0,0 +1,130 @@
/*
* 模块名称:运行参数段(L2/L3 整定层,「数据可信」卡 10)
* 模块功能:遥测死区(9 点)与通道标定系数覆盖(k/b 各 8 路)的**单一真源**:
* - 死区 = 本模块 int32 毫单位 RAM 副本(注册进 cfg 引擎 [run_par] 段,
* 工厂默认 = 「数据可信」卡 6 额定 0.5 % 档),104 消费侧
* (App/protocol/iec104/iec104_pts.c)每拍实时读取——无投递、无缓存
* 副本(卡 9 曾在 pts 侧留运行时副本 s_yc_db,卡 10 落地后收编于此,
* 双源必打架);
* - 系数覆盖 = **直通 adc_cal_set()**([adc_cal]/[adc_cal_ofs] 是系数
* 唯一真源,卡 4 已定;本模块只加 104 地址映射 + 落盘请求,不另存
* 一份系数——同 D1 裁"两处判断必打架"逻辑)。
* 104 地址映射(docs/算法/继电保护功能复现规划.md §5.1 运行参数类
* 0x8020H~0x8058H;逐项表原文"待补"(参考项目 CommPara_ConfigTable
* 不在仓),本表即卡 10 的落实,送检点表若另给权威地址只改常量段:
* 0x8030~0x8038 = 死区 UA/UB/UC/UN/IA/IB/IC/IN/FREQ(工程量 V/A/Hz);
* 0x8040~0x8047 = k 覆盖(µV/码);0x8048~0x804F = b 覆盖(码值,可负);
* 其余留白(未启用类别),**0x8027~0x802A 属 prot_set 告警段**,本表
* 永不让位(两表按精确地址分流,见 iec104_pts.c set 路由)。
* 持久化(B7 prot_set 先例同款):任一写成功 → 脏标志只置不清;
* iec104Task 主循环 peek → 置 SYS_EVT_RUN_PAR_SAVE → defaultTask
* (配置引擎属主)run_par_store_save() 调 cfg_file_save() 整档外科
* 保存(含 [run_par] 与刚被 adc_cal_set 改写的 [adc_cal]/[adc_cal_ofs]
* 副本)→ 成功清脏;失败留脏 = 下轮天然重试。本模块自身零文件 IO
* (属主纪律)。
* 并发契约:引擎副本 int32 对齐字读写在 Cortex-M4 上原子;poll 重载 µs 级窗口
* 为「数据可信」§4.4 明载设计(adc_cal 同款)。写路径 = 104 执行段
* (iec104Task)与引擎读路径互不加锁,勿改成"读侧缓存"(缓存 = 新真源)。
* 纯算法:只用 stdint/stddef/math + cfg_file.h/adc_cal.h 头(注册/直通均为
* 接口级)→ 主机侧可单测(test/host/run_par,链真 cfg 引擎 + 真 adc_cal)。
* 适用平台:STM32F407ZGTx(Cortex-M4F,硬件 FPU)
* 作者:DTU 项目组
* 创建日期:2026-10-25(道D 卡 10)
* 修改记录:
* v1.0 2026-10-25 DTU 项目组 创建:[run_par] 段 + 104 地址映射 + 直通
* adc_cal + B7 式脏标志落盘请求
*/
#ifndef RUN_PAR_H
#define RUN_PAR_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/* 死区/覆盖点数(死区 = YC 表行数:电气 8 + 频率;覆盖 = 语义电气 8 路) */
#define RUN_PAR_DB_N 9u
#define RUN_PAR_CH_N 8u
/*
* 函数功能:运行参数初始化——副本置工厂默认 + 注册 [run_par] 段进 cfg 引擎
* 入口参数:无
* 返 回 值:无
* 限定条件:**内核启动前**调用(app_main_init,adc_cal_init 之后);注册必须先
* 于 defaultTask 的 cfg_file_load(N-19 契约,同 adc_cal 先例);幂等
* 函数说明:注册失败(段名重复/段数超限)打 [ERR] 后继续——工厂默认照常生效,
* 仅文件整定失效(adc_cal_init 同款降级)。
*/
void run_par_init(void);
/*
* 函数功能:读第 idx 路遥测死区(工程量)
* 入口参数:idx - 0..7 = 语义通道序(UA..IN),8 = 频率 uint8_t
* 返 回 值:死区 V / A / Hz;idx 越界返 0.0(调用方按"无死区"保守处理) float
* 限定条件:任意上下文(int32 单字读原子;104 COV 每拍调用)
* 函数说明:毫单位 → 浮点的换算点(除以 1000 唯一在此,勿在消费侧另写)。
*/
float run_par_deadband(uint8_t idx);
/*
* 函数功能:写第 idx 路死区(工程量,含范围守卫 + 脏置位)
* 入口参数:idx - 同上 uint8_t;db - 死区工程量(V/A/Hz,≥0) float
* 返 回 值:0 成功;-1 越界/越程/NaN(副本不动) int
* 限定条件:**iec104Task 上下文**(104 执行段 / 卡 11 后的整定路径;UI 若整定
* 需另行走配置引擎属主路径,勿跨任务直写)
* 函数说明:合法域按点型分档——电压 0~50 V、电流 0~1 A、频率 0~5 Hz(上限 =
* 额定 × 二档裕量,守卫目的是挡误写不是限工况;见 .c 常量段)。
* 成功即置脏(落盘请求,见文件头持久化契约)。
*/
int run_par_deadband_set(uint8_t idx, float db);
/*
* 函数功能:104 运行参数地址读(C_RD 路由,先于 prot_set 扫描)
* 入口参数:addr - 通讯地址(0x8020~0x8058 段内) uint32_t;out - 工程量浮点
* (死区 V/A/Hz;k 覆盖 µV/码;b 覆盖 码值) float *
* 返 回 值:0 命中并写出;-1 地址不归本段(调用方继续走 prot_set) int
* 限定条件:iec104Task 上下文
* 函数说明:命中集 = 0x8030~0x8038 / 0x8040~0x8047 / 0x8048~0x804F 精确匹配;
* k/b 读回 = adc_cal **当前生效值**(与折算同视图:文件坏值被合法带
* 哨兵回退 ROM 时,回显的就是 ROM——主站看到的即下游实际吃到的)。
*/
int run_par_104_read(uint32_t addr, float *out);
/*
* 函数功能:104 运行参数地址写(C_SE 两段式路由,先于 prot_set 扫描)
* 入口参数:addr - 通讯地址 uint32_t;v - 工程量浮点 float;
* select - 1 = 选择段(只校验不落副本)/ 0 = 执行段 uint8_t
* 返 回 值:0 接受;-1 拒绝(越程/NaN/合法带外/非本段可写地址命中守卫);
* -2 地址不归本段(调用方继续走 prot_set) int
* 限定条件:iec104Task 上下文;执行段成功即置脏
* 函数说明:k/b 覆盖经 adc_cal_set() 合法带哨兵(µV/码 ×[0.5,2.0]、|b|≤8192,
* 越带 -1 → 104 侧 PN 否定确认,adc_cal 不留痕由**其自身**越带 [ERR]
* 机制负责——本模块直通不重复留痕);两段式与 prot_set 定值写同构。
*/
int run_par_104_write(uint32_t addr, float v, uint8_t select);
/*
* 函数功能:落盘脏标志探查(非消费)
* 入口参数:无
* 返 回 值:1 = 有已整定未落盘的改动;0 = 干净 uint8_t
* 限定条件:任意上下文(单字节);iec104_serv 主循环 peek 后投递事件(B7 同款)
*/
uint8_t run_par_dirty_peek(void);
/*
* 函数功能:执行落盘请求(cfg_file_save 整档外科保存 + 成功清脏)
* 入口参数:无
* 返 回 值:0 成功(或无脏可存);-1 写盘失败(脏保持,下轮重试) int
* 限定条件:**defaultTask(配置引擎属主)上下文**,由 SYS_EVT_RUN_PAR_SAVE 驱动
* 函数说明:save 走引擎唯一写闸(超长拒写/原子改名,E-01 契约);本函数不清
* 引擎以外模块的脏语义([adc_cal] 副本值已由写路径就位)。
*/
int run_par_store_save(void);
#ifdef __cplusplus
}
#endif
#endif /* RUN_PAR_H */
+7
View File
@@ -24,6 +24,7 @@
#include "tpafe5160.h"
#include "adc_input.h" /* L1 采集层:adc_input_init()(语义映射 + STM32 ADC1 扫描) */
#include "adc_cal.h" /* L1.5 标定层:adc_cal_init()(ROM 系数 + cfg 两段注册,道A 卡2/4) */
#include "run_par.h" /* 运行参数段:run_par_init()([run_par] 注册 + 104 地址映射真源,道D 卡10) */
#include "phasor.h" /* L2 相量层:phasor_init()(系数表 + 实例 + 双区交换) */
#include "freq.h" /* L2 派生量:freq_init()(相位解缠绕测频,B8) */
#include "prot_set.h" /* L3 定值模型:prot_set_init()(B5,内核前封存) */
@@ -261,6 +262,12 @@ int app_main_init(void) {
DBG_INFO("adc cal init: OK (k_q16 tpafe=%ld stm32=%ld)",
(long)ADC_CAL_K_Q16_TPAFE, (long)ADC_CAL_K_Q16_STM32);
/* 4c2) 运行参数段初始化(「数据可信」卡 10):死区 9 点工厂默认 + 注册
* [run_par] 段。同 adc_cal 的「先注册后 load」契约 → 必须内核前;
* 104 消费(COV 实时读 / 0x8020 段读写映射)在 iec104Task 启动后进行。 */
run_par_init();
DBG_INFO("run par init: OK (db9 k/b via adc_cal, evt=1<<8)");
/* 4d) 保护定值 + 元件层初始化(B5):出厂默认定值封存校验和 + 元件状态
* 复位并解析相量索引(须在 phasor_init 之后)。内核前完成 → iec104
* 定值读/protectTask 首拍前定值必已装载。判据消费在 protectTask。 */
+4
View File
@@ -34,6 +34,10 @@ extern osEventFlagsId_t sys_evtHandle;
#define SYS_EVT_PROT_SET_SAVE (1u << 7) /* 保护定值落盘请求(B7):iec104Task 检测到定值脏置位,
* defaultTask(配置引擎属主)消费并写 /prot_set.bin
* (消费型命令位;写盘失败脏标志保留 → 下轮再通知重试) */
#define SYS_EVT_RUN_PAR_SAVE (1u << 8) /* 运行参数段落盘请求(「数据可信」卡 10):iec104Task
* 检测到 run_par 脏(死区 9 键 / 系数覆盖联动 [adc_cal])
* 置位,defaultTask 消费并 cfg_file_save 整档外科保存
* (消费型命令位;失败脏保留 → 下轮重试,B7 同款) */
#ifdef __cplusplus
}
+49 -30
View File
@@ -18,13 +18,18 @@
* 死区改运行时副本 + set/get 入口;COV 遥测腿 = 死区
* 连续 2 点确认(板端按快照 seq 去重)+ 品质翻转即报。
* 单测 = test/host/iec104 TC-IEC104-025~028。
* 卡 10 2026-10-25 DTU 项目组 死区真源收编 App/algo/run_par.c(本层 s_yc_db
* 副本删除,set/get 转转发壳,壳保留清确认计数语义);
* 定值读写路由 = run_par 表先、prot_set 后(0x8020~
* 0x8058 运行参数段 C_RD/C_SE,系数覆盖直通 adc_cal)。
*/
#include "iec104_pts.h"
#include <string.h>
#include "iec104_asdu.h" /* QDS/DIQ 品质位常量 */
#include "meas.h" /* 板端源:遥测 = L2 遥测快照(卡 9;品质域位同源) */
#include "meas.h" /* 板端源:遥测 = L2 遥测快照(卡 9;品质域位同源) */
#include "run_par.h" /* 卡 10:死区真源 + 运行参数段 104 读写路由 */
#include "prot_elm.h" /* 板端源:遥信 = 元件状态 */
#include "prot_set.h" /* 板端源:定值 + 闭锁 */
#include "adc_input.h" /* adc_semantic_t(phasor 实例按语义绑定) */
@@ -37,7 +42,7 @@
typedef struct {
uint32_t ioa;
int8_t sem; /* 快照 yc 槽 = 语义通道(-1 = 非测量量点,当前仅频率) */
float db_default; /* 死区 ROM 默认(运行时生效值 = s_yc_db,卡 10 覆盖) */
float db_default; /* 死区工厂默认(生效真源 = run_par [run_par] 段,卡 10) */
uint8_t src; /* PTS_SRC_* 源类型 */
} pts_yc_def_t;
@@ -45,8 +50,8 @@ typedef struct {
* 额定基准 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 运行参数段可调)。自卡 9 起本表**只作 ROM 默认载体**:运行时死区 =
* s_yc_db 副本;削顶五路不再 sem=-1 绑死,而是绑快照槽、由品质 CLIPPED
* 卡 10 运行参数段可调)。自卡 9 起本表**只作工厂默认载体**:生效死区 =
* App/algo/run_par.c 的 [run_par] 引擎副本(卡 10 真源收编,本层不再存副本);削顶五路不再 sem=-1 绑死,而是绑快照槽、由品质 CLIPPED
* 驱动线上 IV——V0.2(N-18)削顶消失后点位零改动自动启用(docs/任务规划/
* 数据可信.md §10 的 N-18 行)。
* ⚠ 过渡期(eng=0 引脚电压域,卡 12 标定前):上送值本身非二次值,死区
@@ -70,7 +75,6 @@ static const pts_yc_def_t k_yc[] = {
* 判据链:新测量点 → |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 = 确认) */
@@ -115,11 +119,8 @@ static uint8_t s_yx_snap_ok;
* 直接可定);重复调用 = 全状态复位(单测 reset 依赖此语义)。
*/
void iec104_pts_init(void) {
uint8_t i;
for (i = 0u; i < YC_N; i++) {
s_yc_db[i] = k_yc[i].db_default;
}
/* 死区不在本层复位——真源 = run_par(cfg 引擎副本),复位入口唯一
* run_par_init();本函数只复位 COV 状态(单测 reset 依赖此语义) */
(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));
@@ -235,32 +236,35 @@ int iec104_pts_yc_read(uint8_t idx, float *v, uint8_t *qds) {
}
/*
* 函数功能:写死区运行时值(「数据可信」卡 9;卡 10 运行参数段的落点入口)
* 入口参数:idx - YC 表下标 db - 工程量绝对死区(≥0) float
* 返 回 值:0 成功;-1 越界/负值/NaN(RAM 不动) int
* 限定条件:**iec104Task 上下文**(配置属主 = defaultTask,卡 10 接线时经
* 104 任务落地本调用,勿跨任务直调——本表状态无锁)
* 函数功能:写死区(卡 9 API 壳,转发 run_par 真源 + 清本层确认计数)
* 入口参数:idx - YC 表下标 db - 工程量绝对死区(≥0,分档上限见 run_par)
* 返 回 值:0 成功;-1 越界/负值/NaN/越程(run_par 副本不动) int
* 限定条件:**iec104Task 上下文**(run_par 副本写 = 引擎对齐 int32 单字,
* 104 执行段之外调用需自行避免与 104 写并发)
* 函数说明:成功时清零该点待确认计数——跨死区档位不保留半截确认序列;
* 不隐式重建基线(上送基准延续,新死区即刻生效)。
* 不隐式重建基线(上送基准延续,新死区即刻生效)。落盘由 run_par
* 脏标志 + SYS_EVT_RUN_PAR_SAVE 走 defaultTask(卡 10 持久化链)。
*/
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 报恒真) */
if (idx >= YC_N) {
return -1;
}
s_yc_db[idx] = db;
s_yc_cnf[idx] = 0u;
if (run_par_deadband_set(idx, db) != 0) {
return -1; /* 守卫(NaN/负/越程分档上限)单一实现,在 run_par */
}
s_yc_cnf[idx] = 0u; /* 壳语义:跨死区档位不保留半截确认序列 */
return 0;
}
/*
* 函数功能:读当前生效死区
* 函数功能:读当前生效死区(转发 run_par 真源)
* 入口参数:idx - YC 表下标
* 返 回 值:运行时死区;idx 越界返 0.0 float
* 限定条件:任意上下文(单字 float 读,Cortex-M 原子)
* 函数说明:UI 回显 / 卡 10 读写往返校验用;ROM 默认查点表,勿在此反推。
* 返 回 值:生效死区;idx 越界返 0.0 float
* 限定条件:任意上下文(引擎副本 int32 单字读,Cortex-M 原子)
* 函数说明:UI 回显 / 读写往返校验用;工厂默认查点表 db_default 列。
*/
float iec104_pts_yc_deadband(uint8_t idx) {
return (idx < YC_N) ? s_yc_db[idx] : 0.0f;
return (idx < YC_N) ? run_par_deadband(idx) : 0.0f;
}
/*
@@ -389,7 +393,7 @@ void iec104_pts_cov_check(iec104_pts_cov_t *cov) {
if (d < 0.0f) {
d = -d;
}
if (d > s_yc_db[i]) {
if (d > run_par_deadband(i)) {
s_yc_cnf[i]++;
if (s_yc_cnf[i] >= 2u) { /* 连续 2 越 → 确认:报 + 移基准 */
local.yc_changed |= (uint16_t)(1u << i);
@@ -564,7 +568,10 @@ static int s_src_yk_exec(uint32_t ioa, uint8_t dpc, uint8_t select) {
}
/*
* 函数功能:板端默认源 - 定值读(扫 g_prot_set_meta 通讯地址映射)
* 函数功能:板端默认源 - 定值读(**路由顺序 = run_par 表先、未命中落 prot_set**:
* 0x8027~0x802A 属 prot_set 告警段,run_par 命中集为精确地址匹配、
* 永不覆盖之;两表共存靠「先专属表、后通用扫」分流,勿反序——反序
* 当前无冲突但失去 run_par 守卫先行留痕的意义)
* 返 回 值:0 / -1 未知地址(本地项 comm_addr=0 不可寻址,B7 起显式排除)
*/
static int s_src_set_read(uint32_t addr, float *out) {
@@ -573,6 +580,9 @@ static int s_src_set_read(uint32_t addr, float *out) {
if (out == NULL) {
return -1;
}
if (run_par_104_read(addr, out) == 0) {
return 0; /* 运行参数段命中(卡 10) */
}
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)) {
@@ -584,13 +594,22 @@ static int s_src_set_read(uint32_t addr, float *out) {
}
/*
* 函数功能:板端默认源 - 定值写(Q10 范围校验;执行段 prot_set_set_sealed
* 原子写+封存,B7 收口 B5 竞态窗口;置脏 → 104 任务通知落盘)
* 返 回 值:0 / -1 越界或未知地址(本地项 comm_addr=0 不可寻址)
* 函数功能:板端默认源 - 定值写(**先 run_par 后 prot_set**,分流理由见
* s_src_set_read 头注;prot_set 腿 = Q10 范围校验 + 执行段
* prot_set_set_sealed 原子写+封存,B7 收口 B5 竞态窗口)
* 返 回 值:0 / -1 越界或未知地址(本地项 comm_addr=0 不可寻址);两条落盘
* 链独立:run_par 腿脏 → SYS_EVT_RUN_PAR_SAVE(cfg_file),
* prot_set 腿脏 → SYS_EVT_PROT_SET_SAVE(/prot_set.bin)
*/
static int s_src_set_write(uint32_t addr, float v, uint8_t select) {
uint32_t i;
int32_t q10;
int rp;
rp = run_par_104_write(addr, v, select);
if (rp != -2) {
return rp; /* 本段地址:0 接受 / -1 拒绝,不再下探 */
}
for (i = 0u; i < (uint32_t)PS_ID_COUNT; i++) {
const prot_set_meta_t *meta = &g_prot_set_meta[i];
+7 -3
View File
@@ -29,6 +29,8 @@
* 快照(削顶路由品质驱动 IV);死区改运行时副本 +
* set/get 入口;COV 遥测腿 = 死区 2 点确认(seq 去重)
* + 品质变化即报;0x401d 绑频率源(freq_valid → IV)。
* 2026-10-25 DTU 项目组 卡 10:死区真源迁 run_par([run_par] cfg 段 +
* 0x8020~0x8058 读写映射;本层 set/get 为转发壳)。
*/
#ifndef IEC104_PTS_H
#define IEC104_PTS_H
@@ -81,9 +83,11 @@ uint32_t iec104_pts_yc_ioa(uint8_t idx);
/* 读 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 服务任务上下文调用。 */
/* 死区写(卡 9 API;卡 10 起 = 转发 App/algo/run_par.c 真源 + 清本层确认计数):
* db = 工程量绝对值 ≥0(电压 50 V / 电流 1 A / 频率 5 Hz 分档上限);越界/负值/
* NaN → -1 且真源不动;成功时清零该点待确认计数(跨档位不残留半截确认——此壳
* 行为有单测钉住)。仅 104 服务任务上下文调用。落盘 = run_par 脏 → 事件 →
* defaultTask(卡 10 持久化链,docs/子系统设计/IEC104上行设计说明.md §3.4)。 */
int iec104_pts_yc_set_deadband(uint8_t idx, float db);
/* 读当前生效死区(越界 idx 返 0) */
float iec104_pts_yc_deadband(uint8_t idx);
+5 -1
View File
@@ -38,6 +38,7 @@
#include "nand_ftl.h"
#include "net_cfg.h"
#include "cfg_file.h"
#include "run_par.h" /* 运行参数段落盘(卡 10,配置引擎属主 = 本任务) */
#include "prot_set_store.h"
#include "sys_log.h"
#include "sys_events.h"
@@ -207,7 +208,7 @@ void default_task_func(void *argument) {
if (sys_evtHandle != NULL) {
(void)osEventFlagsWait(sys_evtHandle,
SYS_EVT_CFG_SAVE | SYS_EVT_CONSOLE_TX
| SYS_EVT_PROT_SET_SAVE,
| SYS_EVT_PROT_SET_SAVE | SYS_EVT_RUN_PAR_SAVE,
osFlagsWaitAny, 50u);
}
/* 控制台唯一发送点:把环形缓冲中的日志按块发出(无待发时立即返回) */
@@ -231,6 +232,9 @@ void default_task_func(void *argument) {
/* 保护定值落盘(B7):内部消费脏标志,无改动 = 空操作;
* 写失败脏回置 → iec104Task 下轮 poll 再投递事件 = 天然重试 */
(void)prot_set_store_save();
/* 运行参数段落盘(卡 10):同款脏语义;save = 引擎整档外科写
* ([run_par] 9 键 + 被 adc_cal_set 联动改写的 [adc_cal] 两段一趟落) */
(void)run_par_store_save();
#endif
/* 配置文件变更轮询(2s 周期;沉降去抖在引擎内) */
if ((cfg_poll_cnt++ % 40u) == 0u) {
+6
View File
@@ -16,6 +16,7 @@
#include "iec104_pts.h"
#include "net_socket.h"
#include "prot_set.h" /* 定值脏标志 peek(B7 落盘通知) */
#include "run_par.h" /* 运行参数脏 peek(卡 10 落盘通知,同款语义) */
#include "sys_clock.h"
#include "sys_events.h"
#include "task_watchdog.h"
@@ -324,6 +325,11 @@ void iec104_serv_task(void *argument) {
if ((prot_set_dirty_peek() != 0u) && (sys_evtHandle != NULL)) {
(void)osEventFlagsSet(sys_evtHandle, SYS_EVT_PROT_SET_SAVE);
}
/* 运行参数整定(C_SE 0x8020~0x8058 段)→ 通知属主落盘 config.ini
* (卡 10;peek 非消费 = 写盘失败天然重试,与上块 B7 语义完全同款) */
if ((run_par_dirty_peek() != 0u) && (sys_evtHandle != NULL)) {
(void)osEventFlagsSet(sys_evtHandle, SYS_EVT_RUN_PAR_SAVE);
}
osDelay(IEC104_POLL_MS);
}
}
+5
View File
@@ -1022,6 +1022,11 @@
<FileType>1</FileType>
<FilePath>..\App\algo\meas.c</FilePath>
</File>
<File>
<FileName>run_par.c</FileName>
<FileType>1</FileType>
<FilePath>..\App\algo\run_par.c</FilePath>
</File>
<File>
<FileName>prot_set.c</FileName>
<FileType>1</FileType>
@@ -112,6 +112,31 @@
开机在 STORAGE_READY 屏障前装载,损坏/越界/版本换代 = 整表拒绝保持出厂默认
(all-or-nothing)+ 自检留痕 `prot set corrupt`;写盘失败脏标志保留 = 下轮重试。
**运行参数段 `0x8020H~0x8058H`(「数据可信」卡 10,2026-10-25 落实)**:
> ⚠ 逐项地址为**本仓自定**(`docs/算法/继电保护功能复现规划.md` §5.4 的权威表不在仓——
参考项目 `CommPara_ConfigTable` 拿不到,送检点表到位后**以送检为准替换常量表**,
映射表只落在 `App/algo/run_par.c` 顶部常量段一处)。
| 地址 | 项 | 域(C_SE/C_RD 浮点工程量) | 工厂默认 | 真源与守卫 |
|---|---|---|---|---|
| 0x8030~0x8038 | 遥测死区 UA/UB/UC/UN/IA/IB/IC/IN/FREQ | V / A / Hz,分档上限 50/1/5 | 卡 6 额定 0.5 % 档(0.5 V ×4 / 0.005 A ×4 / 0.5 Hz) | `run_par` [run_par] 段 9 键 int32 毫单位;落盘键表 → `docs/子系统设计/网络配置说明.md` §3.5 |
| 0x8040~0x8047 | 通道 k 覆盖(µV/码) | 正浮点;adc_cal 合法带 ROM×[0.5,2.0] 执行段裁决 | 回显生效值(ROM = 152.587890625 / 805.6640625) | **写 = `adc_cal_set` 代理,系数唯一真源在 [adc_cal] 段,run_par 不存系数**(防后人拿 run_par 表当真源表) |
| 0x8048~0x804F | 通道 b 覆盖(码值,可负) | ±8192(adc_cal 带) | 0(回显) | 同上;单侧写 RMW 用另一侧**当前生效值** |
| 其余 | 留白(PT/CT 变比等未启用类别) | — | — | C_RD → COT14+EVI / C_SE → actcon+PN |
- `0x8027~0x802A` 是 prot_set 告警段(过压/欠压),**run_par 命中集精确匹配、永不覆盖**;
两表共存的路由顺序 = **run_par 表先、未命中落 prot_set**(`iec104_pts.c` set 路由,
证据钉在 TC-IEC104-029)。
- 现行门控 = 两段式 + 范围/合法带校验(与保护定值同款),**无「仅暂停/测试态可写」
门控**——文档现行如此,不造新语义;若送检点表要求该类门控,接线点在
`run_par_104_write` 执行段入口,加一处即可。
- 持久化(卡 10):任一写成功置 `run_par` 脏 → iec104Task 同款 peek 投递
`SYS_EVT_RUN_PAR_SAVE`(1<<8) → defaultTask `run_par_store_save()` 调 `cfg_file_save()`
整档外科保存(一趟覆盖 [run_par] 9 键 + 被代理改写的 [adc_cal]/[adc_cal_ofs],
lead 裁②一条事件位一趟够用);失败脏保留 = 下轮重试。消费侧(COV 判定)**实时读
引擎副本**,无缓存无投递(卡 9 的 pts 层 s_yc_db 副本已收编进 run_par)。
## 4. 链路与流程
- 参数:k=12 / w=8 / t1=15s / t2=10s / t3=20s(标准默认);序号 15bit 回绕,
@@ -172,7 +197,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;**板端默认源真实链**(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.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)+ 运行参数段路由/两段式/RMW(卡 10,TC-IEC104-029);`test/host/run_par/`:[run_par] 注册/落盘往返/守卫/系数直通合法带(TC-RUNPAR-001~008);`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 |
@@ -183,8 +208,10 @@
双点带时标不做(V0.1 双点恒 IV,无意义)。
3. YK1~4/13/14 否定确认——出口硬件确认后(R7)转真执行(YK12 已于 B7 转真)。
4. ~~遥测死区 10.0 码值为拟定值~~——工程量死区已于 2026-10-25 卡 6 落地(额定 0.5%);
卡 9(2026-10-25)已改**运行时副本 + set/get 入口 + 2 点确认**;主站可读写(8020H 段
run_par 接线 `iec104_pts_yc_set_deadband`)仍归「数据可信」卡 10。
卡 9 已加 2 点确认;卡 10(2026-10-25)已落地 **0x8020~0x8058 读写映射 + [run_par]
持久化 + 系数覆盖直通 adc_cal**——死区系数唯一真源 = cfg 引擎副本(run_par 存死区、
**不存系数**,系数真源在 [adc_cal] 段,见 §3.4 表注),`iec104_pts_yc_set_deadband`
降级为转发壳。
5. 多主站/双链路冗余不做(单连接语义)。
6. 主站侧验证工具 = `test/host/iec104_master.py`(B6 2026-10-12 就绪,台架往返 18/18,覆盖总召/变位/遥控/定值);**第三方送检环境仍未就绪**——lib60870 级第三方全流程归 B10。
7. **后连优先未实现**(V0.1 = 首到优先):单连接 socket 连接态无法 accept,
@@ -201,5 +228,6 @@
| 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 | **卡 10(道D,「数据可信」M6-①)**:新增 `App/algo/run_par.c/h`——遥测死区 9 点真源收编([run_par] 段 9 键 int32 毫单位落盘,工厂默认 = 卡 6 档;pts 层 s_yc_db 副本删除、set/get 转转发壳并保留清确认计数语义);104 **0x8020~0x8058 读写映射**(§3.4 新表:逐项地址本仓自定、送检点表到位后替换常量段;0x8027~0x802A 永不覆盖,路由 run_par 先 prot_set 后);**k/b 系数覆盖 = adc_cal_set 代理**(合法带/越带留痕复用,run_par 不存系数);持久化走新事件位 `SYS_EVT_RUN_PAR_SAVE`(1<<8) 整档外科 save;单测 test/host/run_par 新建(TC-RUNPAR-001~008,67 断言)+ iec104 套 +TC-IEC104-029 路由钉(141 断言);台架/送检腿待排窗 |
| 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 节 |
@@ -150,6 +150,27 @@ write=1
> **诚实边界**:FTP 协议**明文传输口令**——本方案挡的是**未授权访问**,不挡**网络嗅探**(CH395F 上实现 TLS 不现实)。`pwd_hash` 只防 `/config.ini` 泄露后的**离线口令还原**,盐 = 用户名(无每机随机盐,局域网威胁模型下够用)。
### 3.5 `[run_par]` 段:运行参数(2026-10-25 新增,「数据可信」卡 10)
遥测死区 9 点的持久载体(引擎属主 defaultTask;**现场远程写入口 = 104 段
`0x8030~0x8038`**,本段键供 FTP 手改 / 排障直读,逐项语义与地址表 →
`docs/子系统设计/IEC104上行设计说明.md` §3.4)。**值 = 工程量毫单位 int32 有符号**
(`db_ua=500` 即 0.5 V;电流域 `db_ia=5` 即 0.005 A;频率域 `db_freq=500` 即 0.5 Hz)。
| 键 | 通道 | 工厂默认 | 104 写路径合法域(毫单位) |
|---|---|---|---|
| `db_ua` `db_ub` `db_uc` `db_un` | 电压死区 | 500 | 0 ~ 50000(50 V) |
| `db_ia` `db_ib` `db_ic` `db_in` | 电流死区 | 5 | 0 ~ 1000(1 A) |
| `db_freq` | 频率死区 | 500 | 0 ~ 5000(5 Hz) |
- 键数 9 ≤ 16,**通道系数覆盖不落本段**——系数唯一真源 = `[adc_cal]` / `[adc_cal_ofs]`
两段(卡 4 布局,docs/任务规划/数据可信.md §4.4;104 `0x8040~0x804F` 写 =
`adc_cal_set` 代理,lead 裁②a)。
- 守卫在**写路径**(104/壳 API 拒 NaN/负/越程);**文件通路引擎照收**(同 adc_cal 的
「人工编辑 + 沉降去抖」契约)——死区超程只让上送变钝、不毁量纲,无合法带哨兵。
- 生效链路:load/poll 原地改副本 → 104 COV 每拍实时读(无回调,卡 9/10 契约);
104 写成功 → `SYS_EVT_RUN_PAR_SAVE` → 本任务 `cfg_file_save()` 整档外科落盘。
---
## 4. 生效链路
@@ -308,6 +329,7 @@ ENTER 进入选中地址行 → `↑/↓` 调活动字节(0..255 **环绕**)
| 日期 | 变更 |
|---|---|
| 2026-10-25 | v2.4:**新增 §3.5 `[run_par]` 段(「数据可信」卡 10)**——遥测死区 9 键毫单位布局、104 写路径合法域、系数覆盖不落本段(真源 [adc_cal])、守卫仅在写路径、事件化落盘 |
| 2026-10-11 | v2.3:**新增 §3.4 `[ftp]` 段(N-19 FTP 鉴权)**——`user` / `pwd_hash`(SHA-256(user‖password))/ `write` 字段表、鉴权流程(USER 恒 331 / 恒定时间比较 / 未登录命令门 / 5 败锁 60 s)、改口令步骤与忘口令恢复路径、出厂默认口令启动告警、明文嗅探边界 |
| 2026-10-10 | v2.2:**删除旧档 NCFG 迁移**——`net_cfg_legacy.c/h` 整体移除、`cfg_store_legacy_path()` 撤销;旧档 `/cfg/netcfg.bin` 若存在则被忽略(不读、不删、不迁移),配置回落工厂默认。理由:该一次性路径把整条配置写链挂进启动期调用图,defaultTask 静态最坏栈链 +600 B,且本仓库无携带旧档的设备 |
| 2026-09-14 | v2.1:存储迁移至**通用配置文件** `/config.ini`(INI `[network]` 段,cfg_file 引擎:逐键校验 / 外科手术式保存 / 原子写 / 旧档首启迁移 / 沉降轮询);`net_cfg_store.*` 弃用(由 `cfg_file_store.*` 取代);对外 API 不变 |
@@ -409,6 +409,11 @@
> ⚠️ **待补**:逐项**取值范围 / 步长 / 默认值**取自参考项目 `APP/EquipCfg/Const_CommPara.c` 的 `CommPara_ConfigTable`(`0x8020~0x8882`);本表在实现各保护元件时逐项落实(见 §10.3)。
>
> **2026-10-25 运行参数段落实进度**:卡 10 已落 `App/algo/run_par.c`——本仓自定逐项地址
> 0x8030~0x8038(死区 9 点)/ 0x8040~0x804F(k/b 覆盖直通 adc_cal),表与理由见
> `docs/子系统设计/IEC104上行设计说明.md` §3.4;**送检点表到位后以送检为准替换常量段**。
> 变比/报警/录波/功率置负等其余运行参数类项目前无消费者,留白不预埋。
>
> **2026-10-11 落实进度**:`App/algo/prot_set.c` 已建 RAM 模型(Q10 定点,31 项),**通讯地址逐项对照参考 `Comm/Pub/Const_CommSetPara.c` `g_tCommSetParaTab`**(基址 0x8820:过流 Ⅰ/Ⅱ/Ⅲ = 0x8856~0x8861、过负荷 = 0x8871~0x8874、零序过压 = 0x888E~0x8890;过压/欠压沿用告警段 0x8027~0x802A)。⚠ **默认值/范围为拟定值**(参考 EEPROM 出厂默认表不在本仓库,按 DTU 馈线典型量级拟定),待对照参考出厂表逐项修订。
---
+1
View File
@@ -96,6 +96,7 @@ SIM_EXEMPT = [
("App/algo/sample_ring.c", "L2 环形缓冲纯算法,模拟器无消费路径"),
("App/algo/adc_cal.c", "L1.5 标定折算纯算法 + cfg 段注册(消费者 = MCU 侧 adcTask 每拍;模拟器不跑采样任务),模拟器无消费路径"),
("App/algo/meas.c", "L2 遥测快照发布纯算法(唯一发布者 = MCU 侧 adcTask 200 ms 分频;104/UI 侧消费属卡 9/UI 波次另行接线,模拟器无消费路径;L0.5 单测 test/host/meas 覆盖),模拟器无消费路径"),
("App/algo/run_par.c", "运行参数段纯算法 + [run_par] cfg 段注册 + 104 地址映射(写者 = iec104Task 直通、属主落盘 defaultTask;模拟器不跑 104 协议,模拟器无消费路径;L0.5 单测 test/host/run_par 覆盖),模拟器无消费路径"),
("App/app/app_main.c", "固件启动/就绪屏障,MCU 专属"),
("App/app/ui_data_feed.c", "UI 快照装配器(依赖 phasor/prot_set/iec104 端口常量等固件侧数据源);模拟器不跑装配链,快照由 simulator/main.c 直接发布固定帧,模拟器无消费路径"),
("App/fs/vol_lock.c", "FreeRTOS 互斥量卷锁,MCU 专属"),
+24 -1
View File
@@ -1796,6 +1796,29 @@
"owner_note": "B 层口径(用户裁定 2026-10-21,同 TC-HOST-ADCHEALTH/TC-HOST-ADCCAL 先例;2026-10-25 道D 卡8 建族):属主登记处 = 单测代码目录,不逐号补文档",
"cursor_note": "游标按建族时实测最大号收录(2026-10-25):越界即视为未登记的冒号牌"
},
{
"name": "TC-HOST-RUNPAR",
"class": "TC",
"title": "主机侧单测用例号:运行参数段(test/host/run_par,道D 卡10)",
"pattern": "\\bTC-RUNPAR-[0-9]{1,4}[a-z]?\\b",
"digit_width": [
2,
4
],
"owner_docs_glob": [
"test/host/run_par/*"
],
"scope": "global",
"frozen": false,
"cursor": "TC-RUNPAR-008",
"cursor_owner_only": true,
"check_dangling": true,
"allow_orphans": true,
"subitem_ok": true,
"owner_note": "B 层口径(用户裁定 2026-10-21,同 TC-HOST-MEAS/TC-HOST-ADCCAL 先例;2026-10-25 道D 卡 10 建族):属主登记处 = 单测代码目录,不逐号补文档;路由顺序证据链在 TC-IEC104-029(test/host/iec104)",
"cursor_note": "游标按建族时实测最大号收录(2026-10-25):越界即视为未登记的冒号牌"
},
{
"name": "TC-HOST-IEC104",
"class": "TC",
@@ -1810,7 +1833,7 @@
],
"scope": "global",
"frozen": false,
"cursor": "TC-IEC104-028",
"cursor": "TC-IEC104-029",
"cursor_owner_only": true,
"check_dangling": true,
"allow_orphans": true,
+170 -3
View File
@@ -41,10 +41,12 @@
* →线上 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-027 死区运行时化(卡 10 后 = run_par 真源 + 转发壳):set/get
* + 非法拒 + 运行档压旧档 + 2 点确认/回摆撤销 + 壳清半截确认计数
* TC-IEC104-028 品质翻转直报(假源)+ 板端 seq 去重(同帧不计两点)+
* 换帧只变品质时按集合直报
* TC-IEC104-029 卡 10 路由:0x8030/0x8038 命中 / 0x8027 仍归 prot_set /
* 空隙 COT14 / C_SE 两段式 / k 带拒 / b 单侧 RMW 保 k
*
* 通过:全部 TC PASS(main 返回 0)。
* 创建日期:2026-10-12(B4)
@@ -67,6 +69,115 @@ static int s_total = 0, s_passed = 0, s_failed = 0;
#include "phasor.c"
#include "freq.c"
#include "meas.c" /* 卡 9:板端默认 YC 源 = L2 遥测快照(真实链,非桩) */
/* ===================== 卡 10 真链桩(同 adc_cal 套件先例) =====================
* run_par 依赖真实 cfg 引擎与 adc_cal:dbg_log 走 stub/ 计数宏,文件/堆走
* 内存虚拟盘(cfg_store_* 强符号),adc_input_desc 按 V0.1 权威映射给桩。
* 顺序敏感:桩头与强符号先于被纳入的 .c。 */
#include "dbg_log.h" /* stub/ 优先解析:DBG_* → 计数 */
#include <stdlib.h>
#include "cfg_file_store.h"
#define RP_VFS_CAP 8192u
static uint8_t rp_s_vfs[RP_VFS_CAP];
static size_t rp_s_vfs_len = 0u;
static uint32_t rp_s_vfs_mtime = 100u;
static int rp_s_vfs_exists = 0;
void *cfg_store_alloc(size_t size) { return malloc(size); }
void cfg_store_free(void *p_buf) { free(p_buf); }
uint32_t cfg_store_heap_free(void) { return 0u; }
int cfg_store_read(const char *p_path, uint8_t *p_buf, size_t cap, size_t *p_nread) {
size_t n;
(void)p_path;
if (!rp_s_vfs_exists) {
return -1;
}
n = rp_s_vfs_len;
if (n > cap) {
n = cap;
}
(void)memcpy(p_buf, rp_s_vfs, n);
*p_nread = n;
return 0;
}
int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len) {
(void)p_path;
if (len > RP_VFS_CAP) {
return -1;
}
(void)memcpy(rp_s_vfs, p_buf, len);
rp_s_vfs_len = len;
rp_s_vfs_exists = 1;
rp_s_vfs_mtime++;
return 0;
}
int cfg_store_stat(const char *p_path, cfg_file_stat_t *p_st) {
(void)p_path;
if (!rp_s_vfs_exists) {
return -1;
}
p_st->size = (uint32_t)rp_s_vfs_len;
p_st->mtime = rp_s_vfs_mtime;
return 0;
}
int cfg_store_remove(const char *p_path) {
(void)p_path;
rp_s_vfs_exists = 0;
rp_s_vfs_len = 0u;
return 0;
}
const char *cfg_store_config_path(void) { return "/config.ini"; }
const char *cfg_store_tmp_path(void) { return "/config.tmp"; }
/* ---- sys_log 桩:只计数自检留痕 ---- */
#include "sys_log.h"
static int rp_s_syslog_count = 0;
void syslog_post(syslog_id_t id, const char *desc) {
(void)id;
(void)desc;
rp_s_syslog_count++;
}
/* ---- adc_input_desc 桩:V0.1 权威映射(docs/硬件平台/ADC通道映射说明.md) ---- */
#include "adc_input.h"
static const adc_channel_desc_t rp_s_desc[ADC_SEM_COUNT] = {
{ ADC_SRC_TPAFE, 6u }, /* UA = buf[6] */
{ ADC_SRC_STM32, 14u }, /* UB = CH14(PC4) */
{ ADC_SRC_STM32, 3u }, /* UC = CH3 */
{ ADC_SRC_TPAFE, 5u }, /* UN = buf[5] */
{ ADC_SRC_TPAFE, 2u }, /* IA = buf[2] */
{ ADC_SRC_STM32, 6u }, /* IB = CH6 */
{ ADC_SRC_STM32, 1u }, /* IC = CH1 */
{ ADC_SRC_STM32, 2u }, /* IN = CH2 */
{ ADC_SRC_STM32, 5u }, /* TEMP = CH5 */
};
const adc_channel_desc_t *adc_input_desc(adc_semantic_t ch) {
if ((int)ch < 0 || (unsigned)ch >= (unsigned)ADC_SEM_COUNT) {
return NULL;
}
return &rp_s_desc[(unsigned)ch];
}
/* dbg 计数定义(stub/dbg_log.h 声明 extern) */
int g_dbgstub104_err_count = 0;
int g_dbgstub104_info_count = 0;
int g_dbgstub104_debug_count = 0;
#include "cfg_file.c"
#include "adc_cal.c"
#include "run_par.c"
#include "prot_elm.c"
#include "iec104_pts.c"
#include "iec104_link.c"
@@ -215,6 +326,7 @@ static void reset_all(uint32_t now)
{
prot_set_init();
prot_elm_init();
run_par_init(); /* 卡 10:死区/脏复位到工厂默认(重复注册 -1 走计数桩,无害) */
iec104_pts_init();
iec104_pts_bind_src(&g_fake);
(void)memset(s_f_yc, 0, sizeof(s_f_yc));
@@ -1038,7 +1150,8 @@ static void tc_db_runtime(void)
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) */
run_par_init(); /* 恢复工厂默认档(0.5)——卡 10 复位入口 */
iec104_pts_init();
iec104_pts_bind_src(&g_fake);
s_f_yc[0] = 100.0f;
iec104_pts_cov_check(&cov); /* 建线 */
@@ -1057,6 +1170,18 @@ static void tc_db_runtime(void)
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");
/* 壳语义钉(lead 裁⑤):iec104_pts_yc_set_deadband 成功须清零该点半截确认
* 计数——若不清,下面第一拍就凑满 2 点直发,与"跨档位重新起算"矛盾 */
s_f_yc[0] = 100.0f; /* rep=101.4 起 d=1.4 > 0.5:第 1 越挂起 */
iec104_pts_cov_check(&cov);
CHECK((cov.yc_changed & 0x0001u) == 0u, "TC-IEC104-027 shell pin arms first");
iec104_pts_yc_set_deadband(0u, 0.4f);
s_f_yc[0] = 100.5f; /* d=0.9 > 0.4:计数已清则仍只是第 1 越 */
iec104_pts_cov_check(&cov);
CHECK((cov.yc_changed & 0x0001u) == 0u, "TC-IEC104-027 shell clears pending count");
iec104_pts_cov_check(&cov); /* 新档连续第 2 越 → 确认 */
CHECK((cov.yc_changed & 0x0001u) != 0u, "TC-IEC104-027 reconfirms after shell clear");
}
static void tc_cov_qds_seq(void)
@@ -1100,6 +1225,47 @@ static void tc_cov_qds_seq(void)
"TC-IEC104-028 clipped flip per frame reported as a set");
}
/* 卡 10:0x8020~0x8058 读写路由(板端默认源,run_par 表先 / prot_set 后) */
static void tc_pts_rpar_route(void)
{
float v;
reset_all(1000u);
iec104_pts_bind_src(NULL);
CHECK((iec104_pts_set_read(0x8030u, &v) == 0) && (fabsf(v - 0.5f) < 1e-4f),
"TC-IEC104-029 0x8030 deadband UA reads factory default");
CHECK((iec104_pts_set_read(0x8038u, &v) == 0) && (fabsf(v - 0.5f) < 1e-4f),
"TC-IEC104-029 0x8038 deadband freq reads factory 0.5 Hz");
/* 0x8027~0x802A = prot_set 告警段(欠压默认 20 V)——run_par 精确匹配
* 永不覆盖;路由顺序的直接证据 */
CHECK((iec104_pts_set_read(0x8027u, &v) == 0) && (fabsf(v - 20.0f) < 0.01f),
"TC-IEC104-029 0x8027 still served by prot_set table");
CHECK(iec104_pts_set_read(0x8039u, &v) == -1, "TC-IEC104-029 gap address unknown");
/* C_SE 两段式:选择段校验不提交,执行段落真源 */
CHECK((iec104_pts_set_write(0x8031u, 0.7f, 1u) == 0)
&& (fabsf(iec104_pts_yc_deadband(1u) - 0.5f) < 1e-4f),
"TC-IEC104-029 select phase validates without commit");
CHECK((iec104_pts_set_write(0x8031u, 0.7f, 0u) == 0)
&& (fabsf(iec104_pts_yc_deadband(1u) - 0.7f) < 1e-4f)
&& (run_par_dirty_peek() != 0u),
"TC-IEC104-029 execute commits and marks dirty");
CHECK(iec104_pts_set_write(0x8031u, 51.0f, 1u) == -1,
"TC-IEC104-029 over-range rejected in select");
/* k/b 覆盖直通 adc_cal(UA 合法带 = ROM x[0.5,2.0] = [76.29,305.17]) */
CHECK((iec104_pts_set_write(0x8040u, 300.0f, 0u) == 0)
&& (fabsf(adc_cal_k_uv(ADC_SEM_UA) - 300.0f) < 0.01f),
"TC-IEC104-029 k override lands in adc_cal");
CHECK(iec104_pts_set_write(0x8040u, 400.0f, 0u) == -1,
"TC-IEC104-029 k band reject (via adc_cal)");
CHECK((iec104_pts_set_write(0x8048u, -5.0f, 0u) == 0)
&& (fabsf(adc_cal_b_codes(ADC_SEM_UA) + 5.0f) < 1e-3f)
&& (fabsf(adc_cal_k_uv(ADC_SEM_UA) - 300.0f) < 0.01f),
"TC-IEC104-029 b single-side write keeps k (RMW)");
CHECK((iec104_pts_set_read(0x8048u, &v) == 0) && (fabsf(v + 5.0f) < 1e-3f),
"TC-IEC104-029 b echo via run_par read path");
run_par_init(); /* 收尾:把 k/b 脏与副本复位留给后续 TC 的干净出厂态 */
}
int main(void)
{
printf("=== IEC104 Protocol Stack Host Tests ===\n");
@@ -1131,6 +1297,7 @@ int main(void)
tc_pts_freq();
tc_db_runtime();
tc_cov_qds_seq();
tc_pts_rpar_route();
printf("=== IEC104 Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);
return (s_failed == 0) ? 0 : 1;
+8 -1
View File
@@ -4,7 +4,8 @@
做什么:用本机 MSVC 离线编译 test/host/iec104/iec104_test.c(#include 纳入
生产源文件 iec104_asdu.c + iec104_pts.c + iec104_link.c,及数据源链接
依赖 prot_set.c + prot_elm.c + phasor.c + sample_ring.c),验证
依赖 prot_set.c + prot_elm.c + phasor.c + sample_ring.c + freq.c +
meas.c,卡 10 起再 + cfg_file.c + adc_cal.c + run_par.c 真链),验证
APCI 重组 / STARTDT 握手 / 总召分片 / COV / k-w-t1~t3 / 遥控与定值两段式 /
C_RD / C_CS / 错误应答(EVI)。
@@ -28,8 +29,14 @@ from unit_test_harness import TestBuild, run_host_unit_test # noqa: E402
BUILD = TestBuild(
name="IEC104 Tests",
src=os.path.join(HERE, "iec104_test.c"),
# 卡 10 起 stub 先于生产目录(dbg_log.h → 计数宏桩);App/cfg + App/log
# 真头供 cfg_file.h / cfg_file_store.h / sys_log.h;adc_input_desc /
# syslog_post / cfg_store_* 由测试单元给强符号桩(同 adc_cal 套件先例)。
include_dirs=[
os.path.join(HERE, "stub"),
os.path.join(ROOT, "App", "algo"),
os.path.join(ROOT, "App", "cfg"),
os.path.join(ROOT, "App", "log"),
os.path.join(ROOT, "App", "protocol", "iec104"),
HERE,
],
+20
View File
@@ -0,0 +1,20 @@
/*
* 主机侧单测桩:dbg_log.h(IEC104 套件专用)
* 模块功能:为卡 10 起纳入本 TU 的 cfg_file.c / adc_cal.c / run_par.c 提供
* 主机可编译的日志宏(映射为计数)。⚠ 仅供 test/host/iec104/ 使用,
* 不参与 MCU 构建。模式同 test/host/adc_cal/stub/ 先例。
* 创建日期:2026-10-25(道D 卡 10)
*/
#ifndef DBG_LOG_H
#define DBG_LOG_H
extern int g_dbgstub104_err_count;
extern int g_dbgstub104_info_count;
extern int g_dbgstub104_debug_count;
#define DBG_INFO(fmt, ...) do { g_dbgstub104_info_count++; } while (0)
#define DBG_DEBUG(fmt, ...) do { g_dbgstub104_debug_count++; } while (0)
#define DBG_ERROR(fmt, ...) do { g_dbgstub104_err_count++; } while (0)
#endif /* DBG_LOG_H */
+3
View File
@@ -202,6 +202,9 @@ int sys_clock_set_unix(uint32_t unix_secs)
return g_clock_set_rc;
}
uint8_t prot_set_dirty_peek(void) { return (uint8_t)g_dirty; }
/* run_par 脏 peek 假件(卡 10):任务主循环在单测中不跑,脏恒 0 =
* 不投递 SYS_EVT_RUN_PAR_SAVE;落盘链真身覆盖在 test/host/run_par。 */
uint8_t run_par_dirty_peek(void) { return 0u; }
uint32_t osEventFlagsSet(osEventFlagsId_t ef_id, uint32_t flags)
{
(void)ef_id;
+393
View File
@@ -0,0 +1,393 @@
/*
* 模块名称:运行参数段(run_par)主机侧单元测试
* 模块功能:离线钉死「数据可信」卡 10 的三层契约——① [run_par] cfg 段:
* 注册/工厂默认/落盘往返/文件改值生效(真实 cfg 引擎 + 内存虚拟盘,
* 同 adc_cal 套件先例);② 104 地址映射:命中集/空隙/两段式/分档
* 量程守卫;③ k/b 覆盖直通 adc_cal_set(真实标定层):合法带拒绝、
* 单侧 RMW 保另一侧、回显 = 生效值视图。
* 被测对象:真实源文件(#include 纳入):run_par.c + cfg_file.c + adc_cal.c;
* dbg 计数桩(stub/dbg_log.h)+ cfg_store_* 内存虚拟盘 + adc_input_desc
* V0.1 权威映射桩。**路由顺序证据(run_par 表先 / prot_set 后)在
* test/host/iec104 TC-IEC104-029 钉(那边才有全协议栈)**。
*
* 覆盖点(TC-RUNPAR-xxx;属主登记处 = 本目录,族 TC-HOST-RUNPAR):
* TC-RUNPAR-001 init 工厂默认 = 卡 6 额定档 + idx 守卫
* TC-RUNPAR-002 死区写往返 + 分档上限(V/A/Hz)+ NaN/负值拒 + 定点量化
* TC-RUNPAR-003 落盘链:set→脏→store_save 成功清脏 + 文件含 9 键 +
* load 回读往返 + 外部改文件值生效 + 无脏 save 空操作
* TC-RUNPAR-004 104 读映射:0x8030~0x8038 命中 / 空隙与邻段 -1 / k·b 回显
* TC-RUNPAR-005 104 两段式:选择段不提交不置脏,执行段提交置脏
* TC-RUNPAR-006 k/b 直通:合法带接受与拒绝 / 非正 k 拒 / b 单侧 RMW 保 k /
* 非法带值经 load 注入后回显 = 生效值(哨兵视图)
* TC-RUNPAR-007 init 幂等:重复调用复位默认 + 清脏;重注册被引擎拒(留痕)
* TC-RUNPAR-008 引擎副本 = 生效值(无回调):load 原地改副本即时可见
*
* 通过:全部 TC PASS(main 返回 0)。
* 创建日期:2026-10-25(道D 卡 10)
*/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* 先桩头后生产源码(顺序敏感,同 adc_cal 套件) */
#include "dbg_log.h"
/* ===================== 内存虚拟文件系统(cfg_store_* 强符号) ===================== */
#include "cfg_file_store.h"
#define VFS_CAP 8192u
static uint8_t s_vfs[VFS_CAP];
static size_t s_vfs_len = 0u;
static uint32_t s_vfs_mtime = 100u;
static int s_vfs_exists = 0;
static int s_vfs_writes = 0;
void *cfg_store_alloc(size_t size) { return malloc(size); }
void cfg_store_free(void *p_buf) { free(p_buf); }
uint32_t cfg_store_heap_free(void) { return 0u; }
int cfg_store_read(const char *p_path, uint8_t *p_buf, size_t cap, size_t *p_nread) {
size_t n;
(void)p_path;
if (!s_vfs_exists) {
return -1;
}
n = s_vfs_len;
if (n > cap) {
n = cap;
}
(void)memcpy(p_buf, s_vfs, n);
*p_nread = n;
return 0;
}
int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len) {
(void)p_path;
if (len > VFS_CAP) {
return -1;
}
(void)memcpy(s_vfs, p_buf, len);
s_vfs_len = len;
s_vfs_exists = 1;
s_vfs_mtime++;
s_vfs_writes++;
return 0;
}
int cfg_store_stat(const char *p_path, cfg_file_stat_t *p_st) {
(void)p_path;
if (!s_vfs_exists) {
return -1;
}
p_st->size = (uint32_t)s_vfs_len;
p_st->mtime = s_vfs_mtime;
return 0;
}
int cfg_store_remove(const char *p_path) {
(void)p_path;
s_vfs_exists = 0;
s_vfs_len = 0u;
return 0;
}
const char *cfg_store_config_path(void) { return "/config.ini"; }
const char *cfg_store_tmp_path(void) { return "/config.tmp"; }
/* ---- sys_log 桩 ---- */
#include "sys_log.h"
static int s_syslog_count = 0;
void syslog_post(syslog_id_t id, const char *desc) {
(void)id;
(void)desc;
s_syslog_count++;
}
/* ---- adc_input_desc 桩:V0.1 权威映射(docs/硬件平台/ADC通道映射说明.md) ---- */
#include "adc_input.h"
static const adc_channel_desc_t s_desc_stub[ADC_SEM_COUNT] = {
{ ADC_SRC_TPAFE, 6u }, /* UA = buf[6] */
{ ADC_SRC_STM32, 14u }, /* UB = CH14(PC4) */
{ ADC_SRC_STM32, 3u }, /* UC = CH3 */
{ ADC_SRC_TPAFE, 5u }, /* UN = buf[5] */
{ ADC_SRC_TPAFE, 2u }, /* IA = buf[2] */
{ ADC_SRC_STM32, 6u }, /* IB = CH6 */
{ ADC_SRC_STM32, 1u }, /* IC = CH1 */
{ ADC_SRC_STM32, 2u }, /* IN = CH2 */
{ ADC_SRC_STM32, 5u }, /* TEMP = CH5 */
};
const adc_channel_desc_t *adc_input_desc(adc_semantic_t ch) {
if ((int)ch < 0 || (unsigned)ch >= (unsigned)ADC_SEM_COUNT) {
return NULL;
}
return &s_desc_stub[(unsigned)ch];
}
/* dbg 计数定义(stub/dbg_log.h extern 声明) */
int g_rptest_err_count = 0;
int g_rptest_info_count = 0;
int g_rptest_debug_count = 0;
/* ===================== 测试框架(极简,同 adc_cal 套件) ===================== */
static int s_total = 0, s_passed = 0, s_failed = 0;
#define CHECK(cond, name) do { \
s_total++; \
if (cond) { s_passed++; printf(" [PASS] %s\n", name); } \
else { s_failed++; printf(" [FAIL] %s (line %d)\n", name, __LINE__); } \
} while (0)
#define ASSERT_NEAR(a, b, eps, name) CHECK(fabsf((float)(a) - (float)(b)) <= (eps), name)
/* ===================== 被测代码 ===================== */
#include "cfg_file.c"
#include "adc_cal.c"
#include "run_par.c"
static void vfs_reset(void) {
s_vfs_len = 0u;
s_vfs_mtime = 100u;
s_vfs_exists = 0;
}
/* 全复位:引擎段复位 + run_par 复位 + 标定复位(重注册被拒 = 预期,清计数) */
static void reset_all_rp(void) {
run_par_init();
adc_cal_init();
g_rptest_err_count = 0;
}
/* ===================== 测试用例 ===================== */
static void tc_defaults(void)
{
uint8_t i;
vfs_reset();
reset_all_rp();
for (i = 0u; i < 4u; i++) {
CHECK(fabsf(run_par_deadband(i) - 0.5f) <= 1e-6f, "TC-RUNPAR-001 voltage ch factory 0.5 V");
}
for (i = 4u; i < 8u; i++) {
CHECK(fabsf(run_par_deadband(i) - 0.005f) <= 1e-9f, "TC-RUNPAR-001 current ch factory 0.005 A");
}
CHECK(fabsf(run_par_deadband(8u) - 0.5f) <= 1e-6f, "TC-RUNPAR-001 freq factory 0.5 Hz");
CHECK(run_par_deadband(9u) == 0.0f, "TC-RUNPAR-001 idx guard returns zero");
CHECK(run_par_dirty_peek() == 0u, "TC-RUNPAR-001 init leaves clean");
}
static void tc_set_range(void)
{
volatile float fnan;
vfs_reset();
reset_all_rp();
CHECK((run_par_deadband_set(0u, 0.7f) == 0)
&& (fabsf(run_par_deadband(0u) - 0.7f) <= 1e-6f),
"TC-RUNPAR-002 write round trip voltage domain");
CHECK((run_par_deadband_set(4u, 0.02f) == 0)
&& (fabsf(run_par_deadband(4u) - 0.02f) <= 1e-6f),
"TC-RUNPAR-002 write round trip current domain");
CHECK(run_par_deadband_set(0u, 50.0f) == 0, "TC-RUNPAR-002 voltage cap endpoint inclusive");
CHECK((run_par_deadband_set(0u, 50.001f) == -1)
&& (fabsf(run_par_deadband(0u) - 50.0f) <= 1e-6f),
"TC-RUNPAR-002 over voltage cap rejected");
CHECK(run_par_deadband_set(4u, 1.001f) == -1, "TC-RUNPAR-002 current cap 1 A enforced");
CHECK(run_par_deadband_set(8u, 5.001f) == -1, "TC-RUNPAR-002 freq cap 5 Hz enforced");
CHECK(run_par_deadband_set(0u, -0.001f) == -1, "TC-RUNPAR-002 negative rejected");
fnan = 0.0f;
fnan = fnan / fnan;
CHECK(run_par_deadband_set(0u, fnan) == -1, "TC-RUNPAR-002 NaN rejected");
CHECK(run_par_deadband_set(9u, 1.0f) == -1, "TC-RUNPAR-002 idx guard rejected");
CHECK((run_par_deadband_set(2u, 0.1234f) == 0)
&& (fabsf(run_par_deadband(2u) - 0.123f) <= 1e-6f),
"TC-RUNPAR-002 milli quantization rounds to nearest");
}
static void tc_persist(void)
{
char probe[24];
vfs_reset();
reset_all_rp();
CHECK(run_par_store_save() == 0, "TC-RUNPAR-003 clean save is no-op ok");
CHECK(run_par_deadband_set(2u, 0.9f) == 0, "TC-RUNPAR-003 seed write ok");
CHECK(run_par_dirty_peek() == 1u, "TC-RUNPAR-003 write marks dirty");
CHECK((run_par_store_save() == 0) && (run_par_dirty_peek() == 0u),
"TC-RUNPAR-003 save clears dirty on success");
(void)memset(probe, 0, sizeof(probe));
(void)memcpy(probe, "db_uc=", 6u);
CHECK((s_vfs_exists == 1) && (strstr((const char *)s_vfs, "db_uc=900") != NULL),
"TC-RUNPAR-003 file carries milli value");
CHECK(strstr((const char *)s_vfs, "db_freq=500") != NULL,
"TC-RUNPAR-003 all nine keys present (freq spot check)");
/* 往返:改内存 → 落盘 → 引擎 load 回读 */
CHECK(run_par_deadband_set(4u, 0.02f) == 0, "TC-RUNPAR-003 second seed");
(void)run_par_store_save();
run_par_deadband_set(4u, 0.5f); /* 覆盖内存态 */
CHECK(cfg_file_load() == 0, "TC-RUNPAR-003 engine reload ok");
CHECK(fabsf(run_par_deadband(4u) - 0.02f) <= 1e-9f,
"TC-RUNPAR-003 reload restores last saved value");
/* set(4,0.5) 是一次未落盘的本地整定 → 脏必须活过 reload(reload 不是
* 清脏事件;只有 save 成功才清) */
CHECK(run_par_dirty_peek() == 1u, "TC-RUNPAR-003 unsaved local edit survives reload");
/* 外部改档(FTP 手改 config.ini 的现场形态):load 后副本即生效值 */
{
static const char s_edited[] =
"[run_par]\ndb_ua=250\ndb_ub=500\ndb_uc=500\ndb_un=500\n"
"db_ia=10\ndb_ib=5\ndb_ic=5\ndb_in=5\ndb_freq=800\n";
(void)memcpy(s_vfs, s_edited, sizeof(s_edited) - 1u);
s_vfs_len = sizeof(s_edited) - 1u;
s_vfs_exists = 1;
s_vfs_mtime++;
}
CHECK(cfg_file_load() == 0, "TC-RUNPAR-003 edited file accepted");
CHECK((fabsf(run_par_deadband(0u) - 0.25f) <= 1e-6f)
&& (fabsf(run_par_deadband(4u) - 0.01f) <= 1e-9f)
&& (fabsf(run_par_deadband(8u) - 0.8f) <= 1e-6f),
"TC-RUNPAR-003 edited values live in copy immediately");
/* 越界文件值:引擎照收(int32 域),104/COV 读到超程值 —— 守卫在**写路径**,
* 文件通路的防线是"人工编辑 + 沉降去抖 + 可再改回";与 adc_cal 合法带不同,
* 死区超程不毁量纲(只是上送变钝),此处钉住"引擎不吞值"即可 */
CHECK(fabsf(run_par_deadband(0u) - 0.25f) <= 1e-6f, "TC-RUNPAR-003 value stable re-read");
}
static void tc_104_read(void)
{
float v;
vfs_reset();
reset_all_rp();
(void)run_par_deadband_set(0u, 0.6f);
CHECK((run_par_104_read(0x8030u, &v) == 0) && (fabsf(v - 0.6f) <= 1e-6f),
"TC-RUNPAR-004 db first slot hit");
CHECK((run_par_104_read(0x8038u, &v) == 0) && (fabsf(v - 0.5f) <= 1e-6f),
"TC-RUNPAR-004 db last slot (freq) hit");
CHECK(run_par_104_read(0x8039u, &v) == -1, "TC-RUNPAR-004 gap address not owned");
CHECK(run_par_104_read(0x8027u, &v) == -1, "TC-RUNPAR-004 prot_set alarm addr untouched");
CHECK(run_par_104_read(0x802Fu, &v) == -1, "TC-RUNPAR-004 below base not claimed");
CHECK((run_par_104_read(0x8040u, &v) == 0) && (fabsf(v - 152.587890625f) <= 1e-4f),
"TC-RUNPAR-004 k echo = adc_cal effective UA");
CHECK((run_par_104_read(0x8047u, &v) == 0) && (fabsf(v - 805.6640625f) <= 1e-4f),
"TC-RUNPAR-004 k echo last channel (IN, STM32 ROM)");
CHECK((run_par_104_read(0x8048u, &v) == 0) && (v == 0.0f),
"TC-RUNPAR-004 b echo factory zero");
CHECK(run_par_104_read(0x8050u, &v) == -1, "TC-RUNPAR-004 above b block not owned");
}
static void tc_104_two_step(void)
{
float v;
vfs_reset();
reset_all_rp();
CHECK((run_par_104_write(0x8031u, 0.8f, 1u) == 0)
&& (fabsf(run_par_deadband(1u) - 0.5f) <= 1e-6f)
&& (run_par_dirty_peek() == 0u),
"TC-RUNPAR-005 select validates without commit or dirty");
CHECK((run_par_104_write(0x8031u, 0.8f, 0u) == 0)
&& (fabsf(run_par_deadband(1u) - 0.8f) <= 1e-6f)
&& (run_par_dirty_peek() == 1u),
"TC-RUNPAR-005 execute commits and marks dirty");
CHECK(run_par_104_write(0x8031u, 60.0f, 1u) == -1, "TC-RUNPAR-005 range reject in select");
CHECK(run_par_104_write(0x8039u, 1.0f, 0u) == -2, "TC-RUNPAR-005 foreign addr passes through");
CHECK(run_par_104_write(0x8027u, 1.0f, 0u) == -2, "TC-RUNPAR-005 alarm addr passes through");
CHECK((run_par_104_read(0x8031u, &v) == 0) && (fabsf(v - 0.8f) <= 1e-6f),
"TC-RUNPAR-005 committed value readable at same addr");
}
static void tc_104_cal_proxy(void)
{
float v;
vfs_reset();
reset_all_rp();
CHECK(run_par_104_write(0x8040u, 300.0f, 0u) == 0, "TC-RUNPAR-006 k in-band accepted");
CHECK((run_par_104_read(0x8040u, &v) == 0) && (fabsf(v - 300.0f) <= 0.01f),
"TC-RUNPAR-006 k echo follows adc_cal_set");
CHECK((run_par_104_write(0x8040u, 400.0f, 0u) == -1)
&& (fabsf(adc_cal_k_uv(ADC_SEM_UA) - 300.0f) <= 0.01f),
"TC-RUNPAR-006 k over band rejected by adc_cal, RAM intact");
CHECK((run_par_104_write(0x8040u, 70.0f, 0u) == -1),
"TC-RUNPAR-006 k under band rejected (0.5x ROM)");
CHECK(run_par_104_write(0x8040u, -1.0f, 0u) == -1, "TC-RUNPAR-006 non-positive k rejected");
CHECK((run_par_104_write(0x8048u, -5.0f, 0u) == 0)
&& (fabsf(adc_cal_b_codes(ADC_SEM_UA) + 5.0f) <= 1e-3f)
&& (fabsf(adc_cal_k_uv(ADC_SEM_UA) - 300.0f) <= 0.01f),
"TC-RUNPAR-006 b single-side RMW keeps k");
CHECK(run_par_104_write(0x8048u, -9000.0f, 0u) == -1, "TC-RUNPAR-006 b over +-8192 rejected");
CHECK((run_par_104_write(0x8040u, 200.0f, 1u) == 0)
&& (fabsf(adc_cal_k_uv(ADC_SEM_UA) - 300.0f) <= 0.01f),
"TC-RUNPAR-006 k select does not touch adc_cal");
/* 生效值视图回显:文件塞越带 k → 哨兵回退 ROM → 104 回显 ROM */
{
static const char s_badk[] =
"[adc_cal]\nk_ua=99011483\nvalid=1\n";
(void)memcpy(s_vfs, s_badk, sizeof(s_badk) - 1u);
s_vfs_len = sizeof(s_badk) - 1u;
s_vfs_exists = 1;
s_vfs_mtime++;
}
CHECK(cfg_file_load() == 0, "TC-RUNPAR-006 poisoned file loaded by engine");
CHECK((run_par_104_read(0x8040u, &v) == 0) && (fabsf(v - 152.587890625f) <= 1e-4f),
"TC-RUNPAR-006 echo = effective (ROM fallback), not raw file value");
reset_all_rp();
CHECK(cfg_file_save() == 0, "TC-RUNPAR-006 reset file rewritten");
}
static void tc_init_idempotent(void)
{
int err_before;
vfs_reset();
reset_all_rp();
(void)run_par_deadband_set(1u, 0.9f);
(void)run_par_104_write(0x8040u, 200.0f, 0u);
err_before = g_rptest_err_count;
run_par_init();
CHECK(fabsf(run_par_deadband(1u) - 0.5f) <= 1e-6f, "TC-RUNPAR-007 re-init restores defaults");
CHECK(run_par_dirty_peek() == 0u, "TC-RUNPAR-007 re-init clears dirty");
CHECK(g_rptest_err_count > err_before, "TC-RUNPAR-007 dup register leaves trace");
CHECK(fabsf(run_par_deadband(0u) - 0.5f) <= 1e-6f, "TC-RUNPAR-007 values intact after dup reg");
}
static void tc_copy_is_effective(void)
{
static const char s_file[] =
"[run_par]\ndb_ua=650\ndb_ub=500\ndb_uc=500\ndb_un=500\n"
"db_ia=5\ndb_ib=5\ndb_ic=5\ndb_in=5\ndb_freq=500\n";
vfs_reset();
reset_all_rp();
(void)memcpy(s_vfs, s_file, sizeof(s_file) - 1u);
s_vfs_len = sizeof(s_file) - 1u;
s_vfs_exists = 1;
s_vfs_mtime++;
CHECK(cfg_file_load() == 0, "TC-RUNPAR-008 load ok");
CHECK(fabsf(run_par_deadband(0u) - 0.65f) <= 1e-6f,
"TC-RUNPAR-008 no cache layer: copy read is effective value");
CHECK(run_par_dirty_peek() == 0u, "TC-RUNPAR-008 load itself is not a local edit");
}
int main(void)
{
printf("=== RunPar Layer Host Tests ===\n");
tc_defaults();
tc_set_range();
tc_persist();
tc_104_read();
tc_104_two_step();
tc_104_cal_proxy();
tc_init_idempotent();
tc_copy_is_effective();
printf("=== RunPar Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);
return (s_failed == 0) ? 0 : 1;
}
+44
View File
@@ -0,0 +1,44 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""运行参数段(run_par,「数据可信」卡 10)主机侧单元测试运行器(L0.5,纯标准库)。
做什么:用本机 MSVC 离线编译 test/host/run_par/run_par_test.c(#include 纳入
run_par.c + cfg_file.c + adc_cal.c 真实链 + 内存虚拟盘桩),钉死 [run_par]
注册/工厂默认/落盘往返、104 地址映射两段式、k/b 覆盖直通 adc_cal 合法带、
生效值视图回显与 init 幂等。
用法(仓库根目录):
& '.venv\\Scripts\\python.exe' test/host/run_par/run_unit_test.py
退出码:0=PASS / 1=测试失败 / 2=环境错误。
设计依据:docs/任务规划/数据可信.md(卡 10)+ docs/子系统设计/IEC104上行设计说明.md §3.4。
创建日期:2026-10-25(道D 卡 10)
"""
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(HERE)))
sys.path.insert(0, os.path.join(ROOT, "test", "host", "common"))
from unit_test_harness import TestBuild, run_host_unit_test # noqa: E402
# 顺序敏感:stub 先于生产目录(dbg_log.h → 计数宏桩);App/cfg + App/log 真头
# 供 cfg_file.h / cfg_file_store.h / sys_log.h;adc_input_desc / syslog_post /
# cfg_store_* 内存虚拟盘由测试单元给强符号桩(同 adc_cal 套件先例)。
BUILD = TestBuild(
name="RunPar Layer Tests",
src=os.path.join(HERE, "run_par_test.c"),
include_dirs=[
os.path.join(HERE, "stub"),
os.path.join(ROOT, "App", "algo"),
os.path.join(ROOT, "App", "cfg"),
os.path.join(ROOT, "App", "log"),
HERE,
],
summary_marker="=== RunPar Tests:",
)
if __name__ == "__main__":
sys.exit(run_host_unit_test(BUILD))
+20
View File
@@ -0,0 +1,20 @@
/*
* 主机侧单测桩:dbg_log.h(run_par 套件专用)
* 模块功能:为 cfg_file.c / adc_cal.c / run_par.c 提供主机可编译的日志宏
* (映射为计数)。⚠ 仅供 test/host/run_par/ 使用,不参与 MCU 构建。
* 模式同 test/host/adc_cal/stub/ 先例。
* 创建日期:2026-10-25(道D 卡 10)
*/
#ifndef DBG_LOG_H
#define DBG_LOG_H
extern int g_rptest_err_count;
extern int g_rptest_info_count;
extern int g_rptest_debug_count;
#define DBG_INFO(fmt, ...) do { g_rptest_info_count++; } while (0)
#define DBG_DEBUG(fmt, ...) do { g_rptest_debug_count++; } while (0)
#define DBG_ERROR(fmt, ...) do { g_rptest_err_count++; } while (0)
#endif /* DBG_LOG_H */