Files
STM32F4-Base/App/algo/run_par.c
T
wandering1 8c72698666 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 逐文件自查通过。
2026-10-25 13:15:41 +08:00

290 lines
10 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称:运行参数段(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;
}