feat(运行参数): 卡10 run_par真源收编+104段0x8020~0x8058读写映射(道D M6-①)

新增 App/algo/run_par.c/h(「数据可信」卡 10,lead 四裁落地):
- [run_par] cfg 段:遥测死区 9 键 int32 毫单位(db_ua…db_freq),工厂默认 =
  卡 6 额定 0.5% 档(500mV×4 / 5mA×4 / 500mHz);注册走 adc_cal 同款内核前
  契约(app_main_init),不挂回调 = 引擎副本即生效值;键数 9≤16 不拆段
- 真源收编(裁③):pts 层 s_yc_db 运行时副本删除,COV 每拍实时读 run_par
  (协议→算法向下依赖,同 meas 先例;无投递无缓存 = 顺序/幂等平凡成立);
  iec104_pts_yc_set_deadband()/deadband() 保留为转发壳,壳内保留"成功清
  半截确认计数"语义并由 TC-IEC104-027 追加断言钉住(裁⑤)
- 104 地址映射(裁①):0x8030~0x8038 死区 9 点 / 0x8040~0x8047 k 覆盖 /
  0x8048~0x804F b 覆盖(逐项地址本仓自定——复现规划 §5.4 权威表不在仓,
  送检点表到位后替换 run_par.c 顶部常量段一处;已在 IEC104 §3.4 表头挂账);
  路由顺序 = run_par 表先、未命中落 prot_set——0x8027~0x802A 属 prot_set
  告警段永不覆盖,TC-IEC104-029 双向往返钉死;空隙地址自然 COT14/PN
- 通道系数覆盖 = adc_cal_set 代理(裁②a):合法带/越带 [ERR] 留痕全复用,
  run_par 不存第二份系数(防双真源);单侧写 RMW 用另一侧当前生效值;
  回显 = 生效值视图(文件坏值被哨兵回退 ROM 时主站看到的就是下游吃到的);
  adc_cal 配套:+adc_cal_b_codes() 读取口(回显/RMW 用)+ set() 授权写者
  注记扩至 iec104Task 直通路径(单字原子契约同「数据可信」§4.4)
- 持久化(裁②b):新事件位 SYS_EVT_RUN_PAR_SAVE(1<<8);iec104Task 主循环
  peek 投递 → defaultTask run_par_store_save() 一条事件一趟 cfg_file_save
  整档外科(覆盖 [run_par] 9 键 + 联动 [adc_cal] 两段);失败脏保留 = 天然
  重试(B7 prot_set 同款)。C_SE 现行无"暂停/测试态"门控——文档现行如此
  不造新语义,送检若要求接线点已写进 §3.4 注(裁④)

测试:新建 test/host/run_par(TC-RUNPAR-001~008,67 断言,真实 cfg 引擎 +
真实 adc_cal + 内存虚拟盘同 adc_cal 套件先例;[run_par] 注册契约/落盘往返/
外改文件即副本生效/两段式不提交不置脏/合法带拒+RMW+生效值回显/init 幂等
重注册留痕);iec104 套扩真链 TU(cfg_file+adc_cal+run_par 非桩,141 断言:
+TC-IEC104-029 路由 + 壳清确认计数 3 断言);meas 套静态更名 s_snap*→
s_ms_snap*(与 cfg_file.c s_snap 同 TU 相撞,D1 同款改名法,226 断言不变);
iec104_serv 套补 run_par_dirty_peek 假件。id_registry 建族 TC-HOST-RUNPAR
(游标 008)+ TC-HOST-IEC104 游标 028→029;新钩子 run-par-unit-test +
iec104 钩子触发面随真链扩;uvprojx +run_par.c(合法入库)+ SIM_EXEMPT 登记
(模拟器不编 104 协议,已核实 CMake 面)。

文档:IEC104 §3.4 运行参数段表(含送检替换账/门控未来条件/代理声明)、§7、
§8 第 4 条、变更记录;网络配置说明新增 §3.5 [run_par] 键表;复现规划 §5.4
补运行参数段落实进度。**遗留(交 lead)**:数据可信 §4.4 "引擎 RAM 副本"
一节当时的措辞对象含 pts 侧副本,s_yc_db 删除后该句过时(裁③b 点名,§4.4
在写域外未动)。

gate:@build 0/0(Code=219620 RO=22036 RW=1280 ZI=132864);vs 0cbae7d:
Flash +1912B、RAM(RW+ZI) +8B(37B 新静态被链接器并洞吸收);iec104Task 栈
1456B/margin 43%、defaultTask 3032B 不变;id-check/doc-links/cppcheck/
coding-standard 实跑过;--all-files 全量 45 钩子实跑(43 绿 + 仅 mem-budget /
mem-numbers 两红 = 台账受管区 vs .map 漂移,回填属 lead 单写者区,批 1/道D-1
/D2 同口径):gate: only mem-budget/mem-numbers pending lead ledger backfill
BOM/CR/U+FFFD/字面量 ASCII 逐文件自查通过。
This commit is contained in:
2026-10-25 13:15:41 +08:00
parent 0cbae7dab2
commit 8c72698666
25 changed files with 1310 additions and 63 deletions
+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 */