Files
STM32F4-Base/App/algo/adc_cal.h
T
wandering1 7c505b2457 refactor(adc): 最小二乘拟合移出固件,只留 PC 侧唯一实现
用户裁定(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。
2026-11-13 08:09:09 +08:00

401 lines
28 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.
/*
* 模块名称:通道标定与量纲折算(标定折算,「数据可信」标定折算与落盘整定)
* 模块功能:把 adc_input 的原始码值折算成工程量——模型与系数口径的唯一落点:
* 工程量 = (code + b) × k_f × r_f
* k_f = k[µV/码] × 1e-6(前端斜率,ROM 固化的板级事实)
* r_f = r[Q16] / 65536(端子→引脚变比,现场标定产物;r = 0 = 本路不折算)
* 三维系数各管一件事,**不许合并**:
* k 管「引脚电压 ↔ 码值」——板级量程事实(TPAFE 152.59 µV/码、STM32 805.66 µV/码),
* 由 ROM 固化,合法带 = ROM × [0.5, 2.0](只容纳晶体/基准的板间差);
* b 管零点偏移(码值,±ADC_CAL_B_MAX);
* r 管「端子二次值 ↔ 引脚电压」——由端子/子板分压与 CT 负担电阻决定,
* 出厂默认按台架实测反算(电压 1000 V/V、电流 29.4118 A/V),
* 台架标定标的就是它(多点最小二乘在 PC 侧算,见 v2.2)。
* 为什么必须分开:把变比折进 k 会溢出存储口径——UA 端子域 k = 152 587.9 µV/码,
* Q16 = 1.0e10 超出 int32 上限 2.147e9;而 r 单独存 Q16 完全够(1000 → 65 536 000)。
* 合并还会把「硬件分压比」伪装成「前端斜率」,正是
* docs/硬件平台/ADC通道映射说明.md §7.2 反对的「用系数凑硬件」。
* k / b / r 三份 RAM 副本注册进 cfg_file 引擎([adc_cal] / [adc_cal_ofs] /
* [adc_cal_sec] 三段,布局见 docs/算法/采集与算法层设计说明.md §13.8);
* **不挂回调**——RAM 副本即生效值,逐点折算现取现用(1 kHz × 9 通道 = 9k 次/秒
* 浮点乘,可忽略)。
* 三态可用性:ADC_CAL_F_VALID(该路已标定确认)与
* ADC_CAL_F_CLIPPED(V0.1 硬件削顶)由 adc_cal_flags() 单点查询,
* 104 / UI / 保护闭锁(未标定即闭锁)共用(docs/算法/采集与算法层设计说明.md §13.3)。
* **未标定闭锁的判定本身也归本层**(`adc_cal_prot_blocked()`):它讲的是
* 「这几路标定齐了没」,属标定层事实,不是任务编排逻辑——保护任务与 UI
* 组装器都只是它的消费者(道F task-16 把此前的两份实现收成一份)。
* ⚠ 两个**互不派生**的事实,别互相反推:
* · 数值量纲由 r 决定——r ≠ 0 的通道,adc_cal_apply() 输出的就是二次值(V 或 A);
* · VALID 由标定位图置位——它回答"这个数准不准",回答不了"这个数是什么单位"。
* 屏面第 7 行的量纲标签(eng)只看前者,保护闭锁只看后者。
* 一次值(eng=2)仍留预留:其变比的家是 prot_set 已在册的
* PS_PT_PRI/PS_PT_SEC 与 PS_CT_PRI/PS_CT_SEC 四项(**不再新开 cfg 段**,
* 否则同一事实两个真源),当前无消费者,故本层不产出一次值。
* TEMP 不参与线性标定(决策 5),adc_cal_apply() 原样透传码值。
* 本层**纯算法 + 纯数据**:不碰寄存器、不等事件;adc_cal_init() 内核前
* 可调(注册进引擎必须先于 cfg_file_load,FTP 鉴权确立的契约);apply/flags
* 任意上下文可读(cfg RAM 副本 4 字节对齐字读写在 Cortex-M4 上原子,
* 与 defaultTask 的 load/poll 复位存在 µs 级混窗——设计接受,见 .c 头)。
* 适用平台:STM32F407ZGTx(Cortex-M4F);主机侧纯逻辑单测 test/host/adc_cal/
* 作者:DTU 项目组
* 创建日期:2026-10-25(道A 标定折算与落盘整定)
* 修改记录:
* v1.0 2026-10-25 DTU 项目组 创建:Q16 系数/零点 + 折算/flags/集
* v1.1 2026-10-25 DTU 项目组 运行参数段:+adc_cal_b_codes()(run_par 回显/RMW 用)
* v1.2 2026-10-26 DTU 项目组 道F task-16:+adc_cal_prot_blocked()——「UA/UN/IA
* 三路 VALID 是否齐」这份判据停走条件从
* protect_task.c 与 ui_data_feed.c 的两份重复实现收成本层
* 单点(DTU_BENCH 旁路同时收成一处)
* v1.3 2026-10-29 DTU 项目组 故障录波 task-2:新增 adc_cal_k_q16()——k 的 int32
* Q16 原值直读口(adc_cal_k_uv 的孪生)。录波段头 k 字段
* 是 LSB 精确记账,float 在 5.28e7 码字上掉尾数,
* 禁止经浮点反推(docs/子系统设计/故障录波设计说明.md §6.3)
* v2.0 2026-11-11 DTU 项目组 新增变比维:折算改 (code+b)×k×1e-6×r,
* r 存新段 [adc_cal_sec](int32 Q16,0 = 本路不折算)。
* 理由 = 端子域 k 的 Q16 溢出 int32(UA 152587.9 µV/码
* → 1.0e10),变比并进 k 这条路不通;且变比是硬件
* 事实、k 是前端斜率,混一起就是「用系数凑硬件」。
* 同步:两点公式改标 r(k 由 ROM 固化;v2.1 起标定
* 算法换成 N 点最小二乘,落在 PC 侧,见 v2.2)、
* +adc_cal_ratio_q16()/+adc_cal_ratio()/+adc_cal_ratio_configured()
* +adc_cal_set_ratio()
* v2.1 2026-11-13 DTU 项目组 两点 → N 点最小二乘:删两点公式(生产代码
* 零调用,只有单测调用),新增固件侧拟合函数——把 N 组
* (读数, 注入) 做普通最小二乘 y=a·x+c,再由一次项/常数项
* 折回 r/b(读数在哪个 r 下采的必须入参说明)。
* 拒绝判据:n<3 / den<=0 / 单调性反转 / a<=0 /
* r_new 越带 / |b_new|>B_MAX / 最大残差 > 满量程跨度 2%。
* 全程 int64 定点(不用 double 累加):结果确定、可跨平台
* 复现,并与 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 写入),设备只需接受
* 校正后的参数,不需要自己拟合曲线;固件版生产零调用者、
* 仅为给 Python 提供镜像对象而存在,留着就是第二个真相源。
* **保留**设备侧安全闸:adc_cal_set()/adc_cal_set_ratio()
* 的 k/r/b 合法带拦截(防工具写坏参数)。拟合唯一实现
* 移到 test/host/cal_fit.py,原金标向量随之搬进其
* --selftest(否则"解出的 r 对不对"再无机器验证)。
*/
#ifndef ADC_CAL_H
#define ADC_CAL_H
#include <stdint.h>
#include "adc_input.h" /* adc_semantic_t(语义通道编号 = 系数表下标口径) */
#ifdef __cplusplus
extern "C" {
#endif
/* 通道可用性标志(adc_cal_flags() 返回值位) */
#define ADC_CAL_F_VALID (0x01u) /* 该路已标定确认(k/b/r 合法带内 → 读数可信) */
#define ADC_CAL_F_CLIPPED (0x02u) /* 硬件削顶(V0.1 STM32 侧单极性无偏置)→ 交流量不可信 */
/*
* ROM 默认系数(int32 Q16 = µV/码 × 65536,量程口径冻结定死的引脚侧口径):
* TPAFE ±5 V 档:10 V 跨度 / 65536 码 = 152.587890625 µV/码 → ×65536 = 10,000,000(整)
* STM32 0~3.3 V:3.3 V / 4096 码 = 805.6640625 µV/码 → ×65536 = 52,800,000(整)
* 两个默认值在 Q16 下**无舍入**(系数定点舍入项从误差预算里抹掉,
* 选型依据 → docs/算法/采集与算法层设计说明.md §13.2)
*/
#define ADC_CAL_K_Q16_TPAFE (10000000L)
#define ADC_CAL_K_Q16_STM32 (52800000L)
/*
* ROM 默认变比(int32 Q16 = 比值 × 65536)——**出厂就编进镜像,不是缺省 0**:
* 电压族 1000.0 V/V → 65 536 000(Q16 无舍入)
* 电流族 29.4118 A/V → 1 927 532(= 29.4118 × 65536,回落 29.41180,相对误差 < 2e-7)
* 出处 = 2026-11-11 台架实测反算(继保仪注 UA 端子 5.000 V → 屏上 0.005 V,
* 5.000/0.005 = 1000.0;注 IA 端子 0.500 A → 屏上 0.017 V,0.5/0.017 = 29.4118),
* 电流族与 docs/硬件平台/ADC通道映射说明.md §7.2 的登记值精确吻合。
* ⚠ 电压族的 1000.0 与 §7.2 早先按子板参数算定的 606.06 **不一致**——606.06 的依据是
* 200 kΩ/330 Ω 假定值,而台架读数差 60 %(0.005 vs 0.008),远超屏面分辨率,
* 故以实测为准;§7.2 已按本次结论改写(子板原理图仍不在仓库,见该节「复核边界」)。
* ⚠ 这两个数是**单点粗测**(5 V 注入只有 33 码,1 LSB = 3 %),
* 变比真值区间约 909~1111(电压族);送检前必须用标准源 N 点最小二乘重写 r
* (PC 侧工具 test/host/cal_fit.py,见 v2.2)。
* r = 0 是合法值,语义 = 「本路不折算,输出停在 ADC 引脚电压」——TEMP 恒此态,
* 也是现场关掉某路折算的显式手段(不是 1.0)。
*/
#define ADC_CAL_R_Q16_VOLT (65536000L) /* 1000.0 V/V */
#define ADC_CAL_R_Q16_CURR (1927532L) /* 29.4118 A/V */
/* 变比合法带(Q16):**0 = 本路不折算**,或落在 [ADC_CAL_R_MIN, ADC_CAL_R_MAX](比值 1.0 ~ 32767.99)。
* 为什么用绝对带而不是像 k 那样用 ROM×[0.5,2.0]:k 的窄带是为了容纳晶体/基准的板间差,
* 而 r 是现场标定量(不同 CT/PT 变比差几个数量级),窄带没有意义。下界取 1.0 是因为
* 分压与负担电阻都只会把端子值变小——比「比值 < 1」更可能的是把 mV 当 V 填错。
* 上界 = int32 上限(Q16 下最大比值 32767.99),1000 与 29.41 都远在带内。
* ⚠ 工程实现上**只在下界用 ADC_CAL_R_MIN 比较**:r 是 int32,`r > ADC_CAL_R_MAX`
* 是恒假分支(cppcheck 报 knownConditionTrueFalse)。ADC_CAL_R_MAX 留给
* int64 中间量(最小二乘算出的 r_new / 除法前的分子)做上界闸。 */
#define ADC_CAL_R_MIN (65536L) /* 1.0 */
#define ADC_CAL_R_MAX (2147483647L)
/* 零点偏移幅值上限(码值):超此即判文件损坏/异常标定,回退 ROM 并留痕 */
#define ADC_CAL_B_MAX (8192L)
/* 标定的端子输入域上界(µV,含电流按 µA 同域):±1000 V / 1000 A。
* 它只拦「把 mV 或码值当 µV 填」这一类量级错——本平台 V0.1 端子量程远低于此,
* 定值本身不是量程声明,别拿它当"支持 1000 V"的依据 */
#define ADC_CAL_V_MAX (1000000000L)
/*
* 函数功能:标定层初始化——RAM 副本置 ROM 默认 + 注册三段进 cfg 引擎(幂等)
* 入口参数:无
* 返 回 值:无
* 限定条件:内核启动前调用(app_main_init);注册必须先于 cfg_file_load
* (「所有受管段先注册、后 load」契约,见 Drivers/BSP/NETCFG/net_cfg.c 的
* net_cfg_load 注释)
* 函数说明:段属主为 defaultTask(配置引擎),本模块只提供 RAM 副本与默认值;
* 文件值由引擎 load/poll 原地写入 RAM 副本,本层逐点折算现读。
* ⚠ 变比段 [adc_cal_sec] 的 ROM 默认**不是 0**(电压 1000 / 电流 29.4118),
* 因此烧进新固件后屏上立刻显二次值——这是刻意的:变比是硬件事实,
* 出厂就该带着;而 VALID(准不准)仍然要求标定,两者互不代替。
*/
void adc_cal_init(void);
/*
* 函数功能:码值 → 工程量折算(docs/算法/采集与算法层设计说明.md §13.2 模型)
* 入口参数:sem - 语义通道(TEMP 透传不折算) adc_semantic_t
* code - 原始码值(TPAFE 16 位补码 / STM32 0..4095) int32_t
* 返 回 值:工程量浮点(r≠0 → 二次值 V/A;r=0 → ADC 引脚电压 V) float
* 限定条件:无(任意上下文,纯读)
* 函数说明:eng = (code + b) × k[µV/码] × 1e-6 × r[Q16]/65536,由三份 Q16/int32
* 现值派生,无缓存副本(避免无回调下的陈旧系数);k/b 越合法带 → 该通道
* 回退 ROM 默认 + 首见沿 [ERR] 留痕(adc_cal_init 重置留痕位),
* flags 同时不报 VALID。r 越带或为 0 → 该路按 1.0 处理(不折算),
* 同样不报 VALID。
*/
float adc_cal_apply(adc_semantic_t sem, int32_t code);
/*
* 函数功能:读通道可用性标志(消费者判「能不能信」的唯一入口)
* 入口参数:sem - 语义通道 adc_semantic_t
* 返 回 值:ADC_CAL_F_VALID / ADC_CAL_F_CLIPPED 组合;TEMP 恒 0;sem 越界恒 0 uint32_t
* 限定条件:无
* 函数说明:VALID = 文件位图置位 **且** 该通道 k/b 在合法带内 **且** 变比已配置
* (r ≠ 0 且在带内)——三个条件一起才叫"这路标定过"。哨兵不落状态,
* 天然跟随 load/poll 重载。CLIPPED = V0.1 静态表(UB/UC/IB/IC/IN)。
* ⚠ VALID 回答「准不准」,**不**回答「什么单位」:单位由 r 决定
* (r≠0 → 二次值;r=0 → 引脚电压),而 r=0 时 VALID 必为 0,
* 所以「VALID 未置位却有二次值读数」这种组合不可能出现。
*/
uint32_t adc_cal_flags(adc_semantic_t sem);
/*
* 函数功能:**未标定闭锁判定**——保护判据所需的三路标定是否齐(「数据可信」未标定即闭锁
* / §4.3;道F task-16 把它从两处重复实现收敛成这一份真源)
* 入口参数:无
* 返 回 值:1 - 闭锁(UA/UN/IA 中至少一路 `ADC_CAL_F_VALID` 未置位,判据链应停走);
* 0 - 三路全 VALID(标定侧允许判据跑) uint8_t
* 限定条件:任意上下文(纯读 `adc_cal_flags()`,与它同一条并发契约:int32 对齐
* 字读写原子、cfg poll 重载 µs 级混窗设计接受);**现查,本层不留任何状态**
* 函数说明:① **为什么恰好这三路**:V0.1 把 8 路电气量分给两片转换器(AGENTS
* 「双转换器无共同采样触发」),UA/UN/IA = TPAFE 侧这三路是**唯一有效**
* 的测量通道;UB/UC/IB/IC/IN 即使将来标定也另受 `CLIPPED` 品质约束
* (M5 快照层处理),不掺进本闭锁源。
* ② ⚠ **不得当「保护在跑」的总括用**:判据停走共四路来源,本函数只覆盖
* 第二路——(a) 自检失败闭锁 采样故障闭锁(`adc_health_flags()`);(b) 本函数
*(保护功能清单见 docs/算法/继电保护功能复现规划.md)
* (未标定);(c) 定值校验和失配闭锁(`prot_set_locked()`);(d) 全局软压板
* 退出(`PS_GLOBAL_EN` = 0)。要判「这拍到底评估了没有」必须四路一起看
* (编排顺序见 `App/task/protect_task.c`)。
* ③ ⚠ **两个同族谓词与它不是一件事,不要顺手合并**(两种口径各有用途):
* · 整帧量纲标签 eng(`App/app/ui_data_feed.c` 的 `feed_eng()`)看的是
* **每路 r 是否配置**(配了就出二次值),判据 = 「每路要么已配变比、
* 要么削顶不显示」——削顶通道页面本来就显 "--",不参与整帧量纲断言;
* 并进来会让"三路标定齐但八路变比未全配"的整帧被误标引脚电压,
* 反过来把本三路口径当量纲用,V0.1 下五路削顶会让徽标永不解除;
* · 逐通道打印 VALID/CLIPPED 位图是诊断输出(`App/task/adc_task.c`),
* 不是判定。
* ④ 台架调试旁路 = 编译期 `-DDTU_BENCH`(`docs/算法/采集与算法层设计说明.md`
* 仅本地台架自行定义;本仓任何工程档都不定义 = 产品恒强制)。旁路**只在
* 本函数实现内部这一处**,调用方不再各自写 `#ifndef`:否则有人只给单个
* TU 加宏,屏上「未标定」徽标与判据停走当场分叉,且 0 错误 0 警告。
*/
uint8_t adc_cal_prot_blocked(void);
/*
* 函数功能:读通道当前生效系数(µV/码,浮点)
* 入口参数:sem - 语义通道 adc_semantic_t
* 返 回 值:k[µV/码](回退后口径);TEMP / 越界返 0 float
* 限定条件:无
* 函数说明:采样通道自检 门限换算、日志与整定界面回显用;勿据其反推二次值(看 flags)
*/
float adc_cal_k_uv(adc_semantic_t sem);
/*
* 函数功能:读通道当前生效零点偏移(码值,浮点)——adc_cal_k_uv 的孪生入口
* 入口参数:sem - 语义通道 adc_semantic_t
* 返 回 值:b[码值](越带时为 0,与折算同视图);TEMP/越界返 0 float
* 限定条件:无
* 函数说明:104 运行参数段(「数据可信」这一项)b 覆盖回显与 k 单侧改写的 RMW
* 读侧用(App/algo/run_par.c);勿据其反推二次值(看 flags)。
*/
float adc_cal_b_codes(adc_semantic_t sem);
/*
* 函数功能:读通道当前生效系数的 **int32 Q16 原值**(µV/码 × 65536)——
* adc_cal_k_uv() 的孪生入口,给「必须 LSB 精确」的消费者用
* 入口参数:sem - 语义通道 adc_semantic_t
* 返 回 值:k[Q16 原值](与折算同一生效视图:越带回退 ROM、b 不参与);TEMP/越界返 0 int32_t
* 限定条件:无(任意上下文,纯读)
* 函数说明:故障录波段头的 k 字段是 int32 LSB 精确记账(docs/子系统设计/故障录波设计说明.md
* §6.3 裁定)——float 还原后在 5.28e7 码字上掉尾数,**禁止**拿
* adc_cal_k_uv() 反乘 65536 回算落盘,故开本原值口。
*/
int32_t adc_cal_k_q16(adc_semantic_t sem);
/*
* 函数功能:读该路变比(浮点,V/V 或 A/V)——屏面/104 回显与标定链的读数口
* 入口参数:sem - 语义通道 adc_semantic_t
* 返 回 值:r(比值);**r=0 表示本路不折算**(输出停在引脚电压);TEMP/越界返 0 float
* 限定条件:无(任意上下文,纯读)
* 函数说明:与折算同视图(0 或越带即按 0 报,不给"带外值"这种第三态)。
* 别拿它反推 VALID——VALID 是"标定过没有",r 是"折算比是多少",
* 两者由两个字段各记各的(见 adc_cal_flags 的注释)。
* ⚠ 本值是**整体折算比**(端子二次值 ÷ 引脚电压),不是纯硬件分压比:
* k 由 ROM 固化成板级名义斜率,其残差被 r 吸收(台架标定时一并吸收进 r)。
*/
float adc_cal_ratio(adc_semantic_t sem);
/*
* 函数功能:读该路变比的 **int32 Q16 原值**(比值 × 65536)——录波段头记账口
* 入口参数:sem - 语义通道 adc_semantic_t
* 返 回 值:r[Q16];TEMP/越界/带外/0 均返 0(0 = 本路不折算) int32_t
* 限定条件:无(任意上下文,纯读)
* 函数说明:理由与 adc_cal_k_q16() 同一条——录波头必须 LSB 精确,
* 65000×65536 这类值经 float 往返会掉尾数(docs/子系统设计/故障录波设计说明.md §6.3)。
*/
int32_t adc_cal_ratio_q16(adc_semantic_t sem);
/*
* 函数功能:该路变比是否"已配置"(r 非 0 且落在合法带内)——**整帧量纲标签的唯一判据源**
* 入口参数:sem - 语义通道 adc_semantic_t
* 返 回 值:1 - 已配置(该路输出是二次值);0 - 未配置(输出是引脚电压) uint8_t
* 限定条件:无(任意上下文,纯读)
* 函数说明:存在的唯一理由:`App/app/ui_data_feed.c` 的 `feed_eng()` 要给整帧贴
* 「二次值/未标定」标签,而它需要的是**量纲事实**(配了变比没有),
* 不是**准确度事实**(VALID)。这两件事在 V0.1 上必然分叉——五路削顶通道
* 永远拿不到 VALID,但它们配了变比后输出确实已经是二次值。
* TEMP 恒返 0(不参与线性标定)。
*/
uint8_t adc_cal_ratio_configured(adc_semantic_t sem);
/*
* 函数功能:整定入口——写通道系数与零点,并置位 VALID(「数据可信」标定落盘与整定入口)
* 入口参数:sem - 语义通道(电气 8 路) adc_semantic_t
* k_q16 - 系数,µV/码 × 65536 内 int32_t
* b_codes - 零点偏移,码值 内 int32_t
* 返 回 值:0 - 成功;-1 - 参数非法/越出合法带(RAM 不动,[ERR] 留痕) int
* 限定条件:**整定路径上下文**(defaultTask 标定流程/整定链;运行参数段落地起 iec104Task
* 经 App/algo/run_par.c 的 0x8040~/0x8048~ 直通写亦为授权写者——写的仍是
* 引擎对齐 int32 单字,µs 级混窗契约同docs/算法/采集与算法层设计说明.md §13.8)。勿在 adcTask 调用
* 函数说明:**只写 RAM 副本,不置脏、不落盘、不返回"稍后会存"的承诺**——落盘由属主经
* cfg_file_save 完成,而"要不要落盘"这件事只有一个真源:run_par 的脏标志。
* 因此授权写者是 run_par 的语义 setter(写值 + 置脏成对发生,lead 裁②);
* 绕过它直调本函数 = 值只活在 RAM 里,重启即丢且**没有错误返回**,
* 该形态现可被本层观测计数抓到(差分口径见下方 adc_cal_stats_t 那张表)
*/
int adc_cal_set(adc_semantic_t sem, int32_t k_q16, int32_t b_codes);
/*
* 函数功能:整定入口——写该路变比(「数据可信」标定链的第二个写入点)
* 入口参数:sem - 语义通道(电气 8 路) adc_semantic_t
* r_q16 - 变比,比值 × 65536(0 = 本路不折算) int32_t
* 返 回 值:0 - 成功;-1 - 通道非法 / 变比在合法带外(RAM 不动,[ERR] 留痕) int
* 限定条件:与 adc_cal_set() 同一条并发契约(引擎对齐 int32 单字、µs 级混窗;
* 勿在 adcTask 调用)
* 函数说明:**只写 RAM 副本,不置脏、不落盘**——与 adc_cal_set() 完全同一条理由,
* 落盘的唯一真源仍是 run_par 的脏标志(授权写者 = run_par 的语义 setter)。
* ⚠ 与 adc_cal_set() **没有先后依赖**:VALID 是每次调用现算的三个条件
* (位图 ∧ k/b ∧ r),不是"置过的位",所以先写 r 再写 k/b 和反过来
* 结果一样。这也意味着**标定一路要调两个函数**(本函数管量纲、
* adc_cal_set 管零点并置位),少调一个 VALID 就不会亮——这是刻意的:
* 只配变比 = 出厂默认态(量纲对、准不准不知道)。
*/
int adc_cal_set_ratio(adc_semantic_t sem, int32_t r_q16);
/*
* 函数功能:通道码值域「贴轨带」门限(采样通道自检 越界检出的门限来源,按 FS 百分比)
* 入口参数:sem - 语义通道 adc_semantic_t
* p_lo_th - 下轨带上界(code ≤ 此值判触底) int32_t *
* p_hi_th - 上轨带下界(code ≥ 此值判触顶) int32_t *
* 返 回 值:0 - 已给出门限;-1 - TEMP/越界(不做贴轨监视) int
* 限定条件:无
* 函数说明:TPAFE 双极性 ±98% FS(±32112);STM32 上轨 98% FS(4013)、
* 下轨 2% FS(81);判定状态机在 adc_health_rail_watch()(采样通道自检)
*/
int adc_cal_rail_ths(adc_semantic_t sem, int32_t *p_lo_th, int32_t *p_hi_th);
/*
* 观测计数:把「改了没落盘」从意图式陈述变成可诊断事实(道G task-43 第②项;
* 先例 App/fs/vol_lock.h 的同族设计——只计数、不取锁、不在调用点打日志、不打 [ERR])
*
* 三态语义(**定义只在本头文件这一处**,消费者只描述取数与断言,不重定义):
* set_ok RAM 副本被本层 API 成功改写的累计次数
* set_reject 被本层拒掉的整定请求累计次数(参数非法 / 越出合法带)
* persisted_set_ok **水位**而不是计数:最近一次随整档保存落盘成功时的
* set_ok 快照(由 adc_cal_note_persisted() 盖写,永不越过 set_ok)
*
* 未落盘差额 = set_ok − persisted_set_ok,即"自上次成功落盘之后被改写的次数"。
* 单看差额不够定性,必须与 run_par_dirty_peek() 成对读(两列一起才只有一种解释):
*
* 差额 run_par_dirty_peek() 说的是什么事
* ------ ---------------------- ----------------------------------------------
* 0 0 盘与 RAM 齐(或压根没改过)——健康
* >0 1 改了、还没到落盘时机——正常在途
* >0 0 **改了且永远不会落盘**:写者绕过 run_par 的语义
* setter 直调 adc_cal_set()(运行参数段起的事)
*
* 消费者告诫(与 vol_lock_get_stats() 同一套,别重犯):
* ① 本组接口不取锁、不取时刻,任意上下文可读;三个数各自是 uint32 单字读写,
* Cortex-M4 上原子,但三个数之间**没有共同时刻**——别把它们拼成"一个瞬间的快照"
* 下结论,定性只按上面那张两列表成对读。
* ② 需要"被观测到的时刻"请在消费者侧自盖并冠 obs_ 前缀命名(先例:喂狗点单写者 latch),
* 本层不存时刻——存了就得连带管溢出与单位。
* ③ 这些计数**不参与任何判定**:能不能信只看 adc_cal_flags() /
* adc_cal_prot_blocked(),要不要落盘只看 run_par 的脏标志。别拿差额去改控制流,
* 那等于把观测升级成第二真源(标定写入口已由 run_par 语义 setter 收口,不再加口)。
* ④ 写清盲区:本层刻意**不挂** cfg_file 回调,因此"盘 → RAM 的覆盖"(cfg_file_load
* 与外部变更重载)不产生任何计数;未落盘的改值被一次重载无声吞掉,正是这三个
* 数照不到的那一条。要照它得先给本层挂回调 = 行为改动,另行立项处置。
*/
typedef struct {
uint32_t set_ok; /* 成功改写 RAM 副本次数(见上方唯一落点) */
uint32_t set_reject; /* 被拒次数(见上方唯一落点) */
uint32_t persisted_set_ok; /* 落盘水位,不是计数(见上方唯一落点) */
} adc_cal_stats_t;
/*
* 函数功能:读标定层观测计数(只读,不改任何状态)
* 入口参数:p_out - 输出:三个观测计数 adc_cal_stats_t *
* 返 回 值:无 void
* 限定条件:任意上下文可读(含 ISR 与内核启动前);adc_cal_init() 之前可读,得全 0
* 函数说明:p_out 为 NULL 时静默返回——观测件不得反过来制造故障
*/
void adc_cal_get_stats(adc_cal_stats_t *p_out);
/*
* 函数功能:落盘成功回执——把水位 persisted_set_ok 盖写为当前的 set_ok(**不是自增**)
* 入口参数:无 void
* 返 回 值:无 void
* 限定条件:只允许配置引擎属主侧"整档保存已成功"那一条路径调用;当前唯一调用点 =
* App/algo/run_par.c 的 run_par_store_save() 中 cfg_file_save() 返 0 之后
* 函数说明:为什么盖写不自增——一次 save 覆盖整档,可能对应多次整定改写(逐通道
* 标定 3 路 = 3 次 adc_cal_set + 1 次 save);自增会留下 2 次"假未落盘"。
* 水位语义让差额只反映"上次落盘之后的改写",回归锁见
* test/host/adc_cal/adc_cal_test.c 的 TC-ADCCAL-021
*/
void adc_cal_note_persisted(void);
#ifdef __cplusplus
}
#endif
#endif /* ADC_CAL_H */