feat(adc): 新增变比维 r 三维折算,录波段格式升 v2
采样板注入 Ua=5V/Ia=0.5A 读数偏小,根因是固件只输出引脚电压、 缺「端子→引脚」变比。折算改三维: 工程量 = (code + b) × k[µV/码] × 1e-6 × r k 保持 ROM 引脚 LSB,b 为零点,r 为端子变比(int32 Q16)。 变比不能并进 k:UA 端子域 k 达 1.5e5 µV/码、Q16 溢出 int32; r 单独存 Q16 不溢出。两点标定改标 r,k 仍是板级事实。 - 新开单段 [adc_cal_sec](8 键 r_ua..r_in),cfg 段数达 8/8 上限 - 一次变比复用 prot_set 四项,不新开段;r=0 表示本路不折算 - ROM 默认带台架实测变比(电压族 1000、电流族 29.4118), 烧进固件即显二次值;VALID 仍须两点标定,两者互不派生 - eng 判据改「每路要么已配变比、要么削顶不显示」,不再看 VALID - 104 地址 0x8050~0x8057 写 r,run_par_cal_r_set 写值与置脏同函数 - 录波 v2:通道表加 int32 r_q16,头 128→160、整段 5250→5282; code_to_milli 用余数分解换等价单次圆整,20 万组大整数对拍 0 不符 - 文档同步:采集与算法层/通道映射/故障录波/网络配置/配置文件/ 产品需求/内存台账,历史带日期实测数字按冻结口径不改 - 单测 13 套全绿,/keil-build 0 错误 0 警告(256024/31760/ 2080/152368),RAM 实测回填内存内容分配策略 §6.1
This commit is contained in:
+15
-12
@@ -11,7 +11,7 @@
|
||||
|---|---|---|---|---|
|
||||
| `adc_input.h` / `.c` | 语义取数与有效性 | 统一语义取数:两片转换器的码值按语义(`UA…IN` / 温度)暴露,并消费 STM32 ADC1 的 DMA 乒乓缓冲 | `adc_input_init()` `adc_input_poll()` `adc_input_raw()` | TPAFE 快照消费 / STM32 ADC1 启动与扫描 / 通道语义映射 |
|
||||
| `adc_health.h` / `.c` | 语义取数与有效性 | 采样有效性判定:吃计数快照,产出静默 / 丢帧 / 单调性 / 错误增量故障位(判定与告警编排分离);「采样通道自检」残余的码值贴轨观察器同层 | `adc_health_check()` `adc_health_flags()` `adc_health_rail_watch()` | 「采样有效性校验」/「采样通道自检」(残余) |
|
||||
| `adc_cal.h` / `.c` | 标定折算 | 通道标定折算(「数据可信」标定折算与落盘整定):工程量 =(code+b)×k,Q16 系数/零点两段落盘 + 三态 flags + 合法带哨兵 + 两点公式 | `adc_cal_apply()` `adc_cal_flags()` `adc_cal_set()` | 「通道标定(一次系数 + 零漂)」(M4 半边) |
|
||||
| `adc_cal.h` / `.c` | 标定折算 | 通道标定折算(「数据可信」标定折算与落盘整定):工程量 =(code+b)×k×r,k(前端斜率)/ b(零点)/ r(端子变比)三维、三段 cfg 落盘 + 三态 flags + 合法带哨兵 + 两点公式(标的是 r) | `adc_cal_apply()` `adc_cal_flags()` `adc_cal_set()` `adc_cal_set_ratio()` `adc_cal_ratio_configured()` | 「通道标定(一次系数 + 零漂)」(M4 半边) |
|
||||
| `meas.h` / `.c` | 全周傅氏相量发布 | 遥测快照(「数据可信」遥测快照发布,M5 地基):200 ms 基波有效值 + 品质快照,双区无锁读,全仓唯一发布者 = adcTask | `meas_tick()` `meas_get()` `meas_seq()` | M5(104 改读快照与频率点绑定、UI 是读者) |
|
||||
| `sample_ring.h` / `.c` | 全周傅氏相量输入 | 按语义通道存最近 2 周波(40 点),供相量层"以当前时刻为终止"取窗 | `sample_ring_push_frame()` `sample_ring_window()` | 全周傅氏相量输入(为「每 1 ms 滑动窗刷新」供数) |
|
||||
| `phasor.h` / `.c` | 全周傅氏相量 | 全周傅氏:一周波 20 点 DFT → 基波 / 谐波相量(有效值口径),双区原子切换供无锁读 | `phasor_run()` `phasor_get()` `phasor_calc()` | 全周傅氏系数表 / 基波相量实例 / 相量有效值输出 / 每 1 ms 滑动窗刷新 / 双区指针原子交换 |
|
||||
@@ -106,15 +106,18 @@ ADC 引脚电压 V)+ `adc_health_rail_watch()` ×9(「采样通道自检」
|
||||
- **ROM 默认 = 量程口径冻结定死的引脚侧 LSB**(TPAFE 152.59 / STM32 805.66 µV/码,
|
||||
`docs/硬件平台/ADC通道映射说明.md` §7.1);表下标 = 语义枚举(UA0…IN7),
|
||||
V0.2 改版与本目录 `adc_input` 映射表同步改。
|
||||
- 三态可用性 `adc_cal_flags()`:VALID(已按变比标定,二次值成立——端子变比确认与两点标定前恒 0)
|
||||
- 三态可用性 `adc_cal_flags()`:VALID(已两点标定确认:k/b 在带内 **且** 变比 r 已配置)
|
||||
/ CLIPPED(运行期按来源派生,V0.2 后自动清零)。**消费者判「能不能信」只许查它**,
|
||||
`adc_cal_apply()` 不返回可用性(docs/算法/采集与算法层设计说明.md §13.3 三态可用性表)。
|
||||
- 合法带哨兵:k ∉ ROM×[0.5,2.0] 或 |b| > 8192 → 折算回退 ROM + 每通道每开机一条
|
||||
`[ERR]` + VALID 被压制(**即便文件位图说已标定**);这层兜底同时挡坏文件与坏标定。
|
||||
- `adc_cal_set()` / `adc_cal_two_point()`:整定与两点公式的算术核已齐备(单测还原合成
|
||||
k/b);**台架两点标定待实测**,本层不含注入。
|
||||
- cfg 两段 `[adc_cal]` / `[adc_cal_ofs]` **不挂回调**(RAM 副本即生效值,引擎 load/poll
|
||||
原地改写,逐点现读);注册在 `app_main_init()` 内核前完成,必须先于 `cfg_file_load`。
|
||||
变比 r 是另一条独立哨兵:不在 `0` 或 `≥ 1.0` 的合法带里 → 该路**按不折算处理**
|
||||
(停在引脚电压,不回退),同样不报 VALID。
|
||||
- `adc_cal_set()` / `adc_cal_two_point()` / `adc_cal_set_ratio()`:整定、两点公式(标 r)
|
||||
与变比写入三个入口齐备(单测还原合成 k/b/r);**台架两点标定待实测**,本层不含注入。
|
||||
- cfg 三段 `[adc_cal]` / `[adc_cal_ofs]` / `[adc_cal_sec]` **不挂回调**(RAM 副本即生效值,
|
||||
引擎 load/poll 原地改写,逐点现读);注册在 `app_main_init()` 内核前完成,
|
||||
必须先于 `cfg_file_load`。
|
||||
|
||||
### 3.9 `meas.c` / `.h` — 遥测快照发布(全周傅氏相量发布侧,「数据可信」波次)
|
||||
- **唯一发布者 = adcTask**:`phasor_run()`/`freq_run()` 同拍、200 ms **tick 经过**分频
|
||||
@@ -163,11 +166,11 @@ ADC 引脚电压 V)+ `adc_health_rail_watch()` ×9(「采样通道自检」
|
||||
- **只做字节**:文件落点、路径拼法、标定系数、数据本体全部由调用方注入(meta 由 adcTask 填、
|
||||
数据经 `wave_reader_fn` 回调取、路径由落盘道拼)→ 两份文件零平台依赖,主机单测免桩 `#include`
|
||||
原样纳入;`wave_store_meta_init()` 只填格式侧静态事实(并把通道表 `sem` 钉成下标、按归属填
|
||||
`conv` / `bits`),`avail` / `k_q16` / `b_codes` 一律留 0 等封存瞬间现取现填——不复制第二个事实源。
|
||||
- **尺寸等式**(单测钉死,落盘道拿它比对写回字节数):整段 = 32 + 12×通道数 + 2×点数×通道数 + 2;
|
||||
当前口径(8 路 320 点)= 头 128 + 数据 5120 + 尾 CRC 2 = **5250 字节**(这条等式是给落盘道比对写回
|
||||
字节数用的):FatFs 该移植在簇分配失败时会返回 FR_OK + 短写且无错误码,判成功必须校验字节数
|
||||
(故障录波设计说明「四条已知陷阱」第二条)。
|
||||
`conv` / `bits`),`avail` / `k_q16` / `b_codes` / `r_q16` 一律留 0 等封存瞬间现取现填——不复制第二个事实源。
|
||||
- **尺寸等式**(单测钉死,落盘道拿它比对写回字节数):整段 = 32 + 16×通道数 + 2×点数×通道数 + 2;
|
||||
当前口径(8 路 320 点、格式 v2)= 头 160 + 数据 5120 + 尾 CRC 2 = **5282 字节**(v1 通道表 12 B/路时
|
||||
是 5250 字节;这条等式是给落盘道比对写回字节数用的):FatFs 该移植在簇分配失败时会返回
|
||||
FR_OK + 短写且无错误码,判成功必须校验字节数(故障录波设计说明「四条已知陷阱」第二条)。
|
||||
- **CRC-16/MODBUS 自带无表实现**(反射多项式 0xA001、初值 0xFFFF、异或输出 0,自检锚点字符串
|
||||
"123456789" = 0x4B37)。设计说明 §6.2 原写「与屏板链路同一张表」经全仓实测**不成立**(这张表不
|
||||
存在:crc.c 与 crc.h 已于 2026-10-02 删除,屏板侧 `remo_crc()` 是单字节异或)——文档等文档道回改,
|
||||
@@ -178,7 +181,7 @@ ADC 引脚电压 V)+ `adc_health_rail_watch()` ×9(「采样通道自检」
|
||||
写得出、PC 读不了」。反过来**不设**任何品质侧不变式:`avail` 同时带有效与削顶是合法事实
|
||||
(lead 裁定 2026-10-29),编码器如实透传、解码器不许据此判废。
|
||||
- **游标分次搬运**(256 B 缓冲够用,整段绝不驻留 RAM;数据 CRC 走增量中间值):
|
||||
`wave_store_pack_next()` 头那一步要求缓冲容量不小于整头 128,否则返 `WAVE_ST_E_PARAM` 且**不推进
|
||||
`wave_store_pack_next()` 头那一步要求缓冲容量不小于整头 160(v1 是 128),否则返 `WAVE_ST_E_PARAM` 且**不推进
|
||||
游标**;没有数据源却进数据区 → `WAVE_ST_E_STATE` 且**不产空字节**(产空字节等于写出「长度对、
|
||||
内容全 0」的段 = docs/子系统设计/故障录波设计说明.md §8.1 明令禁止的伪造证据)。
|
||||
- 段名严格 8.3(该移植 `FF_USE_LFN=0`):`wave_store_seg_path()` 当前布局恒返回 16 个字符(不含结束
|
||||
|
||||
+252
-28
@@ -1,18 +1,25 @@
|
||||
/*
|
||||
* 模块名称:通道标定与量纲折算(标定折算)——实现
|
||||
* 模块功能:见 adc_cal.h。折算模型(工程量 = (code + b) × k × 1e-6,k 为 Q16
|
||||
* µV/码)与两段 cfg 布局([adc_cal]:k_* + valid 位图;[adc_cal_ofs]:
|
||||
* b_*)照 docs/算法/采集与算法层设计说明.md §13.2 / §4.4 落地;系数口径
|
||||
* 模块功能:见 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。
|
||||
* §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,flags 不报
|
||||
* VALID,每通道每次开机首见沿打一条 [ERR](adc_cal_init 重置留痕)。
|
||||
* 或 |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 标定折算与落盘整定)
|
||||
@@ -30,6 +37,10 @@
|
||||
* 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 已配置」条件
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
@@ -45,15 +56,19 @@
|
||||
|
||||
/* 常量定义区 */
|
||||
|
||||
/* 段名(≤ CFG_FILE_MAX_NAME_LEN)与键名(k_ua…b_in,均 4/5 字符)
|
||||
/* 段名(≤ 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=语义下标。
|
||||
@@ -70,8 +85,14 @@ 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
|
||||
@@ -91,10 +112,27 @@ static const adc_cal_kcfg_t s_kcfg_rom = {
|
||||
};
|
||||
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() 刻意不清**
|
||||
@@ -126,6 +164,18 @@ static const cfg_key_def_t s_b_keys_tbl[8] = {
|
||||
{ "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 },
|
||||
};
|
||||
|
||||
/* 函数定义区 */
|
||||
|
||||
@@ -189,6 +239,19 @@ static void s_note_bad(unsigned sem, int32_t k, int32_t b) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:变比合法带检查(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 - 输出
|
||||
@@ -211,7 +274,46 @@ static int s_eff_kb(unsigned sem, int32_t *p_k, int32_t *p_b) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:标定层初始化——RAM 副本置 ROM 默认 + 注册两段进 cfg 引擎(幂等)
|
||||
* 函数功能:变比越带留痕(每通道每次开机首见沿一条 [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
|
||||
@@ -223,7 +325,9 @@ void adc_cal_init(void) {
|
||||
|
||||
(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;
|
||||
@@ -250,24 +354,49 @@ void adc_cal_init(void) {
|
||||
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
|
||||
* 返 回 值:工程量浮点(未标定 = ADC 引脚电压 V;TEMP/越界 = 原样透传码值) float
|
||||
* 返 回 值:工程量浮点(r=0 = ADC 引脚电压 V;TEMP/越界 = 原样透传码值) float
|
||||
* 限定条件:无(任意上下文,纯读)
|
||||
* 函数说明:见 adc_cal.h 同名声明;k/b 经 s_eff_kb 合法带哨兵(越带回退 ROM)
|
||||
* 函数说明:见 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);
|
||||
return ((float)(code + b)) * ((float)k) * ADC_CAL_K_SCALE;
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -275,7 +404,10 @@ 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 uint32_t
|
||||
* 限定条件:无
|
||||
* 函数说明:VALID 需文件位图与 k 合法带**同时**成立(哨兵压制说谎位图,见文件头)
|
||||
* 函数说明:VALID 需文件位图、k/b 合法带、变比已配置**三者**同时成立
|
||||
* (哨兵压制说谎位图,见文件头)。三者里任一不成立就整条不报 VALID——
|
||||
* 宁可让现场看到「未标定」,也不能让一个「引脚电压乘以一个猜的变比」
|
||||
* 冒充二次值去解锁保护判据
|
||||
*/
|
||||
uint32_t adc_cal_flags(adc_semantic_t sem) {
|
||||
uint32_t f = 0u;
|
||||
@@ -283,9 +415,10 @@ uint32_t adc_cal_flags(adc_semantic_t sem) {
|
||||
if ((unsigned)sem >= (unsigned)ADC_SEM_TEMP) {
|
||||
return 0u;
|
||||
}
|
||||
/* VALID = 文件位图置位 且 k 合法(哨兵不落共享状态,随重载自愈) */
|
||||
/* 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_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 侧单极性无偏置 → 负半周削顶)——
|
||||
@@ -376,6 +509,53 @@ float adc_cal_b_codes(adc_semantic_t sem) {
|
||||
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 - 零点(码值)
|
||||
@@ -402,6 +582,31 @@ int adc_cal_set(adc_semantic_t sem, int32_t k_q16, int32_t b_codes) {
|
||||
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
|
||||
@@ -430,35 +635,54 @@ void adc_cal_note_persisted(void) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:两点标定公式(标定项的算术核,纯函数无状态)
|
||||
* 入口参数: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 中间量,主机可测;台架注入待跑)
|
||||
* 函数功能:两点标定公式(标定项的算术核,纯函数无状态;v2.0 起标 r 不标 k)
|
||||
* 入口参数:c_lo/c_hi、v_lo_uv/v_hi_uv、k_q16、p_r_q16/p_b 见 adc_cal.h 同名声明
|
||||
* 返 回 值:0 成功 / -1 参数、斜率或变比越带 / -2 零点越 ±B_MAX int
|
||||
* 限定条件:无(int64 中间量,主机可测)
|
||||
* 函数说明:公式与**为什么这样分步**(直接一次乘除会顶穿 int64):
|
||||
* v_uv = (code + b) × k_q16 × r / 65536 ← 折算式的 µV 形态
|
||||
* ⇒ 差分里零点抵消:dv = dc × k_q16 × r / 65536
|
||||
* ⇒ t = dv × 65536 / k_q16 = dc × r ← code·ratio 的混合量,量级 ≤ 2^31
|
||||
* ⇒ r_q16 = t × 65536 / dc ← 还原成 Q16 比值
|
||||
* ⇒ b = t_lo × 65536 / r_q16 − c_lo,其中 t_lo = v_lo × 65536 / k_q16
|
||||
* 若把 r_q16 = dv×2^32/(dc×k_q16) 一次算完,5 kV 端子注入时分子到 1e19
|
||||
* 已经越过 long long 上界(9.22e18)——分两步走,中间量 largest 约 1.4e14
|
||||
*/
|
||||
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) {
|
||||
int32_t k_q16, int32_t *p_r_q16, int32_t *p_b) {
|
||||
long long dc;
|
||||
long long dv;
|
||||
long long kq;
|
||||
long long t;
|
||||
long long rq;
|
||||
long long tl;
|
||||
long long bb;
|
||||
|
||||
if ((p_k_q16 == NULL) || (p_b == NULL)) {
|
||||
if ((p_r_q16 == NULL) || (p_b == NULL)) {
|
||||
return -1;
|
||||
}
|
||||
if ((k_q16 <= 0L) || (k_q16 > 100000000L)) {
|
||||
return -1; /* 系数不在合理域(1526 µV/码 上界)= 调用方取错了 */
|
||||
}
|
||||
if ((v_lo_uv <= -ADC_CAL_V_MAX) || (v_lo_uv >= ADC_CAL_V_MAX) ||
|
||||
(v_hi_uv <= -ADC_CAL_V_MAX) || (v_hi_uv >= ADC_CAL_V_MAX)) {
|
||||
return -1; /* ±1000 V:超了几乎必然是把 mV 或码值当 µV 填了 */
|
||||
}
|
||||
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; /* 斜率必须为正:码与电压同向,反向 = 映射/接线错 */
|
||||
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:越界 = 单位/量级错 */
|
||||
t = ((dv << 16) + ((long long)k_q16 >> 1)) / (long long)k_q16; /* = dc × r */
|
||||
rq = ((t << 16) + (dc >> 1)) / dc; /* = r × 65536 */
|
||||
if ((rq < (long long)ADC_CAL_R_MIN) || (rq > (long long)ADC_CAL_R_MAX)) {
|
||||
return -1; /* 变比越带:<1.0 = 把 mV 当 V,>32767 = 单位/量级错 */
|
||||
}
|
||||
bb = (((long long)v_lo_uv) << 16) / kq - (long long)c_lo;
|
||||
tl = (((long long)v_lo_uv << 16) + ((long long)k_q16 >> 1)) / (long long)k_q16; /* (c_lo+b) × r */
|
||||
bb = ((tl << 16) / rq) - (long long)c_lo;
|
||||
if ((bb > (long long)ADC_CAL_B_MAX) || (bb < -(long long)ADC_CAL_B_MAX)) {
|
||||
return -2; /* 零点异常 → 先查前端,别用系数凑(§8 风险表) */
|
||||
return -2; /* 零点异常 → 先查前端,别用变比凑(§8 风险表) */
|
||||
}
|
||||
*p_k_q16 = (int32_t)kq;
|
||||
*p_r_q16 = (int32_t)rq;
|
||||
*p_b = (int32_t)bb;
|
||||
return 0;
|
||||
}
|
||||
|
||||
+161
-35
@@ -1,22 +1,38 @@
|
||||
/*
|
||||
* 模块名称:通道标定与量纲折算(标定折算,「数据可信」标定折算与落盘整定)
|
||||
* 模块功能:把 adc_input 的原始码值折算成工程量——模型与系数口径的唯一落点:
|
||||
* 工程量 = (code + b) × k_f,k_f = k[µV/码] × 1e-6
|
||||
* k 以 int32 **Q16**(µV/码 × 65536)落盘、b 以 int32 码值落盘,
|
||||
* RAM 副本注册进 cfg_file 引擎([adc_cal] / [adc_cal_ofs] 两段,
|
||||
* 布局见 docs/算法/采集与算法层设计说明.md §13.8);**不挂回调**——RAM 副本
|
||||
* 即生效值,逐点折算现取现用(1 kHz × 9 通道 = 9k 次/秒浮点乘,可忽略)。
|
||||
* 三态可用性:ADC_CAL_F_VALID(已按变比标定 → 工程量为二次值)与
|
||||
* 工程量 = (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),
|
||||
* 两点标定今后标的就是它。
|
||||
* 为什么必须分开:把变比折进 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 把此前的两份实现收成一份)。
|
||||
* ⚠ 输出单位口径 = **ADC 引脚电压 V**(eng=0):ROM 默认系数按量程口径冻结
|
||||
* 定死的引脚侧 LSB(TPAFE 152.59 µV/码、STM32 805.66 µV/码,见
|
||||
* docs/硬件平台/ADC通道映射说明.md §7.1);端子/子板变比确认与两点
|
||||
* 标定完成前不得把输出当二次值消费。TEMP 不参与线性标定
|
||||
* (决策 5),adc_cal_apply() 原样透传码值。
|
||||
* ⚠ 两个**互不派生**的事实,别互相反推:
|
||||
* · 数值量纲由 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 上原子,
|
||||
@@ -35,8 +51,15 @@
|
||||
* 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 是前端斜率,混一起就是「用系数凑硬件」。
|
||||
* 同步:adc_cal_two_point() 改标 r(k 由 ROM 固化)、
|
||||
* +adc_cal_ratio_q16()/+adc_cal_ratio()/+adc_cal_ratio_configured()
|
||||
* +adc_cal_set_ratio()
|
||||
*/
|
||||
|
||||
#ifndef ADC_CAL_H
|
||||
#define ADC_CAL_H
|
||||
|
||||
@@ -49,7 +72,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
/* 通道可用性标志(adc_cal_flags() 返回值位) */
|
||||
#define ADC_CAL_F_VALID (0x01u) /* k 已按变比标定 → 工程量为二次值(两点标定后置位) */
|
||||
#define ADC_CAL_F_VALID (0x01u) /* 该路已两点标定确认(k/b/r 合法带内 → 读数可信) */
|
||||
#define ADC_CAL_F_CLIPPED (0x02u) /* 硬件削顶(V0.1 STM32 侧单极性无偏置)→ 交流量不可信 */
|
||||
|
||||
/*
|
||||
@@ -62,21 +85,56 @@ extern "C" {
|
||||
#define ADC_CAL_K_Q16_TPAFE (10000000L)
|
||||
#define ADC_CAL_K_Q16_STM32 (52800000L)
|
||||
|
||||
/* 系数合法带(docs/算法/采集与算法层设计说明.md §13.2 尾注):k 必须落在板级 ROM 系数的 ×[0.5, 2.0]
|
||||
* 区间内;带宽用 ROM 值移位实现(>>1 / <<1),不设独立宏防两处真相 */
|
||||
/*
|
||||
* 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(电压族);送检前必须用标准源两点标定重写 r(见
|
||||
* adc_cal_two_point() 的语义变更)。
|
||||
* 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 中间量(两点标定算出的 kq / 除法前的分子)做上界闸。 */
|
||||
#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 引擎(幂等)
|
||||
* 函数功能:标定层初始化——RAM 副本置 ROM 默认 + 注册三段进 cfg 引擎(幂等)
|
||||
* 入口参数:无
|
||||
* 返 回 值:无
|
||||
* 限定条件:内核启动前调用(app_main_init);注册必须先于 cfg_file_load
|
||||
* (「所有受管段先注册、后 load」契约,见 Drivers/BSP/NETCFG/net_cfg.c 的
|
||||
* net_cfg_load 注释)
|
||||
* 函数说明:段属主为 defaultTask(配置引擎),本模块只提供 RAM 副本与默认值;
|
||||
* 文件值由引擎 load/poll 原地写入 RAM 副本,本层逐点折算现读
|
||||
* 文件值由引擎 load/poll 原地写入 RAM 副本,本层逐点折算现读。
|
||||
* ⚠ 变比段 [adc_cal_sec] 的 ROM 默认**不是 0**(电压 1000 / 电流 29.4118),
|
||||
* 因此烧进新固件后屏上立刻显二次值——这是刻意的:变比是硬件事实,
|
||||
* 出厂就该带着;而 VALID(准不准)仍然要求两点标定,两者互不代替。
|
||||
*/
|
||||
void adc_cal_init(void);
|
||||
|
||||
@@ -84,11 +142,13 @@ void adc_cal_init(void);
|
||||
* 函数功能:码值 → 工程量折算(docs/算法/采集与算法层设计说明.md §13.2 模型)
|
||||
* 入口参数:sem - 语义通道(TEMP 透传不折算) adc_semantic_t
|
||||
* code - 原始码值(TPAFE 16 位补码 / STM32 0..4095) int32_t
|
||||
* 返 回 值:工程量浮点(未标定时单位 = ADC 引脚电压 V;VALID 后置位 = 二次值) float
|
||||
* 返 回 值:工程量浮点(r≠0 → 二次值 V/A;r=0 → ADC 引脚电压 V) float
|
||||
* 限定条件:无(任意上下文,纯读)
|
||||
* 函数说明:eng = (code + b) × k[µV/码] × 1e-6,由 Q16 现值派生,无缓存副本
|
||||
* (避免无回调下的陈旧系数);k/b 越合法带 → 该通道回退 ROM 默认 +
|
||||
* 首见沿 [ERR] 留痕(adc_cal_init 重置留痕位),flags 同时不报 VALID
|
||||
* 函数说明: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);
|
||||
|
||||
@@ -97,8 +157,12 @@ 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 在合法带内(哨兵不落状态,
|
||||
* 天然跟随 load/poll 重载);CLIPPED = V0.1 静态表(UB/UC/IB/IC/IN)
|
||||
* 函数说明: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);
|
||||
|
||||
@@ -120,11 +184,12 @@ uint32_t adc_cal_flags(adc_semantic_t sem);
|
||||
* (未标定);(c) 定值校验和失配闭锁(`prot_set_locked()`);(d) 全局软压板
|
||||
* 退出(`PS_GLOBAL_EN` = 0)。要判「这拍到底评估了没有」必须四路一起看
|
||||
* (编排顺序见 `App/task/protect_task.c`)。
|
||||
* ③ ⚠ **两个同族谓词与它不是一件事,不要顺手合并**(三种口径各有用途):
|
||||
* · 电气 **8** 路全 VALID 才升 eng=二次值(量纲标签,判据在
|
||||
* `App/app/ui_data_feed.c` 的 `feed_eng()`)——并进来会让部分标定的整帧
|
||||
* 被误标二次值;反过来把本三路口径当 8 路用,V0.1 下 UB/UC/IB/IC/IN
|
||||
* 永不置位 → 徽标与停走**永不解除**;
|
||||
* ③ ⚠ **两个同族谓词与它不是一件事,不要顺手合并**(两种口径各有用途):
|
||||
* · 整帧量纲标签 eng(`App/app/ui_data_feed.c` 的 `feed_eng()`)看的是
|
||||
* **每路 r 是否配置**(配了就出二次值),判据 = 「每路要么已配变比、
|
||||
* 要么削顶不显示」——削顶通道页面本来就显 "--",不参与整帧量纲断言;
|
||||
* 并进来会让"三路标定齐但八路变比未全配"的整帧被误标引脚电压,
|
||||
* 反过来把本三路口径当量纲用,V0.1 下五路削顶会让徽标永不解除;
|
||||
* · 逐通道打印 VALID/CLIPPED 位图是诊断输出(`App/task/adc_task.c`),
|
||||
* 不是判定。
|
||||
* ④ 台架调试旁路 = 编译期 `-DDTU_BENCH`(`docs/算法/采集与算法层设计说明.md`
|
||||
@@ -165,6 +230,42 @@ float adc_cal_b_codes(adc_semantic_t sem);
|
||||
*/
|
||||
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 吸收(见 adc_cal_two_point 说明)。
|
||||
*/
|
||||
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
|
||||
@@ -182,19 +283,44 @@ int32_t adc_cal_k_q16(adc_semantic_t sem);
|
||||
*/
|
||||
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);
|
||||
|
||||
/*
|
||||
* 函数功能:两点标定公式(「数据可信」标定项的算术核,纯函数无状态)
|
||||
* 入口参数:c_lo/c_hi - 两点码值(c_hi > c_lo) int32_t
|
||||
* v_lo_uv/v_hi_uv - 两点实测电压(µV,v_hi > v_lo,端子/引脚域均可) int32_t
|
||||
* p_k_q16 / p_b - 输出:系数(Q16)与零点偏移(码值) int32_t *
|
||||
* 返 回 值:0 - 成功;-1 - 参数/斜率非法;-2 - 零点越 ±ADC_CAL_B_MAX(先查前端,
|
||||
* 不得用系数凑错斜率,docs/算法/采集与算法层设计说明.md §13.2 尾注) int
|
||||
* v_lo_uv/v_hi_uv - 两点**端子侧**实测值(µV:电压填 1e6 域,电流填
|
||||
* 1e6 域但单位是 µA,即「0.5 A → 500000」) int32_t
|
||||
* k_q16 - 该路生效系数(走 adc_cal_k_q16() 取,不要自己填) int32_t
|
||||
* p_r_q16 / p_b - 输出:变比(Q16)与零点(码值) int32_t *
|
||||
* 返 回 值:0 - 成功;-1 - 参数/斜率非法或算出的变比越出 [ADC_CAL_R_MIN, ADC_CAL_R_MAX];
|
||||
* -2 - 零点越 ±ADC_CAL_B_MAX(先查前端,不得用系数凑错斜率,
|
||||
* docs/算法/采集与算法层设计说明.md §13.2 尾注) int
|
||||
* 限定条件:无(纯整数运算,主机侧可直接单测)
|
||||
* 函数说明:k = (v_hi−v_lo)×65536/(c_hi−c_lo)(四舍五入),b = v_lo×65536/k − c_lo;
|
||||
* 实测注入未做(台架待跑),本函数即「公式齐备」的交付物
|
||||
* 函数说明:**v2.0 起标的是 r,不再标 k**——k 是板级前端斜率(ROM 固化,现场
|
||||
* 不该改也改不动),真正未知的是「端子↔引脚」这一层。公式:
|
||||
* r = (v_hi−v_lo) / [ (c_hi−c_lo) × k ] ← 零点在差分里抵消
|
||||
* b = v_lo / (k × r) − c_lo
|
||||
* 两条都用 int64 中间量;`k × r` 的量级先按 Q16 归一到同一个 2^32 再除,
|
||||
* 避免先乘后除把 int64 顶到溢出边。v 上限 1e9 µV(1000 V / 1000 A)——
|
||||
* 超了几乎必然是把 mV 或码值当 µV 填了,直接判参数错而不是算个假变比出来。
|
||||
*/
|
||||
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);
|
||||
int32_t k_q16, int32_t *p_r_q16, int32_t *p_b);
|
||||
|
||||
/*
|
||||
* 函数功能:通道码值域「贴轨带」门限(采样通道自检 越界检出的门限来源,按 FS 百分比)
|
||||
|
||||
+57
-10
@@ -3,14 +3,16 @@
|
||||
* 模块功能:见 run_par.h。工厂默认 = 「数据可信」下游常数按工程量重定后的额定 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)。
|
||||
* (通道系数覆盖**不落本段**——直通 adc_cal 的 [adc_cal]/[adc_cal_ofs]/
|
||||
* [adc_cal_sec] 三段,系数唯一真源在那三段,lead 裁②a)。
|
||||
* 适用平台:STM32F407ZGTx(Cortex-M4F,硬件 FPU)
|
||||
* 作者:DTU 项目组
|
||||
* 创建日期:2026-10-25(道D 运行参数段)
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-25 DTU 项目组 创建:[run_par] 段 + 104 映射 + 直通 adc_cal
|
||||
* + B7 式脏标志落盘请求
|
||||
* v1.2 2026-11-11 DTU 项目组 变比维落地:+run_par_cal_r_set(写值+置脏同一
|
||||
* 函数)+ 104 地址 0x8050~0x8057 读写两支
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
@@ -36,6 +38,7 @@
|
||||
#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 */
|
||||
#define RUN_PAR_IOA_R0 0x8050u /* 变比 r 覆盖 8 路:0x8050~0x8057(比值,无单位) */
|
||||
|
||||
/* 死区合法域上限(毫单位):电压 50 V / 电流 1 A / 频率 5 Hz——守卫挡误写,
|
||||
* 不限工况;下限统一 0(负死区 = 配置错误) */
|
||||
@@ -227,6 +230,10 @@ int run_par_104_read(uint32_t addr, float *out) {
|
||||
*out = adc_cal_b_codes((adc_semantic_t)(addr - RUN_PAR_IOA_B0));
|
||||
return 0;
|
||||
}
|
||||
if ((addr >= RUN_PAR_IOA_R0) && (addr < RUN_PAR_IOA_R0 + RUN_PAR_CH_N)) {
|
||||
*out = adc_cal_ratio((adc_semantic_t)(addr - RUN_PAR_IOA_R0));
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -288,14 +295,43 @@ int run_par_cal_b_set(adc_semantic_t sem, int32_t b_codes) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:104 运行参数地址写(两段式;k/b 经本模块语义 setter——**run_par 不存
|
||||
* 系数**,系数唯一真源 = [adc_cal]/[adc_cal_ofs],lead 裁②a)
|
||||
* 函数功能:写某路变比 r(比值,1.0 ~ 32767.99;0 = 本路不折算)并置脏
|
||||
* 入口参数:sem - 电气语义通道 adc_semantic_t;r - 变比(无单位) float
|
||||
* 返回值:0 - 已写并置脏;-1 - 粗守或执行段拒(副本未动、不置脏) int
|
||||
* 限定条件:同 run_par_cal_k_set()
|
||||
* 函数说明:与 k/b 两支**同一条裁②理由**——写值与置脏必须长在一个函数里,
|
||||
* 所以这里不再给"单独的置脏口"。粗守只为挡误写(0 或 [1, 32767.99]),
|
||||
* 真带由 adc_cal_set_ratio() 执行段裁,两层各说各的。
|
||||
* ⚠ 0 是**合法入参**(显式关掉本路折算),不是"清空/非法"——粗守
|
||||
* 因此写成「0 或 ≥1」两条并集,而不是 `> 0`。写成 `> 0` 会让现场
|
||||
* 无法把某一路退回引脚电压(只能改成 1.0,而那是个假变比)。
|
||||
*/
|
||||
int run_par_cal_r_set(adc_semantic_t sem, float r) {
|
||||
int32_t r_q16;
|
||||
|
||||
if (!isfinite(r) || ((r != 0.0f) && ((r < 1.0f) || (r > 32767.99f)))) {
|
||||
return -1;
|
||||
}
|
||||
r_q16 = (int32_t)lrintf(r * 65536.0f);
|
||||
if (adc_cal_set_ratio(sem, r_q16) != 0) {
|
||||
return -1; /* 执行段拒:副本未动,脏位也不置 = 不承诺任何持久化 */
|
||||
}
|
||||
s_dirty = 1u;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:104 运行参数地址写(两段式;k/b/r 经本模块语义 setter——**run_par
|
||||
* 不存系数**,系数与变比的唯一真源 = [adc_cal]/[adc_cal_ofs]/[adc_cal_sec],
|
||||
* lead 裁②a)
|
||||
* 入口参数:addr - 104 信息体地址;v - 工程量/系数值;select - 1 选择 / 0 执行
|
||||
* 返 回 值:0 接受 / -1 拒绝 / -2 非本段(调用方落 prot_set) int
|
||||
* 函数说明:选择段 = 校验不提交(死区走与 set 同源守卫;k/b 的 adc_cal 合法带
|
||||
* 函数说明:选择段 = 校验不提交(死区走与 set 同源守卫;k/b/r 的 adc_cal 合法带
|
||||
* 由执行段裁决——IEC104 允许执行段 PN,两段结果可不一致,注释在此
|
||||
* 明说防"选择过了怎么还 PN"的现场困惑)。k/b 两支自 2026-10-27 起一律
|
||||
* 走 run_par_cal_k_set() / run_par_cal_b_set():写值与置脏在同一个函数
|
||||
* 里发生,本函数不再自己碰 s_dirty(lead 裁②:消除两份置脏逻辑)。
|
||||
* 明说防"选择过了怎么还 PN"的现场困惑)。k/b/r 三支一律走
|
||||
* run_par_cal_k_set() / run_par_cal_b_set() / run_par_cal_r_set():
|
||||
* 写值与置脏在同一个函数里发生,本函数不再自己碰 s_dirty
|
||||
* (lead 裁②:消除两份置脏逻辑)。
|
||||
*/
|
||||
int run_par_104_write(uint32_t addr, float v, uint8_t select) {
|
||||
uint8_t slot; /* 每个命中分支先赋值再使用(无未init读路径) */
|
||||
@@ -337,6 +373,16 @@ int run_par_104_write(uint32_t addr, float v, uint8_t select) {
|
||||
/* 同款语义 setter:粗守 ±1e6 码在 setter 内,±8192 真带由 adc_cal 执行段裁 */
|
||||
return run_par_cal_b_set((adc_semantic_t)slot, (int32_t)lrintf(v));
|
||||
}
|
||||
if ((addr >= RUN_PAR_IOA_R0) && (addr < RUN_PAR_IOA_R0 + RUN_PAR_CH_N)) {
|
||||
slot = (uint8_t)(addr - RUN_PAR_IOA_R0);
|
||||
if (!isfinite(v)) {
|
||||
return -1; /* NaN/Inf 不得进 lrintf(该转换是 UB) */
|
||||
}
|
||||
if (select != 0u) {
|
||||
return 0; /* 选择段只粗守:真带裁决在执行段(同 k/b 两支) */
|
||||
}
|
||||
return run_par_cal_r_set((adc_semantic_t)slot, v);
|
||||
}
|
||||
return -2;
|
||||
}
|
||||
|
||||
@@ -352,8 +398,9 @@ uint8_t run_par_dirty_peek(void) {
|
||||
* 函数功能:执行落盘(整档外科保存成功后清脏;失败脏保持 = 下轮重试)
|
||||
* 返 回 值:0 成功/无脏;-1 写盘失败 int
|
||||
* 限定条件:defaultTask(配置引擎属主)上下文,SYS_EVT_RUN_PAR_SAVE 驱动
|
||||
* 函数说明:一次 save 同时覆盖 [run_par] 9 键与刚被 adc_cal_set 改写的
|
||||
* [adc_cal]/[adc_cal_ofs] 副本(lead 裁②:一条事件位一次 save)。
|
||||
* 函数说明:一次 save 同时覆盖 [run_par] 9 键与刚被 adc_cal_set()/adc_cal_set_ratio()
|
||||
* 改写的 [adc_cal]/[adc_cal_ofs]/[adc_cal_sec] 三段副本(lead 裁②:
|
||||
* 一条事件位一次 save)。
|
||||
* 成功路径上给标定层盖一次落盘水位(adc_cal_note_persisted())——它是
|
||||
* **观测回执**,不参与本函数任何判定;失败路径不盖,于是"save 一直失败"
|
||||
* 在标定层侧表现为差额长期不归零(道G task-43 第②项)。
|
||||
|
||||
+21
-3
@@ -6,7 +6,8 @@
|
||||
* (App/protocol/iec104/iec104_pts.c)每拍实时读取——无投递、无缓存
|
||||
* 副本(104 改读快照曾在 pts 侧留运行时副本 s_yc_db,运行参数段落地后收编于此,
|
||||
* 双源必打架);
|
||||
* - 系数覆盖 = **直通 adc_cal_set()**([adc_cal]/[adc_cal_ofs] 是系数
|
||||
* - 系数覆盖 = **直通 adc_cal_set() / adc_cal_set_ratio()**
|
||||
* ([adc_cal]/[adc_cal_ofs]/[adc_cal_sec] 是系数与变比的
|
||||
* 唯一真源,标定落盘与整定入口已定;本模块只加 104 地址映射 + 落盘请求,不另存
|
||||
* 一份系数——同 D1 裁"两处判断必打架"逻辑)。
|
||||
* 104 地址映射(docs/算法/继电保护功能复现规划.md §5.1 运行参数类
|
||||
@@ -14,13 +15,16 @@
|
||||
* 不在仓),本表即运行参数段的落实,送检点表若另给权威地址只改常量段:
|
||||
* 0x8030~0x8038 = 死区 UA/UB/UC/UN/IA/IB/IC/IN/FREQ(工程量 V/A/Hz);
|
||||
* 0x8040~0x8047 = k 覆盖(µV/码);0x8048~0x804F = b 覆盖(码值,可负);
|
||||
* 0x8050~0x8057 = 变比 r 覆盖(比值,无单位;0 = 本路不折算,见
|
||||
* run_par_cal_r_set 的说明);
|
||||
* 其余留白(未启用类别),**0x8027~0x802A 属 prot_set 告警段**,本表
|
||||
* 永不让位(两表按精确地址分流,见 iec104_pts.c set 路由)。
|
||||
* 持久化(B7 prot_set 先例同款):任一写成功 → 脏标志只置不清;
|
||||
* iec104Task 主循环 peek → 置 SYS_EVT_RUN_PAR_SAVE → defaultTask
|
||||
* (配置引擎属主)run_par_store_save() 调 cfg_file_save() 整档外科
|
||||
* 保存(含 [run_par] 与刚被 adc_cal_set 改写的 [adc_cal]/[adc_cal_ofs]
|
||||
* 副本)→ 成功清脏;失败留脏 = 下轮天然重试。本模块自身零文件 IO
|
||||
* 保存(含 [run_par] 与刚被 adc_cal_set()/adc_cal_set_ratio() 改写的
|
||||
* [adc_cal]/[adc_cal_ofs]/[adc_cal_sec] 副本)→ 成功清脏;
|
||||
* 失败留脏 = 下轮天然重试。本模块自身零文件 IO
|
||||
* (属主纪律)。
|
||||
* ⚠ 三份职责别混(道G task-43 第③项,此前只有意图式陈述):
|
||||
* - **谁写 RAM**:系数/零点的唯一真源是 adc_cal 的引擎副本,而本模块那两支
|
||||
@@ -57,6 +61,8 @@
|
||||
* 给标定层盖一次落盘水位(纯观测回执,不参与判定);
|
||||
* 本头把"谁写 RAM / 谁落盘 / 什么时机 / poll 不是保存
|
||||
* 路径"从意图式陈述改成逐条契约
|
||||
* v1.3 2026-11-11 DTU 项目组 变比维:+run_par_cal_r_set()(0 = 本路不折算)
|
||||
* + 104 地址 0x8050~0x8057 读写
|
||||
*/
|
||||
|
||||
#ifndef RUN_PAR_H
|
||||
@@ -163,6 +169,18 @@ int run_par_cal_k_set(adc_semantic_t sem, float k_uv);
|
||||
*/
|
||||
int run_par_cal_b_set(adc_semantic_t sem, int32_t b_codes);
|
||||
|
||||
/*
|
||||
* 函数功能:写某路变比 r(比值)**并在同一函数内置脏**(与 k/b 同款语义)
|
||||
* 入口参数:sem - 电气语义通道 adc_semantic_t
|
||||
* r - 变比(无单位;**0 = 本路不折算**,否则须落在 1.0 ~ 32767.99) float
|
||||
* 返回值:0 - 已写并置脏;-1 - 粗守或执行段拒(副本未动、不置脏) int
|
||||
* 限定条件:同 run_par_cal_k_set()
|
||||
* 函数说明:**0 是合法入参**,语义 = 「这一路退回引脚电压」——不是清零、不是非法。
|
||||
* 104 上写 0 就是现场把某路折算关掉的正规手段;写 1.0 反而会留下一个
|
||||
* 假变比(端子值 = 引脚电压),那是错的事实而不是"关掉"。
|
||||
*/
|
||||
int run_par_cal_r_set(adc_semantic_t sem, float r);
|
||||
|
||||
/*
|
||||
* 函数功能:取第 idx 路死区的合法域(工程量)——**合法域的唯一出口**
|
||||
* 入口参数:idx - 0..8 uint8_t;lo/hi - 输出下限/上限(V/A/Hz) float *
|
||||
|
||||
@@ -127,6 +127,7 @@ static void wave_rg_seal_reset(uint32_t a_now_ms, uint32_t a_frame_seq) {
|
||||
s_sealed.ch[i].avail = 0u;
|
||||
s_sealed.ch[i].k_q16 = 0;
|
||||
s_sealed.ch[i].b_codes = 0;
|
||||
s_sealed.ch[i].r_q16 = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,6 +344,7 @@ int32_t wave_ring_latch_meta(const wave_seal_cal_t *p_cal) {
|
||||
s_sealed.ch[i].avail = p_cal->ch[i].avail;
|
||||
s_sealed.ch[i].k_q16 = p_cal->ch[i].k_q16;
|
||||
s_sealed.ch[i].b_codes = p_cal->ch[i].b_codes;
|
||||
s_sealed.ch[i].r_q16 = p_cal->ch[i].r_q16;
|
||||
}
|
||||
s_meta_ready = 1u;
|
||||
return WAVE_ST_OK;
|
||||
@@ -425,7 +427,7 @@ int32_t wave_ring_sealed_meta(wave_seg_meta_t *p_out) {
|
||||
return WAVE_ST_E_STATE;
|
||||
}
|
||||
/* 逐字节拷出而不是 *p_out = s_sealed:结构体赋值在 ARMCC 上会调 __aeabi_memcpy,
|
||||
* 这里没必要为一个赋值多拉一层依赖,量也就 136 字节 */
|
||||
* 这里没必要为一个赋值多拉一层依赖,量也就 172 字节 */
|
||||
p_src = (const uint8_t *)&s_sealed;
|
||||
p_dst = (uint8_t *)p_out;
|
||||
for (i = 0u; i < (uint16_t)sizeof(wave_seg_meta_t); i++) {
|
||||
|
||||
+12
-9
@@ -8,7 +8,7 @@
|
||||
* 为准并回报 lead,不在代码里单方面「修正」。
|
||||
* 纯逻辑层纪律:不 include HAL / CMSIS / RTOS 头、不建任务、不打日志、不碰文件系统,
|
||||
* 也不链 App/algo/adc_cal —— 折算档位、有效性位图、削顶位图、epoch 秒与每路
|
||||
* k_q16 / b_codes 由 adcTask 在「刚封存那一拍」作为参数交棒(见 wave_ring_latch_meta
|
||||
* k_q16 / b_codes / r_q16 由 adcTask 在「刚封存那一拍」作为参数交棒(见 wave_ring_latch_meta
|
||||
* 与下面的「封存握手顺序」),本层只负责把它们随段头一起锁存。因此本层可被
|
||||
* 主机侧单测 test/host/wave_ring/ 与 test/host/wave_trig/ 原样纳入断言、零桩。
|
||||
* 与 sample_ring 的关系(为什么另起一个环):App/algo/sample_ring.c 是相量与保护的
|
||||
@@ -35,8 +35,8 @@
|
||||
* 1) adcTask 每拍 wave_ring_push() 然后 wave_ring_tick();
|
||||
* 2) tick 计满后触发点返回 WAVE_RING_EVT_SEALED —— 此刻状态进 WAVE_RING_ST_SEALED 但
|
||||
* **尚未就绪**,wave_ring_sealed() 仍返 0;
|
||||
* 3) adcTask 在**同一拍**现取封存瞬间事实(adc_cal_k_q16 / adc_cal_flags / sys_clock /
|
||||
* 削顶位图)并调 wave_ring_latch_meta() 一次 —— 只在第 2 步那一拍取一次,
|
||||
* 3) adcTask 在**同一拍**现取封存瞬间事实(adc_cal_k_q16 / adc_cal_ratio_q16 /
|
||||
* adc_cal_flags / sys_clock / 削顶位图)并调 wave_ring_latch_meta() 一次 —— 只在第 2 步那一拍取一次,
|
||||
* 既不每拍读 adc_cal(1 kHz 下 8 路白读),也不低频刷新(低频刷新等于快照可能
|
||||
* 来自标定之前的旧值,正是 §12「标定落地后老文件被解释错」的入口);
|
||||
* 4) 此后 wave_ring_sealed() 才返 1,defaultTask 才准开文件落盘;meta 未就绪时
|
||||
@@ -161,10 +161,13 @@ typedef struct {
|
||||
uint8_t flags_seq; /* bit s = 语义通道 s 的 ADC_CAL_F_VALID(§6.3) */
|
||||
uint16_t clip_mask; /* bit s = 语义通道 s 的 ADC_CAL_F_CLIPPED(V0.1 恒 0xE6) */
|
||||
uint32_t wall; /* 对时后的 epoch 秒(未对时 0;未对时就写 0,不许拿上电毫秒冒充) */
|
||||
wave_ch_desc_t ch[WAVE_ST_CH_MAX]; /* 通道表前 8 项的 avail / k_q16 / b_codes:
|
||||
必须取 adc_cal_k_q16() 的 int32 原值,
|
||||
不许经 adc_cal_k_uv() 的 float 回算(§6.3:
|
||||
float 在 5.28e7 量级掉尾数);
|
||||
wave_ch_desc_t ch[WAVE_ST_CH_MAX]; /* 通道表前 8 项的 avail / k_q16 / b_codes / r_q16:
|
||||
k 与 r **必须取 int32 原值入口**
|
||||
(adc_cal_k_q16 / adc_cal_ratio_q16)——
|
||||
不许经 adc_cal_k_uv() / adc_cal_ratio() 的 float
|
||||
回算(§6.3:float 在 5.28e7 / 6.5e7 量级掉尾数);
|
||||
b 的 float 回乘在本域无损(|b| ≤ 8192 << 2^24,
|
||||
float32 精确表示该范围整数);
|
||||
conv 与 bits 也在这里给(本层无从知道两片
|
||||
转换器的归属);sem 由本层按「表内语义号 == 下标」
|
||||
自己钉,传进来的 sem 被忽略 */
|
||||
@@ -275,7 +278,7 @@ int32_t wave_ring_tick(uint32_t a_now_ms, uint32_t a_frame_seq);
|
||||
/*
|
||||
* 函数功能:封存交棒 —— 把「环已知字段 + adcTask 现取的封存瞬间事实」拼成完整段头并锁存
|
||||
* 入口参数:p_cal 封存瞬间事实(折算档位 / 有效性位图 / 削顶位图 / epoch 秒 / 通道表的
|
||||
* conv、bits、avail、k_q16、b_codes;不可为 NULL)
|
||||
* conv、bits、avail、k_q16、b_codes、r_q16;不可为 NULL)
|
||||
* 返 回 值:WAVE_ST_OK = 已锁存,wave_ring_sealed() 从此为 1;
|
||||
* WAVE_ST_E_STATE = 不在「刚封段待交棒」态(含已 latch 过第二次、非 SEALED);
|
||||
* WAVE_ST_E_PARAM = 空指针或折算档位非法 int32_t
|
||||
@@ -333,7 +336,7 @@ uint8_t wave_ring_sealed(void);
|
||||
|
||||
/*
|
||||
* 函数功能:取已封存段的段头快照(落盘道编头、回放页画标题都用它)
|
||||
* 入口参数:p_out 输出(整份拷出,约 136 字节;不可为 NULL)
|
||||
* 入口参数:p_out 输出(整份拷出,约 172 字节;不可为 NULL)
|
||||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针;WAVE_ST_E_STATE 无就绪段 int32_t
|
||||
* 限定条件:任意上下文(defaultTask / uiTask 只读)。整份拷贝、不暴露内部指针,
|
||||
* 免得调用方攥着指针跨到下一段去看
|
||||
|
||||
+103
-23
@@ -9,6 +9,9 @@
|
||||
* 创建日期:2026-10-29(故障录波 task-1 · 数据面纯逻辑层)
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-29 DTU 项目组 创建
|
||||
* v2.0 2026-11-11 DTU 项目组 格式升 v2:通道表加 int32 r_q16(变比 Q16,一 路 12 -> 16 字节),
|
||||
* 整头 128 -> 160、整段 5250 -> 5282;wave_store_code_to_milli
|
||||
* 输出改为「录波域毫单位」(余数分解换等价单次圆整)
|
||||
*/
|
||||
|
||||
#include "wave_store.h"
|
||||
@@ -83,6 +86,24 @@ static void wave_st_put_code(uint8_t *p, int16_t v) {
|
||||
wave_st_put16(p, (uint16_t)v);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:int64 定点除法并圆整(远离零方向 + 半量进位)
|
||||
* 入口参数:a_num 分子(有符号)
|
||||
* a_den 分母(必须是正数)
|
||||
* 返 回 值:圆整后的商
|
||||
* 限定条件:a_den > 0;|a_num| 与结果都须落在 int64 内
|
||||
* 函数说明:录波折算的三处分母(1000 / 65536 / 65536×65536)共用这一把尺子,
|
||||
* 免得三处各写一遍「负数的向零截断」这种容易写反的分支。
|
||||
* 为什么要「远离零方向」:向零截断会让负半周比正半周小一个 LSB,
|
||||
* 波形出现偶次谐波假象——这正是 §8.1 忌讳的「画出不存在的东西」。
|
||||
*/
|
||||
static int64_t wave_st_div_round(int64_t a_num, int64_t a_den) {
|
||||
if (a_num >= 0) {
|
||||
return (a_num + (a_den / 2)) / a_den;
|
||||
}
|
||||
return -(((-a_num) + (a_den / 2)) / a_den);
|
||||
}
|
||||
|
||||
/* ===== 内部:段头字段一致性自检(编码前与解码后各跑一次,同一把尺子) ===== */
|
||||
|
||||
/*
|
||||
@@ -239,11 +260,13 @@ static const uint8_t s_sem_bits[(uint8_t)WAVE_ST_CH_MAX] = {
|
||||
* docs/硬件平台/ADC通道映射说明.md 的抄本,本层刻意不带 adc_input.h,
|
||||
* 这样格式层在主机侧不需要任何假件就能编译;单测里 0xE6 的算术交叉验证
|
||||
* 会同时钉住这套顺序,抄错就会红)。
|
||||
* 不填 avail / k_q16 / b_codes:那些是封存瞬间的事实,必须由调用方现取现填
|
||||
* (adc_cal_k_q16 / adc_cal_flags / sys_clock),本层不链它们;
|
||||
* 不填 avail / k_q16 / b_codes / r_q16:那些是封存瞬间的事实,必须由调用方现取现填
|
||||
* (adc_cal_k_q16 / adc_cal_ratio_q16 / adc_cal_flags / sys_clock),本层不链它们;
|
||||
* 调用方忘填 k_q16 与 avail 不会让编码失败(0 是合法整数),
|
||||
* 但会让 PC 侧把波形解释成一条 0 线——所以单测里有一条专门检查
|
||||
* 「填了系数的往返」与「没填系数时段的形状仍然正确、只是幅值归零」。
|
||||
* r_q16 = 0 是**合法值**(本路不折算),忘填的后果只是量纲停在引脚域,
|
||||
* 与「刻意不折算」在文件里长得一样——这是设计取舍,不是缺陷。
|
||||
*/
|
||||
int wave_store_meta_init(wave_seg_meta_t *p_meta) {
|
||||
uint8_t i;
|
||||
@@ -271,6 +294,7 @@ int wave_store_meta_init(wave_seg_meta_t *p_meta) {
|
||||
p_meta->ch[i].avail = 0u;
|
||||
p_meta->ch[i].k_q16 = 0;
|
||||
p_meta->ch[i].b_codes = 0;
|
||||
p_meta->ch[i].r_q16 = 0;
|
||||
}
|
||||
return WAVE_ST_OK;
|
||||
}
|
||||
@@ -315,6 +339,8 @@ static uint32_t wave_st_head_raw(const wave_seg_meta_t *p_meta, uint8_t *p_out)
|
||||
* 保证板端 ARMCC 与主机 CRT 写出同一串字节 */
|
||||
wave_st_put32(&p_out[pos + WAVE_ST_CH_OFF_KQ16], (uint32_t)p_meta->ch[i].k_q16);
|
||||
wave_st_put32(&p_out[pos + WAVE_ST_CH_OFF_BCODE], (uint32_t)p_meta->ch[i].b_codes);
|
||||
/* v2:变比 Q16 原值(0 = 本路不折算)。同样按 int32 小端原样落 4 字节 */
|
||||
wave_st_put32(&p_out[pos + WAVE_ST_CH_OFF_RQ16], (uint32_t)p_meta->ch[i].r_q16);
|
||||
}
|
||||
return (uint32_t)WAVE_ST_OFF_TBL + tbl_n;
|
||||
}
|
||||
@@ -324,7 +350,7 @@ static uint32_t wave_st_head_raw(const wave_seg_meta_t *p_meta, uint8_t *p_out)
|
||||
* 入口参数:p_meta 段头元数据;p_out 输出缓冲;a_cap 容量
|
||||
* 返 回 值:>= 0 写入字节数;负 = WAVE_ST_E_* int
|
||||
* 限定条件:任意上下文;无静态缓冲、可重入
|
||||
* 函数说明:头 CRC 覆盖 [0, 30+12×ch_count)、自身紧随其后、低字节在前(§6.3)。
|
||||
* 函数说明:头 CRC 覆盖 [0, 30+16×ch_count)、自身紧随其后、低字节在前(§6.3)。
|
||||
*/
|
||||
int wave_store_head_build(const wave_seg_meta_t *p_meta, uint8_t *p_out, uint32_t a_cap) {
|
||||
uint32_t crc_pos;
|
||||
@@ -419,6 +445,7 @@ int wave_store_head_parse(const uint8_t *p_buf, uint32_t a_len, wave_seg_meta_t
|
||||
tmp.ch[ch_count].avail = 0u;
|
||||
tmp.ch[ch_count].k_q16 = 0;
|
||||
tmp.ch[ch_count].b_codes = 0;
|
||||
tmp.ch[ch_count].r_q16 = 0;
|
||||
}
|
||||
for (ch_count = 0u; ch_count < p_buf[WAVE_ST_OFF_CH_CNT]; ch_count++) {
|
||||
uint32_t pos = (uint32_t)WAVE_ST_OFF_TBL + ((uint32_t)ch_count * WAVE_ST_CH_BYTES);
|
||||
@@ -428,6 +455,7 @@ int wave_store_head_parse(const uint8_t *p_buf, uint32_t a_len, wave_seg_meta_t
|
||||
tmp.ch[ch_count].avail = p_buf[pos + WAVE_ST_CH_OFF_AVAIL];
|
||||
tmp.ch[ch_count].k_q16 = (int32_t)wave_st_get32(&p_buf[pos + WAVE_ST_CH_OFF_KQ16]);
|
||||
tmp.ch[ch_count].b_codes = (int32_t)wave_st_get32(&p_buf[pos + WAVE_ST_CH_OFF_BCODE]);
|
||||
tmp.ch[ch_count].r_q16 = (int32_t)wave_st_get32(&p_buf[pos + WAVE_ST_CH_OFF_RQ16]);
|
||||
}
|
||||
rc = wave_st_meta_check(&tmp);
|
||||
if (rc != WAVE_ST_OK) {
|
||||
@@ -457,8 +485,8 @@ uint32_t wave_store_head_bytes(uint8_t a_ch_count) {
|
||||
* 入口参数:p_meta 段头
|
||||
* 返 回 值:期望字节数;字段非法返回 0 uint32_t
|
||||
* 限定条件:任意上下文
|
||||
* 函数说明:等式 = 32 + 12×ch_count + pts_per_ch×ch_count×2 + 2(§6.3;
|
||||
* 8 路 320 点 = 5250)。落盘道拿它比对 f_write 写回字节数与 f_size。
|
||||
* 函数说明:等式 = 32 + 16×ch_count + pts_per_ch×ch_count×2 + 2(§6.3;
|
||||
* 8 路 320 点 = 5282)。落盘道拿它比对 f_write 写回字节数与 f_size。
|
||||
*/
|
||||
uint32_t wave_store_seg_size(const wave_seg_meta_t *p_meta) {
|
||||
if (p_meta == NULL) {
|
||||
@@ -547,30 +575,82 @@ int wave_store_le_decode(const uint8_t *p_raw, uint32_t a_len,
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:码值 → 定点毫单位(mV)
|
||||
* 入口参数:p_desc 通道表项(必须来自段文件头);a_code 码值;p_milli 输出 mV
|
||||
* 函数功能:码值 → 定点毫单位(录波域:二次 mV/mA 或引脚 mV)
|
||||
* 入口参数:p_desc 通道表项(必须来自段文件头);a_code 码值;p_milli 输出毫单位
|
||||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针 int
|
||||
* 限定条件:任意上下文(只在回放页与单测用,不在 1 kHz 采集链上;用 int64 中间量)
|
||||
* 函数说明:模型 eng = (code + b) × k × 1e-6(§6.3,与 adc_cal_apply() 同式),
|
||||
* k = k_q16 / 65536 µV/码 → mV = (code + b) × k_q16 / 65536000,四舍五入。
|
||||
* 中间量最大约 (32767 + 8192) × 5.28e7 ≈ 2.2e12,int64 宽裕。
|
||||
* 限定条件:任意上下文(只在回放页与单测用,不在 1 kHz 采集链上;全程 int64)
|
||||
* 函数说明:模型 eng = (code + b) × k × 1e-6 × r(§6.3,与 adc_cal_apply() 同式)。
|
||||
* 目标量(Q16 全定点,**只在最后圆整一次**):
|
||||
* t = (code + b) × k_q16 // µV × 65536
|
||||
* r_q16 = 0 → m = 圆整(t / 65536000) // 引脚域 mV,与 v1 同式
|
||||
* r_q16 ≠ 0 → m = 圆整(t × r_q16 / (65536000 × 65536))
|
||||
*
|
||||
* 两个坑,都必须显式躲开:
|
||||
* ① 不能一次乘完:t ≤ 4.3e12、r_q16 ≤ 2.147e9,乘积 9e21 溢出 int64;
|
||||
* ② 不能「先圆整成整数 mV,再乘变比」:中间那次圆整会让 r_q16 = 65536
|
||||
* (比值 1.0,即 v1 的引脚域口径)的输出差 ±1 LSB,而「r = 1.0 必须与
|
||||
* 旧口径逐位一致」正是两份独立实现对齐的锚点(PC 解码器与单测都靠它)。
|
||||
* 故用**余数分解**换出无溢出的等价单次圆整:
|
||||
* 令 t = q × 65536000 + rem(rem ∈ [0, 65536000)),
|
||||
* 则 t/65536000 = q + rem/65536000,
|
||||
* q×r/65536 = P + rem2/65536(P = 商、rem2 ∈ [0, 65536)),
|
||||
* 合到同一分母 den = 65536000 × 65536 后一次圆整:
|
||||
* m = P + (rem2 × 65536000 + rem × r + den/2) / den
|
||||
* 各项上界:q ≤ 1.3e5、q×r ≤ 2.8e14;rem2×65536000 ≤ 4.3e12;
|
||||
* rem×r ≤ 1.4e17 —— 全在 int64(9.2e18)以内。
|
||||
* 圆整 = 远离零方向 + 半量进位(先取绝对值再补符号,等价于对零对称)。
|
||||
* ⚠ 防御:合法头里 |b| ≤ 8192、k ≤ 2 × ROM = 1.056e8,故 |t| ≤ 4.3e12;
|
||||
* CRC 通过但字段是垃圾的头也能进来,故对 |t| 与输出各设一道饱和闸,
|
||||
* 宁可屏上饱和也不要在 int64 里溢出(C 的溢出是 UB,不是「结果偏大」)。
|
||||
*/
|
||||
int wave_store_code_to_milli(const wave_ch_desc_t *p_desc, int16_t a_code,
|
||||
int32_t *p_milli) {
|
||||
int64_t num;
|
||||
int64_t den = 65536000;
|
||||
int64_t v;
|
||||
const int64_t den_k = 65536000; /* 1000 × 2^16 */
|
||||
const int64_t den_m = 65536000LL * 65536LL; /* 1000 × 2^32 */
|
||||
int64_t t;
|
||||
int64_t r;
|
||||
int64_t q;
|
||||
int64_t rem;
|
||||
int64_t p;
|
||||
int64_t rem2;
|
||||
int64_t qr;
|
||||
int64_t m;
|
||||
int neg;
|
||||
|
||||
if ((p_desc == NULL) || (p_milli == NULL)) {
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
num = ((int64_t)a_code + (int64_t)p_desc->b_codes) * (int64_t)p_desc->k_q16;
|
||||
if (num >= 0) {
|
||||
v = (num + (den / 2)) / den;
|
||||
} else {
|
||||
v = -(((-num) + (den / 2)) / den);
|
||||
t = ((int64_t)a_code + (int64_t)p_desc->b_codes) * (int64_t)p_desc->k_q16;
|
||||
r = (int64_t)p_desc->r_q16;
|
||||
if (r == 0) {
|
||||
/* 本路不折算:引脚域整数 mV —— 与 v1 逐位一致(单次圆整) */
|
||||
*p_milli = (int32_t)wave_st_div_round(t, den_k);
|
||||
return WAVE_ST_OK;
|
||||
}
|
||||
*p_milli = (int32_t)v;
|
||||
/* 绝对值域分解:圆整对零对称,最后补回符号 */
|
||||
neg = ((t < 0) != (r < 0)) ? 1 : 0;
|
||||
if (t < 0) {
|
||||
t = -t;
|
||||
}
|
||||
if (r < 0) {
|
||||
r = -r;
|
||||
}
|
||||
if (t > 8000000000000LL) {
|
||||
/* 字段是垃圾(合法上限约 4.3e12):饱和而不溢出 */
|
||||
*p_milli = (neg != 0) ? (-2147483647L - 1) : 2147483647L;
|
||||
return WAVE_ST_OK;
|
||||
}
|
||||
q = t / den_k;
|
||||
rem = t % den_k;
|
||||
qr = q * r;
|
||||
p = qr / 65536;
|
||||
rem2 = qr % 65536;
|
||||
/* 一次圆整:分子 = 两段余数并到 den_m 定标 + 半量 */
|
||||
m = p + ((rem2 * den_k) + (rem * r) + (den_m / 2)) / den_m;
|
||||
if (m > 2147483647LL) {
|
||||
m = 2147483647LL;
|
||||
}
|
||||
*p_milli = (int32_t)((neg != 0) ? -m : m);
|
||||
return WAVE_ST_OK;
|
||||
}
|
||||
|
||||
@@ -580,7 +660,7 @@ int wave_store_code_to_milli(const wave_ch_desc_t *p_desc, int16_t a_code,
|
||||
* 函数功能:整段镜像自检(头 + 数据区 + 数据 CRC)
|
||||
* 入口参数:p_img 镜像;a_len 长度;p_meta 输出段头(可空)
|
||||
* 返 回 值:WAVE_ST_OK 或 WAVE_ST_E_*(见 wave_store.h) int
|
||||
* 限定条件:任意上下文;整段驻留时用(本波 5250 字节)
|
||||
* 限定条件:任意上下文;整段驻留时用(本波 5282 字节)
|
||||
* 函数说明:三条判废一条都不能省:长度 0 或不等、头 CRC、数据 CRC。
|
||||
* 期望总长用**头里的字段**算,不用编译期常量——文件是自己说它多长,
|
||||
* 与镜像实际长度不一致就是坏段(§6.2 末行)。
|
||||
@@ -692,7 +772,7 @@ int wave_store_pack_next(wave_pack_ctx_t *p_ctx, uint8_t *p_out,
|
||||
return WAVE_ST_DONE;
|
||||
}
|
||||
|
||||
/* --- 头:整头一次产完(接线索按 §7 给 256 字节缓冲,足够 128 字节整头) --- */
|
||||
/* --- 头:整头一次产完(接线索按 §7 给 256 字节缓冲,足够 160 字节整头) --- */
|
||||
if (p_ctx->phase == (uint8_t)WAVE_PACK_HEAD) {
|
||||
uint32_t head_sz;
|
||||
int rc;
|
||||
@@ -702,7 +782,7 @@ int wave_store_pack_next(wave_pack_ctx_t *p_ctx, uint8_t *p_out,
|
||||
return WAVE_ST_E_SIZE;
|
||||
}
|
||||
if (a_cap < head_sz) {
|
||||
/* 不拆着头给:半截头意味着游标要存 128 字节中间态,纯浪费 */
|
||||
/* 不拆着头给:半截头意味着游标要存 160 字节中间态,纯浪费 */
|
||||
return WAVE_ST_E_PARAM;
|
||||
}
|
||||
rc = wave_store_head_build(&p_ctx->meta, p_out, a_cap);
|
||||
|
||||
+45
-25
@@ -11,15 +11,16 @@
|
||||
* 职责边界(为什么这样切):
|
||||
* 1) 固定头 + 通道表编解码、头 CRC 与数据区 CRC 自检 —— 格式知识只此一处;
|
||||
* 2) 打包游标(分次搬运):文件系统唯一写者只要「每次给我不超过 256 字节的下一片」,
|
||||
* 整段 5250 字节不驻留 RAM(设计说明 §7:搬运缓冲 256 B × 2,不做整段驻留);
|
||||
* 整段 5282 字节不驻留 RAM(设计说明 §7:搬运缓冲 256 B × 2,不做整段驻留);
|
||||
* 3) 段文件名轮转与反解:只算名字,不打开文件(FatFs FF_USE_LFN=0 只能严格
|
||||
* 8.3 短名,见 docs/架构设计/STM32F4-Base存储架构说明.md §10.1);
|
||||
* 4) 数据区偏移换算 + 小端解码:回放页只画一路(设计说明 §8.1),按通道分列
|
||||
* 让「只读那一路」成为一次连续读。
|
||||
* 不链 App/algo/adc_cal(零依赖 = 主机免桩可测):通道表里的 k_q16 / b_codes /
|
||||
* 不链 App/algo/adc_cal(零依赖 = 主机免桩可测):通道表里的 k_q16 / b_codes / r_q16 /
|
||||
* 可用性由调用方在封存瞬间取 int32 原值填进 meta 后交给本层编码。系数必须走
|
||||
* adc_cal_k_q16()(接线道补的 int32 直读入口),不得经 adc_cal_k_uv() 的 float
|
||||
* 通道回算 —— float 在 5.28e7 量级掉尾数(设计说明 §6.3「k / b 宽度与角色」行)。
|
||||
* adc_cal_k_q16() / adc_cal_ratio_q16()(接线道补的 int32 直读入口),不得经
|
||||
* adc_cal_k_uv() / adc_cal_ratio() 的 float 通道回算 —— float 在 5.28e7 量级掉尾数
|
||||
* (设计说明 §6.3「k / b 宽度与角色」行)。
|
||||
* CRC 一张表的原始说法不成立,lead 已在 §6.3 记录实测依据:全仓无可复用的
|
||||
* CRC-16 表(App/util/README.md 记 crc.c 与 crc.h 已于 2026-10-02 删除;屏板链路
|
||||
* Drivers/BSP/UI/ui_net_sink.c 的 remo_crc() 是单字节异或、不是 CRC-16)。
|
||||
@@ -30,6 +31,9 @@
|
||||
* 修改记录:
|
||||
* v1.0 2026-10-29 DTU 项目组 创建:段头/通道表编解码、CRC-16/MODBUS 无表实现、
|
||||
* 打包游标、段名轮转与反解、数据区偏移换算与小端解码
|
||||
* v2.0 2026-11-11 DTU 项目组 格式升 v2:通道表加 int32 r_q16(变比 Q16,12 -> 16 字节/路),
|
||||
* 整头 128 -> 160、整段 5250 -> 5282;折算口径加 ×r/65536
|
||||
* (只在最后圆整一次:一次乘完溢出 int64)。理由见 adc_cal.h v2.0
|
||||
*
|
||||
* 二期不返工的结构约束(设计说明 §2.1,改本层前先读):
|
||||
* 1) 采样率与每周波点数是写进文件头的数据,不是编译期假设 —— 回放端与 PC 脚本一律
|
||||
@@ -55,8 +59,10 @@ extern "C" {
|
||||
/* 魔数前两字节固定 'W' 与 'R',第三字节 = 版本号(设计说明 §6.2 偏移 0 行) */
|
||||
#define WAVE_ST_MAGIC0 (0x57u) /* 'W' */
|
||||
#define WAVE_ST_MAGIC1 (0x52u) /* 'R' */
|
||||
/* 版本:本波恒 1。ver 字段必须同时等于魔数第三字节与本宏,否则整段判废(§6.2 末行) */
|
||||
#define WAVE_ST_VER (1u)
|
||||
/* 版本:本波恒 2。ver 字段必须同时等于魔数第三字节与本宏,否则整段判废(§6.2 末行)。
|
||||
* v1 -> v2 的理由:通道表加 int32 变比 r_q16(一路 12 -> 16 字节),字节布局变了必须升版本,
|
||||
* 否则 v1 文件会被按 v2 布局错读(老文件一律判「版本不认识」,不做兼容解码)。 */
|
||||
#define WAVE_ST_VER (2u)
|
||||
|
||||
/* 固定头字段偏移(字节,小端) */
|
||||
#define WAVE_ST_OFF_MAGIC (0u) /* 3 字节:'W' 'R' ver */
|
||||
@@ -76,15 +82,20 @@ extern "C" {
|
||||
#define WAVE_ST_OFF_SEQ (26u) /* 4 字节 u32 */
|
||||
#define WAVE_ST_OFF_TBL (30u) /* 通道表起点 */
|
||||
|
||||
/* 通道表一路 12 字节 = 语义号 1 + 来源转换器 1 + 位宽 1 + 可用性 1 + k_q16 4 + b_codes 4
|
||||
* 设计说明 §6.3「LSB 从哪来」:不设独立 LSB 字段,k_q16 就是 LSB(单位 µV/码 × 65536) */
|
||||
/* 通道表一路 16 字节 = 语义号 1 + 来源转换器 1 + 位宽 1 + 可用性 1 + k_q16 4 + b_codes 4
|
||||
* + r_q16 4(v2 新增)
|
||||
* 设计说明 §6.3「LSB 从哪来」:不设独立 LSB 字段,k_q16 就是 LSB(单位 µV/码 × 65536)
|
||||
* 为什么 r 单独一列而不是折进 k:k 是板级前端斜率(ROM 固化),r 是端子变比(现场标定),
|
||||
* 两者量级差几个数量级——折进去后端子域的 k = 152587.9 µV/码,Q16 达 1.0e10,超出 int32
|
||||
* 上限 2.147e9,根本存不下;分开存 r(1000.0 → 65536000)完全够(见 App/algo/adc_cal.h)。 */
|
||||
#define WAVE_ST_CH_OFF_SEM (0u) /* 1 字节 */
|
||||
#define WAVE_ST_CH_OFF_CONV (1u) /* 1 字节 */
|
||||
#define WAVE_ST_CH_OFF_BITS (2u) /* 1 字节 */
|
||||
#define WAVE_ST_CH_OFF_AVAIL (3u) /* 1 字节 = adc_cal_flags(sem) 低 8 位原值,不自造位义 */
|
||||
#define WAVE_ST_CH_OFF_KQ16 (4u) /* 4 字节 int32 */
|
||||
#define WAVE_ST_CH_OFF_BCODE (8u) /* 4 字节 int32 */
|
||||
#define WAVE_ST_CH_BYTES (12u)
|
||||
#define WAVE_ST_CH_OFF_RQ16 (12u) /* 4 字节 int32:变比 Q16(比值 × 65536);0 = 本路不折算 */
|
||||
#define WAVE_ST_CH_BYTES (16u)
|
||||
|
||||
/* 本波通道数固定 8(TEMP 不录:理由见 §6.3 末段与 docs/算法/采集与算法层设计说明.md §13.3) */
|
||||
#define WAVE_ST_CH_NUM (8u)
|
||||
@@ -97,8 +108,8 @@ extern "C" {
|
||||
#define WAVE_ST_PPC_DEF (20u)
|
||||
|
||||
/* 尺寸一律由字段算出,不写死字面量(改通道数/点数时自动跟着走):
|
||||
* 头 CRC 覆盖区 = 30 + 12 × ch_count;头 CRC 自身紧随其后 2 字节;数据区自 32 + 12 × ch 起
|
||||
* ch_count = 8 时:覆盖区 126、整头 128、数据区 5120、整段 5250(§6.3「CRC 覆盖范围」行) */
|
||||
* 头 CRC 覆盖区 = 30 + 16 × ch_count;头 CRC 自身紧随其后 2 字节;数据区自 32 + 16 × ch 起
|
||||
* ch_count = 8 时:覆盖区 158、整头 160、数据区 5120、整段 5282(§6.3「CRC 覆盖范围」行) */
|
||||
#define WAVE_ST_HEAD_CRC_POS(ch) ((uint32_t)(WAVE_ST_OFF_TBL) + \
|
||||
((uint32_t)(ch) * WAVE_ST_CH_BYTES))
|
||||
#define WAVE_ST_HEAD_BYTES(ch) ((uint32_t)(WAVE_ST_HEAD_CRC_POS(ch)) + 2u)
|
||||
@@ -106,8 +117,8 @@ extern "C" {
|
||||
#define WAVE_ST_SEG_TOTAL(ch, pts) (WAVE_ST_HEAD_BYTES(ch) + \
|
||||
WAVE_ST_DATA_BYTES(ch, pts) + 2u)
|
||||
/* 本波实际值(只给注释与单测引用,代码里一律用上面的参数化形态) */
|
||||
#define WAVE_ST_HEAD_SZ_CUR (128u)
|
||||
#define WAVE_ST_SEG_SZ_CUR (5250u)
|
||||
#define WAVE_ST_HEAD_SZ_CUR (160u)
|
||||
#define WAVE_ST_SEG_SZ_CUR (5282u)
|
||||
|
||||
/* 段目录与轮转(设计说明 §5.2 第三条:只能严格 8.3 短名;目录与 /log 平级、FTP 根下可见)
|
||||
* 名字形态 = 目录 + 前缀 + 两位十进制 + 扩展名,号 0..99 共 100 个轮转覆盖 */
|
||||
@@ -161,6 +172,7 @@ typedef struct {
|
||||
uint8_t avail; /* 可用性 = adc_cal_flags(sem) 低 8 位原值(不自造位义) */
|
||||
int32_t k_q16; /* 封存瞬间生效系数:µV/码 × 65536,就是 LSB */
|
||||
int32_t b_codes; /* 封存瞬间生效零点偏移:码值 */
|
||||
int32_t r_q16; /* 封存瞬间生效变比:比值 × 65536;0 = 本路不折算(引脚域) */
|
||||
} wave_ch_desc_t;
|
||||
|
||||
/* 段头元数据 = 固定头 + 通道表的内存形态。
|
||||
@@ -270,9 +282,10 @@ int wave_store_crc_selftest(void);
|
||||
* (UA / UN / IA 走 TPAFE 16 位;UB / UC / IB / IC / IN 走片内 ADC 12 位 ——
|
||||
* 通道归属口径属主是 docs/硬件平台/ADC通道映射说明.md,本函数只是它的抄本,
|
||||
* 单测里对 0xE6 的算术交叉验证会同时钉住这套顺序)。
|
||||
* 不填 avail / k_q16 / b_codes / trig_* / t0_ms / wall / eng / flags_seq /
|
||||
* 不填 avail / k_q16 / b_codes / r_q16 / trig_* / t0_ms / wall / eng / flags_seq /
|
||||
* clip_mask / frame_seq:那些是封存瞬间的事实,必须由调用方现取现填
|
||||
* (adc_cal_k_q16 / adc_cal_flags / sys_clock / wave_ring),本层不链它们。
|
||||
* (adc_cal_k_q16 / adc_cal_ratio_q16 / adc_cal_flags / sys_clock / wave_ring),
|
||||
* 本层不链它们。
|
||||
*/
|
||||
int wave_store_meta_init(wave_seg_meta_t *p_meta);
|
||||
|
||||
@@ -313,7 +326,7 @@ int wave_store_head_parse(const uint8_t *p_buf, uint32_t a_len, wave_seg_meta_t
|
||||
* p_meta 输出段头(可为 NULL)
|
||||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针;WAVE_ST_E_SIZE 长度为 0 或与期望总长不符;
|
||||
* WAVE_ST_E_CRC 任一 CRC 不符;WAVE_ST_E_FORMAT 头本身判废 int
|
||||
* 限定条件:任意上下文;只在整段驻留(本波 5250 字节)时用。回放页不要走这条 ——
|
||||
* 限定条件:任意上下文;只在整段驻留(本波 5282 字节)时用。回放页不要走这条 ——
|
||||
* 用 wave_store_head_parse + wave_store_data_off + wave_store_crc_update 分段查,
|
||||
* 才符合 §7 的 256 字节读窗预算
|
||||
* 函数说明:这是「段文件 CRC 不符时脚本必须拒绝而不是画出东西」那条判据在板侧的同一份
|
||||
@@ -324,7 +337,7 @@ int wave_store_seg_check(const uint8_t *p_img, uint32_t a_len, wave_seg_meta_t *
|
||||
/*
|
||||
* 函数功能:按头部字段算整段文件应有的字节数(写侧记账与读侧长度判废共用)
|
||||
* 入口参数:p_meta 段头元数据
|
||||
* 返 回 值:期望字节数(8 路 320 点 = 5250);字段非法(含零长度)返回 0 uint32_t
|
||||
* 返 回 值:期望字节数(8 路 320 点 = 5282);字段非法(含零长度)返回 0 uint32_t
|
||||
* 限定条件:任意上下文
|
||||
* 函数说明:返回 0 就是「这一段连长度都不成立」,据此判废。不要拿 0 去开缓冲,
|
||||
* 也不要拿 0 去比对 f_write 的写回字节数(那会把失败读成成功)。
|
||||
@@ -381,16 +394,23 @@ int wave_store_le_decode(const uint8_t *p_raw, uint32_t a_len,
|
||||
|
||||
/*
|
||||
* 函数功能:把一路的 int16 码值折成定点毫单位(回放页峰值标注用;纯整数,不用 float)
|
||||
* 入口参数:p_desc 该路通道表项(k_q16 / b_codes 来自段文件头,不来自当前固件)
|
||||
* 入口参数:p_desc 该路通道表项(k_q16 / b_codes / r_q16 来自段文件头,不来自当前固件)
|
||||
* a_code 原始码值
|
||||
* p_milli 输出四舍五入后的毫单位(k_q16 是 µV/码 → 输出单位 mV)(不可为 NULL)
|
||||
* p_milli 输出四舍五入后的毫单位(**录波域**:eng=1 时是二次 mV/mA,
|
||||
* eng=0 时是引脚 mV)(不可为 NULL)
|
||||
* 返 回 值:WAVE_ST_OK;WAVE_ST_E_PARAM 空指针 int
|
||||
* 限定条件:任意上下文(只在回放页与单测里用,不在 1 kHz 采集链上;内部用 int64 中间量,
|
||||
* 落在这条路径上时可接受)
|
||||
* 函数说明:算式 eng = (code + b) × k × 1e-6,与 App/algo/adc_cal.c 的 adc_cal_apply()
|
||||
* 同一模型(§6.3)。输出单位是 mV;单位含义由段头 eng 字段决定(eng = 0 是
|
||||
* 引脚域,别读成二次值)。必须用段文件里的 k / b,不许拿当前固件的系数解释
|
||||
* 老文件(§12 风险表「标定落地后老文件被解释错」)。
|
||||
* 函数说明:算式 eng = (code + b) × k × 1e-6 × r,与 App/algo/adc_cal.c 的 adc_cal_apply()
|
||||
* 同一模型(§6.3);r 以 Q16 存,折算即再乘 r_q16 / 65536。
|
||||
* 输出单位是「录波域毫单位」:段头 eng 决定这毫单位指什么(eng = 1 二次值,
|
||||
* eng = 0 引脚电压),本函数只按 r_q16 是否非零给出对应域,不猜单位。
|
||||
* **只在最后圆整一次**:一次乘完会溢出 int64(分子到 9e21),而「先圆整成整数
|
||||
* mV 再乘变比」又会让 r_q16 = 65536(比值 1.0)差 ±1 LSB —— 后者是两份实现
|
||||
* 对齐的锚点,不能动。实现用余数分解换等价单次圆整(分解式与各上界见
|
||||
* App/algo/wave_store.c 的 wave_store_code_to_milli 注释)。
|
||||
* 必须用段文件里的 k / b / r,不许拿当前固件的系数解释老文件
|
||||
* (§12 风险表「标定落地后老文件被解释错」)。
|
||||
*/
|
||||
int wave_store_code_to_milli(const wave_ch_desc_t *p_desc, int16_t a_code,
|
||||
int32_t *p_milli);
|
||||
@@ -423,8 +443,8 @@ int wave_store_pack_start(wave_pack_ctx_t *p_ctx, const wave_seg_meta_t *p_meta,
|
||||
* 负 = WAVE_ST_E_*(未 start / 容量为 0 / 数据源回调返错 / 游标状态错) int
|
||||
* 限定条件:任意上下文,但同一游标不得并发使用(文件系统唯一写者单线程推进)
|
||||
* 函数说明:头区字节流与 wave_store_head_build 完全一致(内部同一份写字段函数);
|
||||
* **头那一步要求 a_cap 不小于整头字节数**(本波 8 路 = 128),否则返回
|
||||
* WAVE_ST_E_PARAM 且不推进游标——不拆着头给,是为了让游标不必存 128 字节
|
||||
* **头那一步要求 a_cap 不小于整头字节数**(本波 8 路 = 160),否则返回
|
||||
* WAVE_ST_E_PARAM 且不推进游标——不拆着头给,是为了让游标不必存 160 字节
|
||||
* 中间态;§7 的 256 字节搬运缓冲天然满足。
|
||||
* 数据区 CRC 走增量中间值,整段最后两片是数据 CRC 的低字节、高字节。
|
||||
* 调用方每写完一片都必须比对 f_write 写回字节数,短写按失败处理(§5.2)。
|
||||
|
||||
+7
-5
@@ -26,7 +26,7 @@
|
||||
#include "gd5f2gq5ue.h"
|
||||
#include "tpafe5160.h"
|
||||
#include "adc_input.h" /* 语义取数与有效性层:adc_input_init()(语义映射 + STM32 ADC1 扫描) */
|
||||
#include "adc_cal.h" /* 标定折算层:adc_cal_init()(ROM 系数 + cfg 两段注册,道A 标定折算与落盘整定) */
|
||||
#include "adc_cal.h" /* 标定折算层:adc_cal_init()(ROM 系数 + cfg 三段注册,道A 标定折算与落盘整定) */
|
||||
#include "run_par.h" /* 运行参数段:run_par_init()([run_par] 注册 + 104 地址映射真源,道D 这一项) */
|
||||
#include "iec104_comm.h" /* [comm] 段:iec104_comm_init()(ca/端口注册,道G task-8) */
|
||||
#include "phasor.h" /* 全周傅氏相量层:phasor_init()(系数表 + 实例 + 双区交换) */
|
||||
@@ -292,14 +292,16 @@ int app_main_init(void) {
|
||||
DBG_INFO("freq layer init: OK");
|
||||
|
||||
/* 4c-3) 通道标定层(标定折算,「数据可信」标定折算与落盘整定)初始化:RAM 系数副本置
|
||||
* ROM 默认(量程口径冻结定死的引脚侧 LSB,docs/硬件平台/ADC通道映射说明.md §7.1)
|
||||
* + 向配置引擎注册 [adc_cal] / [adc_cal_ofs] 两段。注册必须先于
|
||||
* ROM 默认(k = 量程口径冻结定死的引脚侧 LSB,docs/硬件平台/ADC通道映射说明.md §7.1;
|
||||
* r = 台架实测反算的端子变比,同文档 §7.2)
|
||||
* + 向配置引擎注册 [adc_cal] / [adc_cal_ofs] / [adc_cal_sec] 三段。注册必须先于
|
||||
* defaultTask 的 cfg_file_load(「先注册后 load」契约)→ 只能落内核前;
|
||||
* 折算消费在 adcTask 每拍(adc_cal_apply),标定文件值随 load/poll 原地
|
||||
* 生效,无回调。 */
|
||||
adc_cal_init();
|
||||
DBG_INFO("adc cal init: OK (k_q16 tpafe=%ld stm32=%ld)",
|
||||
(long)ADC_CAL_K_Q16_TPAFE, (long)ADC_CAL_K_Q16_STM32);
|
||||
DBG_INFO("adc cal init: OK (k_q16 tpafe=%ld stm32=%ld r_q16 v=%ld i=%ld)",
|
||||
(long)ADC_CAL_K_Q16_TPAFE, (long)ADC_CAL_K_Q16_STM32,
|
||||
(long)ADC_CAL_R_Q16_VOLT, (long)ADC_CAL_R_Q16_CURR);
|
||||
|
||||
/* 4c2) 运行参数段初始化(「数据可信」这一项):死区 9 点工厂默认 + 注册
|
||||
* [run_par] 段。同 adc_cal 的「先注册后 load」契约 → 必须内核前;
|
||||
|
||||
+16
-6
@@ -218,16 +218,25 @@ static void feed_map_phasor(void) {
|
||||
* 入口参数:无
|
||||
* 返回值:UI_DATA_ENG_PIN 或 UI_DATA_ENG_SEC uint8_t
|
||||
* 限定条件:无(纯读)
|
||||
* 函数说明:**全或无**——电气 8 路(TEMP 除外,它不参与线性标定)的
|
||||
* ADC_CAL_F_VALID 全置位才升二次值。取全或无是因为整帧共用一个 eng:
|
||||
* 只要还有一路是引脚电压,页面挂"二次值"就会把那一路的读数误标
|
||||
* (宁可整体显示"未标定",也不给部分真值贴真值标签)
|
||||
* 函数说明:判据 = 「每路要么已配变比、要么削顶不显示」(2026-11-11 口径变更):
|
||||
* 电气 8 路里,凡**未被削顶**的通道都必须 `adc_cal_ratio_configured()`,
|
||||
* 全部满足才升二次值;削顶通道不参与断言——它们页面本来就显 "--",
|
||||
* 不进任何读数,拿它们卡住整帧标签只会让 V0.1 那五路削顶把徽标永久钉在
|
||||
* "未标定"上。
|
||||
* ⚠ 与 `adc_cal_prot_blocked()`(判据闭锁看 UA/UN/IA 三路 VALID)**不是
|
||||
* 同一件事,别合并**:那个问"准不准"、这个问"什么单位"。也没用 VALID:
|
||||
* 只配了变比、没做两点标定时输出**确实是**二次值,只是精度未定——
|
||||
* 量纲标签若跟着 VALID 走,屏上就会出现"数值是 5.000(二次)却标未标定 V"
|
||||
* 这种自相矛盾。
|
||||
*/
|
||||
static uint8_t feed_eng(void) {
|
||||
uint8_t sem;
|
||||
|
||||
for (sem = 0u; sem < (uint8_t)ADC_SEM_TEMP; sem++) {
|
||||
if ((adc_cal_flags((adc_semantic_t)sem) & ADC_CAL_F_VALID) == 0u) {
|
||||
if ((adc_cal_flags((adc_semantic_t)sem) & ADC_CAL_F_CLIPPED) != 0u) {
|
||||
continue; /* 削顶通道不显示读数,不参与整帧量纲断言 */
|
||||
}
|
||||
if (adc_cal_ratio_configured((adc_semantic_t)sem) == 0u) {
|
||||
return UI_DATA_ENG_PIN;
|
||||
}
|
||||
}
|
||||
@@ -322,7 +331,8 @@ static void feed_fill_rt(ui_data_rt_t *p_snap, uint32_t tick_ms) {
|
||||
p_snap->valid = 1u;
|
||||
p_snap->eng = feed_eng();
|
||||
|
||||
/* 遥测:语义通道序的基波有效值(采样环已按 adc_cal 折算 → 当前为引脚电压域) */
|
||||
/* 遥测:语义通道序的基波有效值(采样环已按 adc_cal 折算 → 量纲看 eng:
|
||||
* 已配变比的路是二次值 V/A,未配的是引脚电压 V) */
|
||||
for (sem = 0u; sem < (uint8_t)ADC_SEM_COUNT; sem++) {
|
||||
int8_t pi = s_yc_phasor[sem];
|
||||
|
||||
|
||||
+39
-22
@@ -519,38 +519,55 @@ void adc_task_func(void *arg) {
|
||||
wev = wave_ring_tick(now, s_frames_consumed);
|
||||
|
||||
if (wev == WAVE_RING_EVT_SEALED) {
|
||||
uint8_t c;
|
||||
|
||||
/* 封存瞬间事实(交棒结构 s_wave_cal 为文件级 static,不上栈):
|
||||
* eng / VALID 位图 / CLIPPED 位图 / 墙钟 / 逐路 conv+bits+avail+k+b。
|
||||
* eng / VALID 位图 / CLIPPED 位图 / 墙钟 / 逐路 conv+bits+avail+k+b+r。
|
||||
* eng 口径 = adc_cal.h 注③ 的 8 路量纲标签(判据源
|
||||
* App/app/ui_data_feed.c feed_eng(),与 prot_blocked 的三路口径**不是**
|
||||
* 一件事):8 路电气量全 VALID 才是二次值;V0.1 无 CT 变比,一次值不可达。
|
||||
* 一件事):**每路要么已配变比、要么削顶(页面本来就显 "-",不参与
|
||||
* 整帧量纲断言)——八路全满足即二次值,否则引脚域**。不再看 VALID:
|
||||
* VALID 回答"准不准"、r 回答"什么单位",V0.1 五路削顶永远拿不到 VALID,
|
||||
* 用 VALID 当量纲判据会让徽标永不解除。
|
||||
* wall 未对时写 0(sys_clock_get 失败 = UINT32_MAX,不许拿上电毫秒冒充)。
|
||||
* k 取 adc_cal_k_q16() int32 原值;b 的 float 回乘在本域无损
|
||||
* (|b| 限幅 8192 << 2^24,float32 精确表示整数;禁用浮点只针对 k)。 */
|
||||
* k / r 取 adc_cal_k_q16() / adc_cal_ratio_q16() 的 int32 原值(float 在
|
||||
* 5.28e7 / 6.5e7 量级掉尾数);b 的 float 回乘在本域无损
|
||||
* (|b| 限幅 8192 << 2^24,float32 精确表示整数;禁用浮点只针对 k / r)。 */
|
||||
s_wave_cal.flags_seq = 0u;
|
||||
s_wave_cal.clip_mask = 0u;
|
||||
for (c = 0u; c < WAVE_RING_CH; c++) {
|
||||
const uint32_t f = adc_cal_flags((adc_semantic_t)c);
|
||||
const adc_channel_desc_t *d = adc_input_desc((adc_semantic_t)c);
|
||||
/* 整帧量纲:每路要么已配变比、要么削顶不显示 → 全满足即二次值。
|
||||
* 削顶通道不参与断言的理由:它们页面本来就显 "-",把它们的量纲也算进
|
||||
* 整帧会把「已全部配好变比」的段误标成引脚域(V0.1 五路削顶恒成立)。
|
||||
* 与逐路取数**同一遍循环**算完:adc_cal 每拍只走一遍(逐路 flags /
|
||||
* ratio_configured / k_q16 / b_codes / ratio_q16 五个纯读子查询),
|
||||
* 不为了一个派生量再走第二趟。 */
|
||||
{
|
||||
uint8_t eng_ok = 1u;
|
||||
uint8_t c;
|
||||
|
||||
if ((f & ADC_CAL_F_VALID) != 0u) {
|
||||
s_wave_cal.flags_seq |= (uint8_t)(1u << c);
|
||||
for (c = 0u; c < WAVE_RING_CH; c++) {
|
||||
const uint32_t f = adc_cal_flags((adc_semantic_t)c);
|
||||
const adc_channel_desc_t *d = adc_input_desc((adc_semantic_t)c);
|
||||
|
||||
if ((f & ADC_CAL_F_VALID) != 0u) {
|
||||
s_wave_cal.flags_seq |= (uint8_t)(1u << c);
|
||||
}
|
||||
if ((f & ADC_CAL_F_CLIPPED) != 0u) {
|
||||
s_wave_cal.clip_mask |= (uint16_t)(1u << c);
|
||||
} else if (adc_cal_ratio_configured((adc_semantic_t)c) == 0u) {
|
||||
eng_ok = 0u;
|
||||
}
|
||||
s_wave_cal.ch[c].conv = (d != NULL) ? (uint8_t)d->source
|
||||
: (uint8_t)ADC_SRC_STM32;
|
||||
s_wave_cal.ch[c].bits = (s_wave_cal.ch[c].conv == (uint8_t)ADC_SRC_TPAFE)
|
||||
? 16u : 12u;
|
||||
s_wave_cal.ch[c].avail = (uint8_t)f;
|
||||
s_wave_cal.ch[c].k_q16 = adc_cal_k_q16((adc_semantic_t)c);
|
||||
s_wave_cal.ch[c].b_codes = (int32_t)adc_cal_b_codes((adc_semantic_t)c);
|
||||
s_wave_cal.ch[c].r_q16 = adc_cal_ratio_q16((adc_semantic_t)c);
|
||||
}
|
||||
if ((f & ADC_CAL_F_CLIPPED) != 0u) {
|
||||
s_wave_cal.clip_mask |= (uint16_t)(1u << c);
|
||||
}
|
||||
s_wave_cal.ch[c].conv = (d != NULL) ? (uint8_t)d->source
|
||||
: (uint8_t)ADC_SRC_STM32;
|
||||
s_wave_cal.ch[c].bits = (s_wave_cal.ch[c].conv == (uint8_t)ADC_SRC_TPAFE)
|
||||
? 16u : 12u;
|
||||
s_wave_cal.ch[c].avail = (uint8_t)f;
|
||||
s_wave_cal.ch[c].k_q16 = adc_cal_k_q16((adc_semantic_t)c);
|
||||
s_wave_cal.ch[c].b_codes = (int32_t)adc_cal_b_codes((adc_semantic_t)c);
|
||||
s_wave_cal.eng = (eng_ok != 0u)
|
||||
? (uint8_t)WAVE_ENG_SEC : (uint8_t)WAVE_ENG_PIN;
|
||||
}
|
||||
s_wave_cal.eng = (s_wave_cal.flags_seq == 0xFFu)
|
||||
? (uint8_t)WAVE_ENG_SEC : (uint8_t)WAVE_ENG_PIN;
|
||||
{
|
||||
uint32_t wall = sys_clock_get();
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
* (自检已上报、卷保留、不重格式化),等下次上电重新 resume */
|
||||
#define STORAGE_BRINGUP_RETRIES 3u
|
||||
|
||||
/* 录波段落盘持锁耗时告警线(lead 裁③i):一次心跳整段 5250 B(约 21 次 256 B
|
||||
/* 录波段落盘持锁耗时告警线(lead 裁③i):一次心跳整段 5282 B(约 21 次 256 B
|
||||
* f_write),设计 §4.2 预估落盘窗「几十 ms」;越过本线说明本段在占用 defaultTask
|
||||
* 喂狗链前的最慢一段,按事件补一条 WARN(不是周期行),并把历史峰值留在本层
|
||||
* 静态里供收工报告核喂狗余量(vol_lock.h 铁律「等锁即毁喂狗链」的前例在案) */
|
||||
@@ -267,7 +267,8 @@ void default_task_func(void *argument) {
|
||||
syslog_post(SYSLOG_OPER, "settings saved");
|
||||
}
|
||||
/* 运行参数段落盘(「数据可信」这一项):同款脏语义;save = 引擎整档外科写
|
||||
* ([run_par] 9 键 + 被 adc_cal_set 联动改写的 [adc_cal] 两段一趟落) */
|
||||
* ([run_par] 9 键 + 被 adc_cal_set() / adc_cal_set_ratio() 联动改写的
|
||||
* [adc_cal] / [adc_cal_ofs] / [adc_cal_sec] 三段一趟落) */
|
||||
(void)run_par_store_save();
|
||||
/* [comm] 段落盘(道G task-9 通讯设置页):同款脏位、每轮无条件,
|
||||
* 无事件位(task-8 D5 口径——ca 改后重启生效,落盘只保断电不丢) */
|
||||
@@ -624,7 +625,7 @@ static FRESULT storage_bring_up(FATFS *fs) {
|
||||
* 入口参数:无 void
|
||||
* 返回值:无 void
|
||||
* 限定条件:仅 defaultTask 主循环调用(vol_idle_sync_poll 之后);内部绝不阻塞——
|
||||
* try 锁 + 有界段长(5250 B / 256 B ≈ 21 次 f_write,设计 §4.2 预估几十 ms),
|
||||
* try 锁 + 有界段长(5282 B / 256 B ≈ 21 次 f_write,设计 §4.2 预估几十 ms),
|
||||
* 绝不把喂狗点做成等待态(vol_lock.h 使用约定:defaultTask 一律 try)
|
||||
* 函数说明:所有权按 docs/子系统设计/故障录波设计说明.md §5.1——环的生产者是
|
||||
* adcTask,本道是唯一落盘消费者;跨任务握手只认 wave_ring_sealed()。
|
||||
@@ -632,9 +633,10 @@ static FRESULT storage_bring_up(FATFS *fs) {
|
||||
* ① 打开一律 FA_WRITE|FA_CREATE_ALWAYS(**覆盖,不是追加**):段名是 100 个
|
||||
* 槽位的轮转复用(wave_store.h 的「号 0..99 轮转覆盖,写满回绕盖最老的」),
|
||||
* 槽位被复用时同名文件已存在——用 FA_OPEN_APPEND 会把新段接在旧段尾巴上,
|
||||
* 文件长成 5250×N,而回放页的单段读侧要求 size == 5250,于是逐件判
|
||||
* 文件长成 段长×N,而回放页的单段读侧要求 size == 段长,于是逐件判
|
||||
* 「长度废」(reject=2),全板没有一段放得出来。2026-11-10 板级实测:
|
||||
* 100 个文件 / 1868 段全部合法、零损坏,纯粹是容器尺寸与写侧追加语义相抵。
|
||||
* 100 个文件 / 1868 段全部合法、零损坏,纯粹是容器尺寸与写侧追加语义相抵
|
||||
* (当时段长 5250 B = v1 布局;v2 加变比维后为 5282 B,判据形状不变)。
|
||||
* 本移植 FA_CREATE_ALWAYS 会 remove_chain 并清零目录项的 FileSize,
|
||||
* 是真正的截断(Lib/FatFs/ff.c 的 f_open 覆盖分支);
|
||||
* 注意 FA_CREATE_ALWAYS 单独用**不含 FA_WRITE**(本移植旧式标志位),
|
||||
@@ -802,7 +804,7 @@ static void wave_store_drain(void) {
|
||||
if (rc == WAVE_ST_DONE) {
|
||||
break;
|
||||
}
|
||||
if (++iter > 32u) { /* 5250/256 = 21 片;游标卡死时的有界保险 */
|
||||
if (++iter > 32u) { /* 5282/256 ≈ 21 片;游标卡死时的有界保险 */
|
||||
why = "pack stall";
|
||||
break;
|
||||
}
|
||||
|
||||
+20
-15
@@ -84,7 +84,7 @@
|
||||
* wave_store.h 给段格式解析与定点 mV 换算(唯一口径,页面不复述算式);
|
||||
* vol_lock.h + ff.h:/wave 只读遍历按 soe_store.c 读者同款纪律——
|
||||
* try 锁非阻塞(卷忙本轮按「无段」处理,uiTask 绘制节拍绝不等待)、
|
||||
* 锁跨完整文件操作序列、小窗口分次读、整段 5250B 绝不驻留 RAM */
|
||||
* 锁跨完整文件操作序列、小窗口分次读、整段 5282B 绝不驻留 RAM */
|
||||
#include "ff.h"
|
||||
#include "vol_lock.h"
|
||||
#include "wave_ring.h"
|
||||
@@ -404,7 +404,7 @@ static const ui_soe_reader_t s_soe_reader = { ui_task_soe_count, ui_task_soe_get
|
||||
* - 段完整性判定只信 wave_store 自己的判据(尺寸等式 / magic+ver / 双 CRC /
|
||||
* sem==index),本胶水不读 avail/flags 做二次裁断(§6.3 与 2026-10-29
|
||||
* 「VALID+CLIPPED 非矛盾」裁定同源:可用性不是完整性);
|
||||
* - 整段 5250B 不驻留:256B 静态窗口分次读,头区一次、数据区流式过 CRC。
|
||||
* - 整段 5282B 不驻留:256B 静态窗口分次读,头区一次、数据区流式过 CRC。
|
||||
* uiTask 阻塞上界(IWDG 800ms 窗、uiTask 在 required_mask 内;写法对齐上方
|
||||
* 维护通道那段上界,同文件先例):
|
||||
* - 第零项是「本块不等待」:vol_lock_wait(0) 拿不到锁即按「本轮无段」
|
||||
@@ -412,7 +412,7 @@ static const ui_soe_reader_t s_soe_reader = { ui_task_soe_count, ui_task_soe_get
|
||||
* - 一次渲染实际发生的 I/O(进回放页首绘、或按键后重绘那帧)三件叠加:
|
||||
* ① 表重扫 = 1 次 f_opendir + 至多 WAVE_ST_SEG_MAX(100)次 f_readdir,
|
||||
* 收尾是 100 元插入排序(纯内存);
|
||||
* ② 段头装载 = 仅未缓存段才发生:1 次 f_open + 头区 1 次 128B 读 +
|
||||
* ② 段头装载 = 仅未缓存段才发生:1 次 f_open + 头区 1 次 160B 读 +
|
||||
* head_parse 内的头 CRC + 数据区 20 次 256B 流式读 + 尾 2B 一次
|
||||
* = 最坏 22 次小读;5120B 逐字节 CRC 与 sem 表校验是纯计算,
|
||||
* 168MHz 下微秒到毫秒量级;缓存命中零卷操作;
|
||||
@@ -589,7 +589,7 @@ static uint8_t ui_task_wave_find(uint8_t a_idx, uint8_t *a_pos) {
|
||||
* 成功则把段头留在 s_wave_meta 供 seg_win 免重读
|
||||
* 入口参数:a_idx - 段号
|
||||
* 返回值:UI_WAVE_OK / UI_WAVE_NONE(卷忙/文件没了)/ UI_WAVE_BAD(判废) int
|
||||
* 限定条件:uiTask;锁跨 open→close 完整序列;头 128B 一次、数据区 256B
|
||||
* 限定条件:uiTask;锁跨 open→close 完整序列;头 160B 一次、数据区 256B
|
||||
* 分窗流式(共约 1 + 20 次 f_read),整段不驻留
|
||||
* 耗时账(lead 裁定 2026-10-29 第 1 条·第三条并存):未缓存段的完整性
|
||||
* 判定最坏约 21 次小读 + 5120B 逐位 CRC,全部在卷锁内、uiTask 上;
|
||||
@@ -597,7 +597,7 @@ static uint8_t ui_task_wave_find(uint8_t a_idx, uint8_t *a_pos) {
|
||||
* 缓存只留一段:左右切段 A→B→A 会把 B 的校验再来一遍——接受该
|
||||
* 成本的代价换「CRC 不符必须拒收」永远现算,不做跨键存的位图
|
||||
* 函数说明:缓存命中(同段且已验)直接返 OK——左右切段回到上一段、上下切
|
||||
* 通道都靠它把重复的 5250B 全扫压成一次;文件读中途消失(被别的
|
||||
* 通道都靠它把重复的 5282B 全扫压成一次;文件读中途消失(被别的
|
||||
* 写者轮转掉)按 NONE 处理,页面下轮自然重扫;五类判废(reject=
|
||||
* 原因码见上方 UI_WAVE_REJ_* 组)在本函数收尾统一收敛成一条
|
||||
* DBG_ERROR——只有串口在手的远程轮也要有判废可查面(#93 F2,
|
||||
@@ -641,7 +641,7 @@ static int ui_task_wave_load_head(uint8_t a_idx) {
|
||||
size = (uint32_t)f_size(&f);
|
||||
total = 0u;
|
||||
rc = UI_WAVE_BAD;
|
||||
/* 第一步:头区(首窗 128B = 当前形态整头);head_parse 自带判废 */
|
||||
/* 第一步:头区(首窗 160B = 当前形态整头);head_parse 自带判废 */
|
||||
n = (size < (uint32_t)WAVE_ST_HEAD_SZ_CUR) ? size
|
||||
: (uint32_t)WAVE_ST_HEAD_SZ_CUR;
|
||||
fr = f_read(&f, s_wave_buf, n, &br);
|
||||
@@ -656,7 +656,7 @@ static int ui_task_wave_load_head(uint8_t a_idx) {
|
||||
uint16_t crc;
|
||||
uint8_t tail[2];
|
||||
|
||||
/* 第三步:数据区流式 CRC(128B 窗;CRC 覆盖不含尾两字节自身) */
|
||||
/* 第三步:数据区流式 CRC(256B 窗;CRC 覆盖不含尾两字节自身) */
|
||||
crc = WAVE_ST_CRC_INIT;
|
||||
off = wave_store_head_bytes(s_wave_meta.ch_count);
|
||||
rc = UI_WAVE_BAD;
|
||||
@@ -876,15 +876,17 @@ static int ui_task_wave_seg_head(uint8_t a_idx, uint8_t *a_eng,
|
||||
/*
|
||||
* 函数功能:读固定显示窗(触发点前后各一周波)的码值与峰值
|
||||
* 入口参数:a_idx - 段号;a_ch - 通道号;a_codes - UI_WAVE_WIN_PTS 个码值
|
||||
* (已并入段头 b_codes);a_peak_milli - 窗内绝对值最大点的 mV
|
||||
* (已并入段头 b_codes);a_peak_milli - 窗内绝对值最大点的**录波域毫单位**
|
||||
* (eng=1 二次 mV/mA,eng=0 引脚 mV;变比由 wave_store_code_to_milli
|
||||
* 按段头 r_q16 现折,本层不重算)
|
||||
* 返回值:UI_WAVE_OK / NONE(段头未验过/卷忙)/ BAD(数据区读不齐) int
|
||||
* 限定条件:uiTask;先段头后窗口(未过完整性判定的段不供数据,页面显示
|
||||
* 「该段不可读」而不是半截波形——判废优先于展示)
|
||||
* 函数说明:窗口起点用段头 pre_pts 现算(§8.2「数据不是编译常量」:
|
||||
* 起点 = pre_pts - 窗长一半,与光标居中是同一个 20 的一半,
|
||||
* 改窗宽只改 ui_data.h 的 UI_WAVE_WIN_PTS 一处);峰值的 mV
|
||||
* 换算逐点走 wave_store_code_to_milli —— 全仓唯一口径,
|
||||
* 这里不重抄公式(§6.3;PC 道同一条款)
|
||||
* 改窗宽只改 ui_data.h 的 UI_WAVE_WIN_PTS 一处);峰值的米制单位
|
||||
* 换算逐点走 wave_store_code_to_milli —— 全仓唯一口径(v2 起自带变比,
|
||||
* peak 自动跟着录波域走),这里不重抄公式(§6.3;PC 道同一条款)
|
||||
*/
|
||||
static int ui_task_wave_seg_win(uint8_t a_idx, uint8_t a_ch,
|
||||
int16_t *a_codes, int32_t *a_peak_milli) {
|
||||
@@ -959,10 +961,13 @@ static int ui_task_wave_seg_win(uint8_t a_idx, uint8_t a_ch,
|
||||
a_codes[i] = (int16_t)v;
|
||||
/* 显示与度量同一把尺(「谁被显示谁被度量」,§6.3):
|
||||
* a_codes 画的是 raw+b,而 wave_store_code_to_milli 的公式是
|
||||
* (a_code + b_codes) × k_q16 / 65536000 取整(App/algo/
|
||||
* wave_store.c:567 实测口径)——传 raw 进去,b 已由唯一口径
|
||||
* 自己加过,PK 数与屏上波形天然同尺,这里不再加第二遍
|
||||
* (加两遍才是错);clamp 只护 int16 画布,b 的 V0.1 恒 0 */
|
||||
* (a_code + b_codes) × k_q16 × r_q16 的定点折算(口径唯一属主
|
||||
* App/algo/wave_store.c 的 wave_store_code_to_milli)——传 raw 进去,
|
||||
* b 与 r 都已由唯一口径自己算过,PK 数与屏上波形天然同尺,
|
||||
* **这里不再加第二遍 b,也不乘 r**(重复折算才是错);
|
||||
* 输出是「录波域毫单位」:eng=1 时是二次 mV/mA,eng=0 时是引脚 mV,
|
||||
* 本段不重算公式、也不按当前固件系数猜单位。
|
||||
* clamp 只护 int16 画布,b 的 V0.1 恒 0 */
|
||||
if (wave_store_code_to_milli(&s_wave_meta.ch[a_ch], raw[i],
|
||||
&milli) == 0) {
|
||||
if ((milli < 0 ? -milli : milli) >
|
||||
|
||||
Reference in New Issue
Block a user