/* * 模块名称:运行参数段(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 #include #include #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(®, 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(®) != 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; }