/* * 模块名称:通道标定与量纲折算(标定折算)——实现 * 模块功能:见 adc_cal.h。折算模型(工程量 = (code + b) × k × 1e-6,k 为 Q16 * µV/码)与两段 cfg 布局([adc_cal]:k_* + valid 位图;[adc_cal_ofs]: * b_*)照 docs/任务规划/数据可信.md §4.1 / §4.4 落地;系数口径 * (ROM 默认 = 量程口径冻结定死的引脚侧 LSB)见 docs/硬件平台/ADC通道映射说明.md * §7.1。 * 并发契约:cfg RAM 副本属主 = 配置引擎(defaultTask 的 load/poll 原地 * 改写),本层逐点折算现读——int32 对齐字在 Cortex-M4 上读写原子, * poll 重载「复位默认 + 逐键解析」存在 µs 级窗口,撞上则该拍该通道 * 为默认值/半新值(相量 20 点窗摊薄,且触发面 = 用户改文件后 4 s * 沉降一次性事件)——「数据可信」§4.4 明载此设计(不挂回调)。 * 合法带哨兵(防损坏文件/错误标定把假电压送进下游):k ∉ ROM×[0.5,2.0] * 或 |b| > ADC_CAL_B_MAX → 该通道折算回退 ROM + b=0,flags 不报 * VALID,每通道每次开机首见沿打一条 [ERR](adc_cal_init 重置留痕)。 * 适用平台:STM32F407ZGTx(Cortex-M4F,硬件 FPU) * 作者:DTU 项目组 * 创建日期:2026-10-25(道A 标定折算与落盘整定) * 修改记录: * v1.0 2026-10-25 DTU 项目组 创建:Q16 系数/零点两段落盘 + 折算 + 三态 flags * v1.1 2026-10-25 DTU 项目组 运行参数段(道D):新增 adc_cal_b_codes() 生效零点 * 读取口;set() 授权写者扩至 iec104Task 直通路径 * (run_par 0x8040~/0x8048~,契约同 §4.4) * + 合法带哨兵回退 + 两点标定公式(台架注入待跑) * v1.2 2026-10-26 DTU 项目组 道F task-16:新增 adc_cal_prot_blocked()——未标定 * 闭锁(UA/UN/IA 三路 VALID 全齐才放行)收成本层单点, * DTU_BENCH 旁路同时收成全仓唯一一处;此前该判据在 * protect_task.c 与 ui_data_feed.c 各写一份,两处一旦 * 漂移,屏上「未标定」与判据实际停走就不是同一件事 * v1.3 2026-10-29 DTU 项目组 故障录波 task-2:新增 adc_cal_k_q16() int32 原值直读口 * (段头 k 字段 LSB 精确,float 反推在 5.28e7 掉尾数, * 口径见 docs/子系统设计/故障录波设计说明.md §6.3) */ /* 头文件包含区 */ #include #include #include "adc_cal.h" #include "cfg_file.h" #define DBG_TAG "[CAL]" #include "dbg_log.h" /* 常量定义区 */ /* 段名(≤ CFG_FILE_MAX_NAME_LEN)与键名(k_ua…b_in,均 4/5 字符) * ——「数据可信」§4.4 布局;键的书写顺序按该表,存储下标按语义枚举 */ #define ADC_CAL_SEC_K "adc_cal" #define ADC_CAL_SEC_B "adc_cal_ofs" /* Q16 → V 的合并比例因子:1e-6 / 65536(float 常量,精确到 1 ulp 以内即可: * 系数本身 Q16 舍入误差 < 0.001 %,见「数据可信」§4.1) */ #define ADC_CAL_K_SCALE (1.52587890625e-11f) /* 静态变量定义区 */ /* [adc_cal] 段 RAM 副本:k 按语义下标 0..7,valid 位图 bit=语义下标。 * data_size 取 offsetof(valid)+1 = 33(尾随对齐填充不属于引擎视图, * 引擎「复位默认」按 data_size memcpy,必须覆盖 valid,见单测 TC-ADCCAL-001) */ typedef struct { int32_t k[8]; uint8_t valid; } adc_cal_kcfg_t; /* [adc_cal_ofs] 段 RAM 副本:b 按语义下标 0..7(int32 有符号,有符号零点类型 * CFG_VAL_INT 是负零点偏移能干净落盘的前提) */ typedef struct { int32_t b[8]; } adc_cal_bcfg_t; static adc_cal_kcfg_t s_kcfg; /* 生效系数副本(引擎原地更新) */ static adc_cal_bcfg_t s_bcfg; /* 生效零点副本 */ /* 工厂默认(ROM 理论系数,量程口径冻结定死,「数据可信」§4.4):**下标 = 语义枚举** * (UA0 / UB1 / UC2 / UN3 / IA4 / IB5 / IC6 / IN7)——UA/UN/IA 走 TPAFE * (16 位 ±5 V 档),UB/UC/IB/IC/IN 走 STM32 ADC1;逐路对过 * docs/硬件平台/ADC通道映射说明.md §3(IN=ADC11/STM32,勿再按分组顺序误写)。 * ROM 表按**当前 V0.1 来源排布**显式书写(不查 desc 运行期派生),因为它是 * 量程口径冻结定稿的存档事实;V0.2 改版(消除双转换器无共同采样触发)时本表与 adc_input 映射表同步改, * 上层语义与 API 不动。CLIPPED 位则**运行期按来源派生**(见 adc_cal_flags) */ static const adc_cal_kcfg_t s_kcfg_rom = { /* idx0 UA / idx1 UB / idx2 UC / idx3 UN */ { ADC_CAL_K_Q16_TPAFE, ADC_CAL_K_Q16_STM32, ADC_CAL_K_Q16_STM32, ADC_CAL_K_Q16_TPAFE, /* idx4 IA / idx5 IB / idx6 IC / idx7 IN */ ADC_CAL_K_Q16_TPAFE, ADC_CAL_K_Q16_STM32, ADC_CAL_K_Q16_STM32, ADC_CAL_K_Q16_STM32 }, 0u /* 出厂未标定:VALID 全 0 */ }; static const adc_cal_bcfg_t s_bcfg_rom = { { 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L } }; /* 合法带哨兵留痕位(bit=语义下标;每通道每次开机只打一条 [ERR], * adc_cal_init 清零 = 新一轮观测窗) */ static uint8_t s_err_latched; /* * 观测计数(三态语义唯一落点在 adc_cal.h,这里只写"怎么自增"):与上面那个留痕位 * 不同,这三个数一个 [ERR] 都不打、也不参与任何判定,因此 **adc_cal_init() 刻意不清** * ——清了水位与计数就失配,会凭空造出一段"假未落盘"差额。 */ static volatile uint32_t s_st_set_ok; /* 成功改写 RAM 副本的次数 */ static volatile uint32_t s_st_set_reject; /* 被拒次数(非法通道 / 越出合法带) */ static volatile uint32_t s_st_persist_water; /* 落盘水位:盖写,绝不自增 */ /* 键表:offset 用 offsetof 取真值(k[0]@0 / k[1]@4 … valid@32) */ static const cfg_key_def_t s_k_keys_tbl[9] = { { "k_ua", CFG_VAL_INT, offsetof(adc_cal_kcfg_t, k[0]), 4u }, { "k_un", CFG_VAL_INT, offsetof(adc_cal_kcfg_t, k[3]), 4u }, { "k_ia", CFG_VAL_INT, offsetof(adc_cal_kcfg_t, k[4]), 4u }, { "k_ub", CFG_VAL_INT, offsetof(adc_cal_kcfg_t, k[1]), 4u }, { "k_uc", CFG_VAL_INT, offsetof(adc_cal_kcfg_t, k[2]), 4u }, { "k_ib", CFG_VAL_INT, offsetof(adc_cal_kcfg_t, k[5]), 4u }, { "k_ic", CFG_VAL_INT, offsetof(adc_cal_kcfg_t, k[6]), 4u }, { "k_in", CFG_VAL_INT, offsetof(adc_cal_kcfg_t, k[7]), 4u }, { "valid", CFG_VAL_UINT, offsetof(adc_cal_kcfg_t, valid), 1u }, }; static const cfg_key_def_t s_b_keys_tbl[8] = { { "b_ua", CFG_VAL_INT, offsetof(adc_cal_bcfg_t, b[0]), 4u }, { "b_un", CFG_VAL_INT, offsetof(adc_cal_bcfg_t, b[3]), 4u }, { "b_ia", CFG_VAL_INT, offsetof(adc_cal_bcfg_t, b[4]), 4u }, { "b_ub", CFG_VAL_INT, offsetof(adc_cal_bcfg_t, b[1]), 4u }, { "b_uc", CFG_VAL_INT, offsetof(adc_cal_bcfg_t, b[2]), 4u }, { "b_ib", CFG_VAL_INT, offsetof(adc_cal_bcfg_t, b[5]), 4u }, { "b_ic", CFG_VAL_INT, offsetof(adc_cal_bcfg_t, b[6]), 4u }, { "b_in", CFG_VAL_INT, offsetof(adc_cal_bcfg_t, b[7]), 4u }, }; /* 函数定义区 */ /* * 函数功能:判定语义通道是否 STM32 源(经 adc_input 权威映射表,不另立第二份表) * 入口参数:sem - 语义通道 unsigned * 返 回 值:1 - STM32;0 - TPAFE(含 desc 取不到时的保守分支) int * 限定条件:无 */ static int s_rom_k_stm32(unsigned sem) { const adc_channel_desc_t *d = adc_input_desc((adc_semantic_t)sem); return ((d != NULL) && (d->source == ADC_SRC_STM32)) ? 1 : 0; } /* * 函数功能:取语义通道的 ROM 默认系数(合法带/回退参照) * 入口参数:sem - 语义下标(0..7) unsigned * 返 回 值:Q16 系数 int32_t * 限定条件:sem < 8(调用方已先行拦截 TEMP/越界) * 函数说明:直接读出厂表本体——保证「哨兵合法带基准」与「引擎工厂默认」 * 永远同一份数(两者若靠重复表达式各算各的,改一处漏一处就是暗雷) */ static int32_t s_rom_k(unsigned sem) { return s_kcfg_rom.k[sem]; } /* * 函数功能:k/b 合法带检查(ROM×[0.5,2.0] / |b| ≤ ADC_CAL_B_MAX) * 入口参数:sem - 语义下标(0..7) unsigned * k - 待检系数 Q16 int32_t * b - 待检零点 码值 int32_t * 返 回 值:1 - 合法;0 - 越带 int * 限定条件:sem < 8 */ static int s_kband_ok(unsigned sem, int32_t k, int32_t b) { const int32_t rom = s_rom_k(sem); if (k < (rom >> 1) || k > (rom << 1)) { return 0; } if (b > ADC_CAL_B_MAX || b < -ADC_CAL_B_MAX) { return 0; } return 1; } /* * 函数功能:越带留痕(每通道每次开机首见沿一条 [ERR],init 重置) * 入口参数:sem - 语义下标 0..7;k/b - 现场值 * 返 回 值:无 * 限定条件:仅 adc_cal_apply/set 内部 */ static void s_note_bad(unsigned sem, int32_t k, int32_t b) { const uint8_t bit = (uint8_t)(1u << sem); if ((s_err_latched & bit) == 0u) { s_err_latched |= bit; DBG_ERROR("ch=%u k_q16=%ld b=%ld out of ROM band [0.5x,2x]/|b|<=%ld -> ROM fallback", (unsigned)sem, (long)k, (long)b, (long)ADC_CAL_B_MAX); } } /* * 函数功能:读通道「当前生效且合法」的 k/b(越带就地回退 ROM) * 入口参数:sem - 语义下标 0..7;p_k/p_b - 输出 * 返 回 值:1 - 用了合法值;0 - 已回退 ROM * 限定条件:sem < 8 */ static int s_eff_kb(unsigned sem, int32_t *p_k, int32_t *p_b) { const int32_t k = s_kcfg.k[sem]; const int32_t b = s_bcfg.b[sem]; if (s_kband_ok(sem, k, b) != 0) { *p_k = k; *p_b = b; return 1; } s_note_bad(sem, k, b); *p_k = s_rom_k(sem); *p_b = 0L; return 0; } /* * 函数功能:标定层初始化——RAM 副本置 ROM 默认 + 注册两段进 cfg 引擎(幂等) * 入口参数:无 * 返 回 值:无 * 限定条件:内核启动前调用(app_main_init);注册必须先于 cfg_file_load * 函数说明:完整契约见 adc_cal_init() 同名声明(adc_cal.h);注册失败(段名重复/ * 段数超限)打 [ERR] 后继续——ROM 默认系数照常生效,仅文件整定失效 */ void adc_cal_init(void) { cfg_section_reg_t reg; (void)memcpy(&s_kcfg, &s_kcfg_rom, sizeof(s_kcfg)); (void)memcpy(&s_bcfg, &s_bcfg_rom, sizeof(s_bcfg)); s_err_latched = 0u; (void)memset(®, 0, sizeof(reg)); reg.name = ADC_CAL_SEC_K; reg.p_data = &s_kcfg; reg.p_defaults = &s_kcfg_rom; reg.data_size = (offsetof(adc_cal_kcfg_t, valid) + 1u); /* 33:覆盖到 valid */ reg.p_keys = s_k_keys_tbl; reg.key_count = 9u; reg.p_cb = NULL; /* 不挂回调:RAM 副本即生效值(「数据可信」§4.4) */ if (cfg_file_register(®) != 0) { DBG_ERROR("register '%s' FAIL (dup/full?) - calibration file values inactive", ADC_CAL_SEC_K); } (void)memset(®, 0, sizeof(reg)); reg.name = ADC_CAL_SEC_B; reg.p_data = &s_bcfg; reg.p_defaults = &s_bcfg_rom; reg.data_size = sizeof(s_bcfg); /* 32 */ reg.p_keys = s_b_keys_tbl; reg.key_count = 8u; reg.p_cb = NULL; if (cfg_file_register(®) != 0) { DBG_ERROR("register '%s' FAIL (dup/full?) - zero-offset file values inactive", ADC_CAL_SEC_B); } } /* * 函数功能:码值 → 工程量折算(「数据可信」§4.1 模型) * 入口参数:sem - 语义通道;code - 原始码值 adc_semantic_t / int32_t * 返 回 值:工程量浮点(未标定 = ADC 引脚电压 V;TEMP/越界 = 原样透传码值) float * 限定条件:无(任意上下文,纯读) * 函数说明:见 adc_cal.h 同名声明;k/b 经 s_eff_kb 合法带哨兵(越带回退 ROM) */ float adc_cal_apply(adc_semantic_t sem, int32_t code) { int32_t k; int32_t b; if ((unsigned)sem >= (unsigned)ADC_SEM_TEMP) { return (float)code; /* TEMP/越界:不纳入线性标定(决策 5),原样透传 */ } (void)s_eff_kb((unsigned)sem, &k, &b); return ((float)(code + b)) * ((float)k) * ADC_CAL_K_SCALE; } /* * 函数功能:读通道可用性标志(消费者判「能不能信」的唯一入口) * 入口参数:sem - 语义通道 adc_semantic_t * 返 回 值:ADC_CAL_F_VALID / ADC_CAL_F_CLIPPED 组合;TEMP/越界恒 0 uint32_t * 限定条件:无 * 函数说明:VALID 需文件位图与 k 合法带**同时**成立(哨兵压制说谎位图,见文件头) */ uint32_t adc_cal_flags(adc_semantic_t sem) { uint32_t f = 0u; if ((unsigned)sem >= (unsigned)ADC_SEM_TEMP) { return 0u; } /* VALID = 文件位图置位 且 k 合法(哨兵不落共享状态,随重载自愈) */ if (((s_kcfg.valid & (uint8_t)(1u << (unsigned)sem)) != 0u) && (s_kband_ok((unsigned)sem, s_kcfg.k[sem], s_bcfg.b[sem]) != 0)) { f |= ADC_CAL_F_VALID; } /* CLIPPED 由**来源**派生(R5:STM32 侧单极性无偏置 → 负半周削顶)—— * V0.2 改版(8 路全接 TPAFE,消除双转换器无共同采样触发)后本位自动恒 0,无需改码, * 对应「数据可信」§10「页面与 104 绑定零改动」的要求 */ if (s_rom_k_stm32((unsigned)sem) != 0) { f |= ADC_CAL_F_CLIPPED; } return f; } /* * 函数功能:未标定闭锁判定(判据所需的 UA/UN/IA 三路是否齐) * 返 回 值:1 - 闭锁;0 - 放行 uint8_t(契约、三路集合的理由、判据停走的四路 * 来源、两个同族谓词的边界都写在 adc_cal.h,本处不重述——重述就是第二真源) * 函数说明:**现查 flags(),本函数不留任何状态**。刻意不复用保护任务里那份 * 「上拍闭锁态」——那是给边沿日志用的,开机首拍前恒 0;拿它当判据等于在 * 「还没判过」的时候谎称「已标定」,而这正是本谓词最贵的失效(屏上 * 「未标定」徽标一消失,现场读到的就是「改了定值就会动作」)。 * DTU_BENCH 旁路留在这里、且全仓只此一处;用 #ifndef 把产品默认路径留在 * 主分支——#ifdef 双分支会让 cppcheck 对未定义宏取真分支、把静态分析引到 * 旁路上报 knownConditionTrueFalse 假信号(实测 2026-10-25,这条形态说明原先 * 写在 protect_task.c 的实现处,随判据一起搬到这里) */ uint8_t adc_cal_prot_blocked(void) { #ifndef DTU_BENCH if (((adc_cal_flags(ADC_SEM_UA) & ADC_CAL_F_VALID) == 0u) || ((adc_cal_flags(ADC_SEM_UN) & ADC_CAL_F_VALID) == 0u) || ((adc_cal_flags(ADC_SEM_IA) & ADC_CAL_F_VALID) == 0u)) { return 1u; } #endif return 0u; } /* * 函数功能:读通道当前生效系数(µV/码,回退后口径) * 入口参数:sem - 语义通道 adc_semantic_t * 返 回 值:k[µV/码];TEMP/越界返 0 float * 限定条件:无 * 函数说明:Q16 现值 ÷ 65536 浮点还原;勿据其反推二次值(可用性看 flags) */ float adc_cal_k_uv(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)k) / 65536.0f; } /* * 函数功能:读通道当前生效系数的 int32 Q16 原值(adc_cal_k_uv 孪生,见 .h 声明) * 入口参数:sem - 语义通道 adc_semantic_t * 返 回 值:k[Q16];TEMP/越界返 0 int32_t * 限定条件:无(任意上下文,纯读) * 函数说明:与 adc_cal_k_uv() 共用 s_eff_kb 生效视图(越带回退 ROM 口径一致), * 差别只在不做浮点还原——录波段头按 LSB 精确记账必须走本口(§6.3) */ int32_t adc_cal_k_q16(adc_semantic_t sem) { int32_t k; int32_t b; if ((unsigned)sem >= (unsigned)ADC_SEM_TEMP) { return 0; } (void)s_eff_kb((unsigned)sem, &k, &b); return k; } /* * 函数功能:读通道当前生效零点偏移(码值,浮点)——adc_cal_k_uv 孪生入口 * 入口参数:sem - 语义通道 adc_semantic_t * 返 回 值:b[码值](越带时 s_eff_kb 已回 0);TEMP/越界返 0 float * 限定条件:无(任意上下文,纯读) * 函数说明:运行参数段的 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(「数据可信」标定落盘与整定入口) * 入口参数:sem - 语义通道;k_q16 - 系数(µV/码×65536);b_codes - 零点(码值) * 返 回 值:0 - 成功;-1 - 参数非法/越出合法带(RAM 不动,[ERR] 留痕) int * 限定条件:整定路径上下文(defaultTask 整定/标定链;iec104Task 经 run_par * 直通为运行参数段授权写者,单字原子契约同 §4.4);落盘由属主经 cfg_file_save * 函数说明:见 adc_cal.h 同名声明 */ int adc_cal_set(adc_semantic_t sem, int32_t k_q16, int32_t b_codes) { if ((unsigned)sem >= (unsigned)ADC_SEM_TEMP) { s_st_set_reject++; /* 观测:连通道号都非法,RAM 未动 */ return -1; } if (s_kband_ok((unsigned)sem, k_q16, b_codes) == 0) { DBG_ERROR("set ch=%u rejected: k_q16=%ld b=%ld out of ROM band [0.5x,2x]/|b|<=%ld", (unsigned)sem, (long)k_q16, (long)b_codes, (long)ADC_CAL_B_MAX); s_st_set_reject++; /* 观测:越出合法带,RAM 未动([ERR] 由上一行打) */ return -1; } s_kcfg.k[sem] = k_q16; s_bcfg.b[sem] = b_codes; s_kcfg.valid |= (uint8_t)(1u << (unsigned)sem); s_st_set_ok++; /* 观测:RAM 副本确实被改写了(与落盘无关) */ return 0; } /* * 函数功能:读标定层观测计数(不取锁、不取时刻、任意上下文可读) * 返 回 值:无 void * 限定条件:无;adc_cal_init() 之前可读(全 0 = 本层还没被改写过) * 函数说明:见 adc_cal.h 同名声明(三态语义与那张成对读表都在那里,本处只取数) */ void adc_cal_get_stats(adc_cal_stats_t *p_out) { if (p_out == NULL) { return; /* 观测件不得反过来制造故障 */ } p_out->set_ok = s_st_set_ok; p_out->set_reject = s_st_set_reject; p_out->persisted_set_ok = s_st_persist_water; } /* * 函数功能:落盘成功回执——把水位盖写为当前的 set_ok(不是自增) * 返 回 值:无 void * 限定条件:仅"整档保存已成功"那一条路径(当前唯一调用点 run_par_store_save()) * 函数说明:盖写而非自增的理由与回归用例见 adc_cal.h 同名声明。水位直接取 set_ok 的 * 当前值,因此它**在结构上不可能越过** set_ok——差额是 uint32 相减,一旦越过 * 就绕回成一个巨大的假未落盘数,这里不留那种可能 */ void adc_cal_note_persisted(void) { s_st_persist_water = s_st_set_ok; /* 水位只会追平,不会越过 */ } /* * 函数功能:两点标定公式(标定项的算术核,纯函数无状态) * 入口参数:c_lo/c_hi、v_lo_uv/v_hi_uv、p_k_q16/p_b 见 adc_cal.h 同名声明 * 返 回 值:0 成功 / -1 参数或斜率非法 / -2 零点越 ±B_MAX int * 限定条件:无(int64 中间量,主机可测;台架注入待跑) */ int adc_cal_two_point(int32_t c_lo, int32_t c_hi, int32_t v_lo_uv, int32_t v_hi_uv, int32_t *p_k_q16, int32_t *p_b) { long long dc; long long dv; long long kq; long long bb; if ((p_k_q16 == NULL) || (p_b == NULL)) { return -1; } dc = (long long)c_hi - (long long)c_lo; dv = (long long)v_hi_uv - (long long)v_lo_uv; if ((dc <= 0LL) || (dv <= 0LL)) { return -1; /* 斜率必须为正:码与电压同向,反向 = 映射/接线错 */ } kq = ((dv << 16) + (dc >> 1)) / dc; /* (v_hi−v_lo)×65536/dc,四舍五入(dc>0) */ if ((kq <= 0LL) || (kq > 100000000LL)) { return -1; /* 绝对合理域上界 1526 µV/code:越界 = 单位/量级错 */ } bb = (((long long)v_lo_uv) << 16) / kq - (long long)c_lo; if ((bb > (long long)ADC_CAL_B_MAX) || (bb < -(long long)ADC_CAL_B_MAX)) { return -2; /* 零点异常 → 先查前端,别用系数凑(§8 风险表) */ } *p_k_q16 = (int32_t)kq; *p_b = (int32_t)bb; return 0; } /* * 函数功能:通道码值域贴轨带门限(采样通道自检 判定的门限来源,按 FS 百分比) *(保护功能清单见 docs/算法/继电保护功能复现规划.md) * 入口参数:sem - 语义通道;p_lo_th/p_hi_th - 输出下/上轨带界 int32_t * * 返 回 值:0 - 已给出门限;-1 - TEMP/越界/参数空(不做贴轨监视) int * 限定条件:无 * 函数说明:状态机在 adc_health_rail_watch();本函数只供门限,无状态 */ int adc_cal_rail_ths(adc_semantic_t sem, int32_t *p_lo_th, int32_t *p_hi_th) { if ((p_lo_th == NULL) || (p_hi_th == NULL) || ((unsigned)sem >= (unsigned)ADC_SEM_TEMP)) { return -1; /* TEMP(热敏非线性,不判「轨」)与越界不做监视 */ } if (s_rom_k_stm32((unsigned)sem) != 0) { *p_hi_th = (int32_t)((4095L * 98L) / 100L); /* 4013 = 98 % FS */ *p_lo_th = (int32_t)((4095L * 2L) / 100L); /* 81 = 触底带(削顶签名) */ } else { *p_hi_th = 32112L; /* +98 % 半量程 */ *p_lo_th = -32112L; /* -98 % 半量程 */ } return 0; }