7c505b2457
用户裁定(2026-11-13):设备本体只需要校正后的参数,不需要自己拟合 标定曲线,故固件侧最小二乘拟合整体移出,拟合的唯一实现留在 PC 侧 test/host/cal_fit.py。 删除的死代码(生产零调用、上一轮 map 里已被 linker 标 Removing): - adc_cal.h:adc_cal_fit() 声明、8 个拒绝码、5 个常量 - adc_cal.c:s_div_round() / s_fit_accum() / adc_cal_fit() 共 224 行 - adc_cal_test.c:tc_00b_fit 226 行(184 -> 150 条) 原样保留的设备侧安全闸: - adc_cal_apply() 折算、adc_cal_set() / adc_cal_set_ratio() 参数合法 带拦截(防工具写坏参数)、adc_cal_flags() / adc_cal_prot_blocked() / adc_cal_rail_ths()。设备不「算」,但必须「用」和「验」。 测试面: - cal_fit.py 转为唯一实现,新增 --selftest:承接原 C 侧金标向量 (r_q16=39322000 / b=12)与 4 条拒绝路径,共 11 条断言;已做变异 测试证明它会红(改坏圆整口径 -> 2 条 FAIL) - cal_two_point.py 删除,两点工具由多点拟合取代 文档口径统一为「求参在 PC、设备只做折算与合法带校验」,6 篇 docs 同步; 执行卡改名 ADC多点标定执行流程.md(正文同步 5 点最小二乘口径)。 cfg 热重载吞段缺陷的解析器契约与 TC-CONFIG-063 机器钉一并入库。 Program Size 不变(Code=256196):删掉的是从未链接进镜像的死代码。 验证:Keil 0 errors 0 warnings;adc_cal 150/150;selftest 11/11; ci_local.py RC=0(static 48/48、manual 三根、size-gate、host-unit 4341/4341);doc-links / mermaid / mojibake 全 0。
678 lines
33 KiB
C
678 lines
33 KiB
C
/*
|
||
* 模块名称:通道标定与量纲折算(标定折算)——实现
|
||
* 模块功能:见 adc_cal.h。折算模型(工程量 = (code + b) × k × 1e-6 × r,
|
||
* k 为 Q16 µV/码、r 为 Q16 比值)与三段 cfg 布局([adc_cal]:k_* + valid 位图;
|
||
* [adc_cal_ofs]:b_*;[adc_cal_sec]:r_*)照
|
||
* docs/算法/采集与算法层设计说明.md §13.2 / §4.4 落地;k 口径
|
||
* (ROM 默认 = 量程口径冻结定死的引脚侧 LSB)见 docs/硬件平台/ADC通道映射说明.md
|
||
* §7.1,r 口径(出厂默认 = 台架实测反算的端子↔引脚变比)见该文档 §7.2。
|
||
* 三维各管一件事:k = 前端斜率(板级事实,ROM 固化)、b = 零点、
|
||
* r = 端子↔引脚变比(现场标定产物)。**不合并**——端子域 k 的 Q16 溢出
|
||
* int32(UA 152587.9 µV/码 → 1.0e10 > 2.147e9),且合并会把硬件分压比
|
||
* 伪装成前端斜率(§7.2 明令禁止「用系数凑硬件」)。
|
||
* 并发契约:cfg RAM 副本属主 = 配置引擎(defaultTask 的 load/poll 原地
|
||
* 改写),本层逐点折算现读——int32 对齐字在 Cortex-M4 上读写原子,
|
||
* poll 重载「复位默认 + 逐键解析」存在 µs 级窗口,撞上则该拍该通道
|
||
* 为默认值/半新值(相量 20 点窗摊薄,且触发面 = 用户改文件后 4 s
|
||
* 沉降一次性事件)——docs/算法/采集与算法层设计说明.md §13.8 明载此设计(不挂回调)。
|
||
* 合法带哨兵(防损坏文件/错误标定把假电压送进下游):k ∉ ROM×[0.5,2.0]
|
||
* 或 |b| > ADC_CAL_B_MAX → 该通道折算回退 ROM + b=0;r 不在
|
||
* [65536, ADC_CAL_R_MAX] → 该路按 1.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)
|
||
* + 合法带哨兵回退 + 标定公式(v2.1 起改在 PC 侧做 N 点
|
||
* 最小二乘,固件不再持有拟合算术)
|
||
* 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)
|
||
* v2.0 2026-11-11 DTU 项目组 新增变比维 r([adc_cal_sec] 一段 8 键):
|
||
* 折算 (code+b)×k×1e-6×r;+adc_cal_r_uv/r_q16/
|
||
* ratio_configured/set_ratio;two_point 改标 r;
|
||
* VALID 追加「r 已配置」条件
|
||
* v2.1 2026-11-13 DTU 项目组 两点 → N 点最小二乘:删两点公式(全仓
|
||
* 生产代码零调用,只有单测调用,故对运行中的固件无影响),
|
||
* 新增固件侧拟合函数 + 配套拒绝码:
|
||
* N 组 (读数, 注入) 做普通最小二乘 y=a·x+c,再一次项
|
||
* r_new=a·r_old、常数项 b_new=b_old+c/(k·1e-3·r_new)
|
||
* 折回 r/b。拒绝判据 n<3 / den<=0 / 单调性反转 / a<=0 /
|
||
* r_new 越带 / |b_new|>B_MAX / 最大残差 > 满量程 2%。
|
||
* 全程 int64 定点,与 test/host/cal_fit.py 逐位对拍
|
||
* (金标向量钉在 test/host/adc_cal/adc_cal_test.c)
|
||
* v2.2 2026-11-13 DTU 项目组 拟合移出固件:删 v2.1 新增的固件侧拟合函数及其算术核
|
||
* (圆整除法 + 累加器)、5 个域上界常量与 8 个拒绝码宏。
|
||
* 理由:标定是台架一次性离线动作(注入已知量 → 读屏上
|
||
* meas: → PC 解 r/b → 经 104 写入),设备只需接受校正后的
|
||
* 参数;固件版生产零调用者(被 linker 移除),留着就是
|
||
* 第二个真相源。设备侧安全闸(set/set_ratio 的 k/r/b
|
||
* 合法带拦截)**原样保留**——那是防工具写坏参数的闸。
|
||
* 拟合唯一实现移到 test/host/cal_fit.py,原金标向量随之
|
||
* 搬进其 --selftest。
|
||
*/
|
||
|
||
/* 头文件包含区 */
|
||
#include <stddef.h>
|
||
#include <string.h>
|
||
|
||
#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…r_in,均 4/5 字符)
|
||
* ——docs/算法/采集与算法层设计说明.md §13.8 布局;键的书写顺序按该表,存储下标按语义枚举 */
|
||
#define ADC_CAL_SEC_K "adc_cal"
|
||
#define ADC_CAL_SEC_B "adc_cal_ofs"
|
||
#define ADC_CAL_SEC_R "adc_cal_sec"
|
||
|
||
/* Q16 → V 的合并比例因子:1e-6 / 65536(float 常量,精确到 1 ulp 以内即可:
|
||
* 系数本身 Q16 舍入误差 < 0.001 %,见docs/算法/采集与算法层设计说明.md §13.2) */
|
||
#define ADC_CAL_K_SCALE (1.52587890625e-11f)
|
||
|
||
/* Q16 比值 → 无量纲倍率的比例因子:1 / 65536 = 2^-16(二进制精确,无舍入) */
|
||
#define ADC_CAL_R_SCALE (1.52587890625e-05f)
|
||
|
||
/* 静态变量定义区 */
|
||
|
||
/* [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;
|
||
|
||
/* [adc_cal_sec] 段 RAM 副本:r 按语义下标 0..7(int32 Q16 比值;0 = 本路不折算) */
|
||
typedef struct {
|
||
int32_t r[8];
|
||
} adc_cal_rcfg_t;
|
||
|
||
static adc_cal_kcfg_t s_kcfg; /* 生效系数副本(引擎原地更新) */
|
||
static adc_cal_bcfg_t s_bcfg; /* 生效零点副本 */
|
||
static adc_cal_rcfg_t s_rcfg; /* 生效变比副本 */
|
||
|
||
/* 工厂默认(ROM 理论系数,量程口径冻结定死,docs/算法/采集与算法层设计说明.md §13.8):**下标 = 语义枚举**
|
||
* (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 } };
|
||
|
||
/* 变比工厂默认:**下标 = 语义枚举**,按族给(UA/UB/UC/UN 电压族、IA/IB/IC/IN 电流族)。
|
||
* 两族单位不同(V/V 与 A/V)——这是它们必须分列而非共用一个数的原因。
|
||
* ⚠ 电流族四路里有三路(IB/IC/IN)在 V0.1 是 STM32 单极性削顶通道,配了变比也测不了
|
||
* 交流量;但**变比照配**——它描述的是端子↔引脚的分压/负担关系,与"这路能不能测"
|
||
* 是两件事,等 V0.2 把 8 路全接 TPAFE 后这些值直接继续用。 */
|
||
static const adc_cal_rcfg_t s_rcfg_rom = {
|
||
/* idx0 UA / idx1 UB / idx2 UC / idx3 UN —— 电压族 V/V */
|
||
{ ADC_CAL_R_Q16_VOLT, ADC_CAL_R_Q16_VOLT, ADC_CAL_R_Q16_VOLT, ADC_CAL_R_Q16_VOLT,
|
||
/* idx4 IA / idx5 IB / idx6 IC / idx7 IN —— 电流族 A/V */
|
||
ADC_CAL_R_Q16_CURR, ADC_CAL_R_Q16_CURR, ADC_CAL_R_Q16_CURR, ADC_CAL_R_Q16_CURR }
|
||
};
|
||
|
||
/* 合法带哨兵留痕位(bit=语义下标;每通道每次开机只打一条 [ERR],
|
||
* adc_cal_init 清零 = 新一轮观测窗) */
|
||
static uint8_t s_err_latched;
|
||
|
||
/* 变比越带留痕位:**与 s_err_latched 分开**——k/b 越带和「r 配了个不可能的值」
|
||
* 是两件独立的事,共用一位会让先到的那个把后到的永久遮住(同一个通道上
|
||
* 一次开机只留一条痕是刻意的,但两类痕必须各留各的) */
|
||
static uint8_t s_rerr_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 },
|
||
};
|
||
/* 变比键表:键的书写顺序照 §13.8 表(UA/UN/IA 优先——这三路是 V0.1 唯一有效的测量通道),
|
||
* 存储下标按语义枚举(同 k 表)。8 键 < CFG_FILE_MAX_KEYS=16,不拆段。 */
|
||
static const cfg_key_def_t s_r_keys_tbl[8] = {
|
||
{ "r_ua", CFG_VAL_INT, offsetof(adc_cal_rcfg_t, r[0]), 4u },
|
||
{ "r_un", CFG_VAL_INT, offsetof(adc_cal_rcfg_t, r[3]), 4u },
|
||
{ "r_ia", CFG_VAL_INT, offsetof(adc_cal_rcfg_t, r[4]), 4u },
|
||
{ "r_ub", CFG_VAL_INT, offsetof(adc_cal_rcfg_t, r[1]), 4u },
|
||
{ "r_uc", CFG_VAL_INT, offsetof(adc_cal_rcfg_t, r[2]), 4u },
|
||
{ "r_ib", CFG_VAL_INT, offsetof(adc_cal_rcfg_t, r[5]), 4u },
|
||
{ "r_ic", CFG_VAL_INT, offsetof(adc_cal_rcfg_t, r[6]), 4u },
|
||
{ "r_in", CFG_VAL_INT, offsetof(adc_cal_rcfg_t, r[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);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:变比合法带检查(0 = 本路不折算;否则必须 ≥ 1.0)
|
||
* 入口参数:r - 待检变比 Q16 int32_t
|
||
* 返 回 值:1 - 可折算;0 - 不折算(0 / 负数 / 比值 < 1.0)
|
||
* 限定条件:无
|
||
* 函数说明:**上界只在 int64 域查**(ADC_CAL_R_MAX = int32 上限本身,
|
||
* int32 里不可能越上界,写成 r > ADC_CAL_R_MAX 是恒假分支、cppcheck 会报
|
||
* knownConditionTrueFalse)。int32 域只守下界 1.0 的理由见 adc_cal.h
|
||
*/
|
||
static int s_rband_ok(int32_t r) {
|
||
return (r >= ADC_CAL_R_MIN) ? 1 : 0;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读通道「当前生效且合法」的 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;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:变比越带留痕(每通道每次开机首见沿一条 [ERR],init 重置)
|
||
* 入口参数:sem - 语义下标 0..7;r - 现场值(Q16)
|
||
* 返 回 值:无
|
||
* 限定条件:仅 s_eff_r 内部
|
||
*/
|
||
static void s_note_bad_r(unsigned sem, int32_t r) {
|
||
const uint8_t bit = (uint8_t)(1u << sem);
|
||
|
||
if ((s_rerr_latched & bit) == 0u) {
|
||
s_rerr_latched |= bit;
|
||
DBG_ERROR("ch=%u r_q16=%ld below 1.0 ratio -> no scaling (pin volts shown)",
|
||
(unsigned)sem, (long)r);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读通道「当前生效」的变比(0 = 本路不折算)
|
||
* 入口参数:sem - 语义下标 0..7
|
||
* 返 回 值:Q16 比值;0 = 不折算(配置为 0,或落在合法带外被就地忽略)
|
||
* 限定条件:sem < 8
|
||
* 函数说明:**不做 ROM 回退**——这与 s_eff_kb 的关键差别:k 越带必须回退(否则
|
||
* 引脚电压就错了,下游全跟着错),而 r 越带的正确行为是「不折算」,
|
||
* 因为折算成什么量纲本来就没有可信答案;r=0 与 r 越带在输出上同义
|
||
* (都停在引脚电压),区别只在于越带会留一条 [ERR]
|
||
*/
|
||
static int32_t s_eff_r(unsigned sem) {
|
||
const int32_t r = s_rcfg.r[sem];
|
||
|
||
if (r == 0L) {
|
||
return 0L;
|
||
}
|
||
if (s_rband_ok(r) != 0) {
|
||
return r;
|
||
}
|
||
s_note_bad_r(sem, r);
|
||
return 0L;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:标定层初始化——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));
|
||
(void)memcpy(&s_rcfg, &s_rcfg_rom, sizeof(s_rcfg));
|
||
s_err_latched = 0u;
|
||
s_rerr_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 副本即生效值(docs/算法/采集与算法层设计说明.md §13.8) */
|
||
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);
|
||
}
|
||
|
||
(void)memset(®, 0, sizeof(reg));
|
||
reg.name = ADC_CAL_SEC_R;
|
||
reg.p_data = &s_rcfg;
|
||
reg.p_defaults = &s_rcfg_rom;
|
||
reg.data_size = sizeof(s_rcfg); /* 32 */
|
||
reg.p_keys = s_r_keys_tbl;
|
||
reg.key_count = 8u;
|
||
reg.p_cb = NULL;
|
||
if (cfg_file_register(®) != 0) {
|
||
DBG_ERROR("register '%s' FAIL (dup/full?) - ratio file values inactive, ROM ratio used",
|
||
ADC_CAL_SEC_R);
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:码值 → 工程量折算(docs/算法/采集与算法层设计说明.md §13.2 模型)
|
||
* 入口参数:sem - 语义通道;code - 原始码值 adc_semantic_t / int32_t
|
||
* 返 回 值:工程量浮点(r=0 = ADC 引脚电压 V;TEMP/越界 = 原样透传码值) float
|
||
* 限定条件:无(任意上下文,纯读)
|
||
* 函数说明:见 adc_cal.h 同名声明;k/b 经 s_eff_kb 合法带哨兵(越带回退 ROM),
|
||
* r 经 s_eff_r(越带/0 → 不折算,即乘 1.0)。两处哨兵都不落共享状态。
|
||
* ⚠ 乘序刻意是 `(码值 × k)` 先算成引脚电压、再乘 r:k 的 Q16 上界
|
||
* (5.28e7)乘 code(±32768)后落在 float 的 2^24 精度区内,而若先把
|
||
* k×r 并成一个大系数(60 万级 Q16 → 4e10)再乘 code,float 只剩 7 位
|
||
* 有效数字,末位抖动会打进 3 位小数的屏面读数
|
||
*/
|
||
float adc_cal_apply(adc_semantic_t sem, int32_t code) {
|
||
int32_t k;
|
||
int32_t b;
|
||
int32_t r;
|
||
float v;
|
||
|
||
if ((unsigned)sem >= (unsigned)ADC_SEM_TEMP) {
|
||
return (float)code; /* TEMP/越界:不纳入线性标定(决策 5),原样透传 */
|
||
}
|
||
(void)s_eff_kb((unsigned)sem, &k, &b);
|
||
v = ((float)(code + b)) * ((float)k) * ADC_CAL_K_SCALE;
|
||
r = s_eff_r((unsigned)sem);
|
||
if (r == 0L) {
|
||
return v;
|
||
}
|
||
return v * ((float)r) * ADC_CAL_R_SCALE;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读通道可用性标志(消费者判「能不能信」的唯一入口)
|
||
* 入口参数:sem - 语义通道 adc_semantic_t
|
||
* 返 回 值:ADC_CAL_F_VALID / ADC_CAL_F_CLIPPED 组合;TEMP/越界恒 0 uint32_t
|
||
* 限定条件:无
|
||
* 函数说明:VALID 需文件位图、k/b 合法带、变比已配置**三者**同时成立
|
||
* (哨兵压制说谎位图,见文件头)。三者里任一不成立就整条不报 VALID——
|
||
* 宁可让现场看到「未标定」,也不能让一个「引脚电压乘以一个猜的变比」
|
||
* 冒充二次值去解锁保护判据
|
||
*/
|
||
uint32_t adc_cal_flags(adc_semantic_t sem) {
|
||
uint32_t f = 0u;
|
||
|
||
if ((unsigned)sem >= (unsigned)ADC_SEM_TEMP) {
|
||
return 0u;
|
||
}
|
||
/* VALID = 文件位图置位 且 k/b 合法 且 r 已配置(三哨兵都不落共享状态,随重载自愈) */
|
||
if (((s_kcfg.valid & (uint8_t)(1u << (unsigned)sem)) != 0u) &&
|
||
(s_kband_ok((unsigned)sem, s_kcfg.k[sem], s_bcfg.b[sem]) != 0) &&
|
||
(s_rband_ok(s_rcfg.r[sem]) != 0)) {
|
||
f |= ADC_CAL_F_VALID;
|
||
}
|
||
/* CLIPPED 由**来源**派生(R5:STM32 侧单极性无偏置 → 负半周削顶)——
|
||
* V0.2 改版(8 路全接 TPAFE,消除双转换器无共同采样触发)后本位自动恒 0,无需改码,
|
||
* 对应docs/算法/采集与算法层设计说明.md §13.3 三态可用性表「页面与 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);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读该路变比(浮点,V/V 或 A/V)
|
||
* 入口参数:sem - 语义通道 adc_semantic_t
|
||
* 返 回 值:r 比值;0 = 本路不折算;TEMP/越界返 0 float
|
||
* 限定条件:无(任意上下文,纯读)
|
||
* 函数说明:与折算同视图(越带即按 0 报)。Q16 还原用 2^-16 精确乘,
|
||
* 不写除以 65536.0f——除法在 ARMCC 软浮点路径上多一次舍入且更慢,
|
||
* 这里的值域完全用不到"精确除法"那点好处
|
||
*/
|
||
float adc_cal_ratio(adc_semantic_t sem) {
|
||
if ((unsigned)sem >= (unsigned)ADC_SEM_TEMP) {
|
||
return 0.0f;
|
||
}
|
||
return ((float)s_eff_r((unsigned)sem)) * ADC_CAL_R_SCALE;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读该路变比的 int32 Q16 原值(adc_cal_ratio 孪生,见 .h 声明)
|
||
* 入口参数:sem - 语义通道 adc_semantic_t
|
||
* 返 回 值:r[Q16];TEMP/越界/带外/0 均返 0 int32_t
|
||
* 限定条件:无(任意上下文,纯读)
|
||
* 函数说明:与 adc_cal_ratio() 共用 s_eff_r 生效视图,差别只在不做浮点还原——
|
||
* 录波段头按 LSB 精确记账必须走本口(docs/子系统设计/故障录波设计说明.md §6.3)
|
||
*/
|
||
int32_t adc_cal_ratio_q16(adc_semantic_t sem) {
|
||
if ((unsigned)sem >= (unsigned)ADC_SEM_TEMP) {
|
||
return 0;
|
||
}
|
||
return s_eff_r((unsigned)sem);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:该路变比是否已配置(**整帧量纲标签的唯一判据源**)
|
||
* 入口参数:sem - 语义通道 adc_semantic_t
|
||
* 返 回 值:1 - 已配置(该路输出是二次值);0 - 未配置(输出是引脚电压) uint8_t
|
||
* 限定条件:无(任意上下文,纯读)
|
||
* 函数说明:判据与折算**同源**(都走 s_eff_r),因此不可能出现"标签说二次值、
|
||
* 实际乘的是 1.0"这种分叉——这正是把它做成独立谓词而不是让调用方
|
||
* 自己读 adc_cal_ratio() 比较的原因:调用方那一侧的比较迟早会漂
|
||
*/
|
||
uint8_t adc_cal_ratio_configured(adc_semantic_t sem) {
|
||
if ((unsigned)sem >= (unsigned)ADC_SEM_TEMP) {
|
||
return 0u;
|
||
}
|
||
return (s_eff_r((unsigned)sem) != 0L) ? 1u : 0u;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:整定入口——写通道系数与零点,并置位 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;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:整定入口——写该路变比(标定链的第二个写入点,见 .h 同名声明)
|
||
* 入口参数:sem - 语义通道;r_q16 - 变比 × 65536(0 = 本路不折算)
|
||
* 返 回 值:0 - 成功;-1 - 通道非法 / 变比在合法带外(RAM 不动,[ERR] 留痕) int
|
||
* 限定条件:与 adc_cal_set() 同一条并发契约(引擎对齐 int32 单字;勿在 adcTask 调用)
|
||
* 函数说明:只写 RAM 副本,不置脏、不落盘(理由见 .h)。r 的合法带**不设上界比较**
|
||
* (见 s_rband_ok 注释),故此处只拦「非 0 且 < 1.0」这一种越带形态;
|
||
* r = 0 是合法输入(显式关掉本路折算),不走留痕
|
||
*/
|
||
int adc_cal_set_ratio(adc_semantic_t sem, int32_t r_q16) {
|
||
if ((unsigned)sem >= (unsigned)ADC_SEM_TEMP) {
|
||
s_st_set_reject++; /* 观测:连通道号都非法,RAM 未动 */
|
||
return -1;
|
||
}
|
||
if ((r_q16 != 0L) && (s_rband_ok(r_q16) == 0)) {
|
||
DBG_ERROR("set ch=%u rejected: r_q16=%ld below 1.0 ratio (0 or >=%ld)",
|
||
(unsigned)sem, (long)r_q16, (long)ADC_CAL_R_MIN);
|
||
s_st_set_reject++;
|
||
return -1;
|
||
}
|
||
s_rcfg.r[sem] = r_q16;
|
||
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; /* 水位只会追平,不会越过 */
|
||
}
|
||
|
||
/*
|
||
* 函数功能:通道码值域贴轨带门限(采样通道自检 判定的门限来源,按 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;
|
||
}
|