Files
STM32F4-Base/test/host/adc_cal/adc_cal_test.c
T
wandering1 3528644e9d feat(采集标定): 卡2 adc_cal 折算模块 + 卡4 系数落盘 + L5-2 码值贴轨检出
卡 2(App/algo/adc_cal.*,新增;adc_task.c:335 折算点收口):
- 模型:工程量 =(code+b)×k(「数据可信」§4.1);k 以 int32 Q16(µV/码×65536)
  落盘、b 以 int32 码值落盘;ROM 默认 = 卡 1 定死的引脚侧 LSB,Q16 下整除无
  舍入(TPAFE 10,000,000=152.587890625 µV/码;STM32 52,800,000=805.6640625)
- adc_task 每拍 frame[s] 改 adc_cal_apply()(现单位 = ADC 引脚电压 V,eng=0,
  二次值语义等 VALID);TEMP 透传码值(决策 5);不缓存 k_f 副本——逐点由
  RAM 现值派生(无回调设计下杜绝陈旧系数;18k flops/s 可忽略)
- 三态 flags(§4.2):VALID = 文件位图 ∧ k 在合法带;CLIPPED 按来源运行期
  派生(V0.2/N-18 后自动恒 0);消费者判可用性只查 adc_cal_flags()
- 合法带哨兵:k∉ROM×[0.5,2.0] 或 |b|>8192 → 折算回退 ROM + 每通道每开机
  一条 [ERR] + VALID 压制(位图说谎也兜住)——同时挡坏文件与坏标定
- 两点标定公式 adc_cal_two_point() 算术核齐备(int64、±舍入界、斜率/零点
  守卫 -1/-2 分档);台架两点标定(卡 12)按用户指示未跑,留台架注入口径:
  心跳新增 cal: 行(ua/un/ia_mv 定点 + vl/cl/rail 位图,未标定 vl 应恒 0x00、
  cl 应恒 0xE6,注入饱和电平 rail 位应起)

卡 4([adc_cal] k_ua..k_in+valid / [adc_cal_ofs] b_ua..b_in,§4.4 布局):
- 两段注册进 cfg 引擎(data_size 33/32,不挂回调——RAM 副本即生效值);
  adc_cal_init() 落 app_main_init 内核前(注册必须先于 defaultTask 的
  cfg_file_load;跨任务读走 4 字节对齐字,µs 级混窗为「数据可信」§4.4 明载
  设计);文件缺失/损坏 = 引擎回退工厂默认 = ROM 表本体(默认即理论系数)
- adc_cal_set():整定写入口(属主上下文),越带拒绝 RAM 不动
- 单测走真实 cfg_file 引擎集成:文件值生效 / 越界 k 哨兵兜住且不报 VALID /
  超长损坏文件回退默认且 flags 无 VALID(卡 4 DoD 原句)/ 段缺失默认

L5-2(adc_health v1.2,越界门限按 FS 百分比、码值域与标定正交):
- adc_health_rail_watch():连续 20 拍(=一周波)贴同侧轨带(TPAFE±98%半量程、
  STM32 顶 98%/底 2%,门限由 adc_cal_rail_ths 供)且此前见过中带释放沿才
  锁存——空载恒 0 / 开机恒 0 合法工况不误报(短板评测 §6.2 判据①);
  adc_task 编排:边沿 DBG_ERROR + SYSLOG 每开机一次 + 心跳 rail/edge 位图
- 健康用例 60/60(+13):释放沿/清零/交替/双侧/init 复位/越界通道守卫

测试与注册:新套件 test/host/adc_cal(89/89,8 通道×≥3 码点期望值由 LSB
常数独立推导、Q16 无舍入、set 边界含端点、两点还原合成 k/b 往返);
编号形态返工一轮:TC-ADCCAL-00A~00D 改 014~017、注释 FS-1 字样改写、
id_registry 族 pattern 转义修正、族游标登至 017;coding-standard 返工:
公开实现函数补齐函数头注释块(CODE-RULE-09)
uvprojx 仅增 adc_cal.c 一条挂载(diff 已核零抖动);SIM_EXEMPT/id_registry
(TC-HOST-ADCCAL 族)/adc-cal-unit-test 钩子行按 new-source-file 与 B 层口径
补齐;覆盖率 34/34 套件 89.6%,adc_cal.c 99.2%(baseline --update 属共享
台账文件,留 lead done 后统一回填)

gate:@build 0 错 0 警(Code=215440 RO=19780 RW=1220 ZI=131980);
RAM 增量 +144 B(RW+16/ZI+128:贴轨 9×6B + 两段 RAM 副本,IRAM1 57.7%→
约 57.8%);Flash +1336B/+920B RO。adcTask 栈 worst=1344B margin 1216B
(47%) PASS(stack_report 实测)。staged 门禁除 mem-numbers/mem-budget 外
全绿(该两条 = 台账漂移,回填 --write 落 docs/内存管理/** 单写者区,按
Lead 既定例外归 lead done 后处理):
gate: only mem-budget/mem-numbers pending lead ledger backfill
BOM/CR/U+FFFD/字面量 ASCII 已逐文件自查(本次未触碰任何带 BOM 文件)
2026-10-25 04:21:12 +08:00

511 lines
22 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.
/*
* 模块名称:通道标定层(L1.5,adc_cal)主机侧单元测试
* 被测对象:真实源文件 App/algo/adc_cal.c + App/cfg/cfg_file.c(均 #include
* 原样纳入——卡 4 的落盘契约必须用真引擎测,不造轮子替身)。
* 平台依赖桩:dbg_log.h(stub/ 计数宏)、cfg_store_*(本文件内存
* 虚拟盘,同 cfg_file 套件先例)、syslog_post(计数)、
* adc_input_desc()(按 ADC通道映射说明.md 权威表给 V0.1 来源映射)。
*
* 为什么必须主机侧(数据可信 §2.6):板端无标准源,「读数 = 系数 × 码值」
* 这一根本判据在台架上不可验;Q16 舍入、系数翻倍口径、负零点落盘、合法带
* 哨兵漏放,只有离线端能用独立推导的期望值 + 坏文件注入一次性钉死。
*
* 覆盖点(TC-ADCCAL-xxx;属主登记处 = 本目录,族 TC-HOST-ADCCAL):
* TC-ADCCAL-001 init 注册两段:名称/键数/data_size/无回调/默认表指向
* TC-ADCCAL-002 UA(TPAFE)折算:0 / 8192 / 32767 / -32768 对 10V/65536 独立推导
* TC-ADCCAL-003 UB(STM32)折算:0 / 2048 / 4095 对 3.3V/4096
* TC-ADCCAL-004 其余 6 路各 1 点(合计 8 通道 × ≥3 码值点,卡 2 DoD)
* TC-ADCCAL-005 TEMP 透传 + flags/k_uv 恒 0;sem 越界守卫
* TC-ADCCAL-006 默认 flags 三态:VALID 全 0;CLIPPED = UB/UC/IB/IC/IN(0xE6)
* TC-ADCCAL-007 k_uv 精确值:152.587890625 / 805.6640625(Q16 无舍入验证)
* TC-ADCCAL-008 set():合法带边界含端点 / 越界拒绝 RAM 不动 / TEMP 拒绝
* TC-ADCCAL-009 合法带哨兵回退:坏 k/坏 b → ROM 值 + 每通道一次 [ERR] +
* flags 不报 VALID(即便位图置位)+ init 重置观测窗
* TC-ADCCAL-014 零点 b 生效(set 路径):(code+b)×k 逐项可算
* TC-ADCCAL-015 两点标定公式:合成数据还原 k/b + 写回往返 + 参数/斜率/
* 零点越界(-2)守卫(卡 12 的算术核,台架注入待跑)
* TC-ADCCAL-016 rail_ths 门限:TPAFE ±32112 / STM32 4013·81 / TEMP 不监视
* TC-ADCCAL-017 init 幂等:重复调用 RAM 复位默认 + 重新注册
* —— 以下走真实 cfg_file 引擎(卡 4 DoD)——
* TC-ADCCAL-010 文件值生效:k_ua/b_ua 经 load 改变折算结果,VALID 置位
* TC-ADCCAL-011 越界文件值:k=999 / k=-999 被引擎收下后由哨兵兜住:
* 折算 = ROM、VALID 不报(位图置位也不报)、[ERR] 留痕
* TC-ADCCAL-012 段损坏(超长文件)→ 回退 ROM 且 flags 无 VALID(卡 4 DoD)
* TC-ADCCAL-013 段缺失(文件无本段)→ ROM 默认、VALID 全 0
*
* 通过:全部 TC PASS(main 返回 0)。
* 创建日期:2026-10-25(道A 卡 2/卡 4)
*/
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/* 先纳入桩头(DBG_* → 计数),再纳入生产源码 */
#include "dbg_log.h"
/* ===================== 内存虚拟文件系统(cfg_store_* 强符号) ===================== */
/* 与 cfg_file 套件同款的最小实现;生产代码经 cfg_file_store.h 做全部文件/堆操作 */
#include "cfg_file_store.h"
#define VFS_CAP 8192u
static uint8_t s_vfs[VFS_CAP];
static size_t s_vfs_len = 0u;
static uint32_t s_vfs_mtime = 100u;
static int s_vfs_exists = 0;
void *cfg_store_alloc(size_t size) { return malloc(size); }
void cfg_store_free(void *p_buf) { free(p_buf); }
uint32_t cfg_store_heap_free(void) { return 0u; }
int cfg_store_read(const char *p_path, uint8_t *p_buf, size_t cap, size_t *p_nread) {
size_t n;
(void)p_path;
if (!s_vfs_exists) {
return -1;
}
n = s_vfs_len;
if (n > cap) {
n = cap;
}
(void)memcpy(p_buf, s_vfs, n);
*p_nread = n;
return 0;
}
int cfg_store_write_atomic(const char *p_path, const uint8_t *p_buf, size_t len) {
(void)p_path;
if (len > VFS_CAP) {
return -1;
}
(void)memcpy(s_vfs, p_buf, len);
s_vfs_len = len;
s_vfs_exists = 1;
s_vfs_mtime++;
return 0;
}
int cfg_store_stat(const char *p_path, cfg_file_stat_t *p_st) {
(void)p_path;
if (!s_vfs_exists) {
return -1;
}
p_st->size = (uint32_t)s_vfs_len;
p_st->mtime = s_vfs_mtime;
return 0;
}
int cfg_store_remove(const char *p_path) {
(void)p_path;
s_vfs_exists = 0;
s_vfs_len = 0u;
return 0;
}
const char *cfg_store_config_path(void) { return "/config.ini"; }
const char *cfg_store_tmp_path(void) { return "/config.tmp"; }
/* ---- sys_log 桩:只计数自检留痕 ---- */
#include "sys_log.h"
static int s_syslog_count = 0;
void syslog_post(syslog_id_t id, const char *desc) {
(void)id;
(void)desc;
s_syslog_count++;
}
/* ---- adc_input_desc 桩:V0.1 权威映射(docs/硬件平台/ADC通道映射说明.md §3/§5) ---- */
#include "adc_input.h"
static const adc_channel_desc_t s_desc_stub[ADC_SEM_COUNT] = {
{ ADC_SRC_TPAFE, 6u }, /* UA = buf[6] */
{ ADC_SRC_STM32, 14u }, /* UB = CH14(PC4) */
{ ADC_SRC_STM32, 3u }, /* UC = CH3 */
{ ADC_SRC_TPAFE, 5u }, /* UN = buf[5] */
{ ADC_SRC_TPAFE, 2u }, /* IA = buf[2] */
{ ADC_SRC_STM32, 6u }, /* IB = CH6 */
{ ADC_SRC_STM32, 1u }, /* IC = CH1 */
{ ADC_SRC_STM32, 2u }, /* IN = CH2 */
{ ADC_SRC_STM32, 5u }, /* TEMP = CH5 */
};
const adc_channel_desc_t *adc_input_desc(adc_semantic_t ch) {
if ((int)ch < 0 || (unsigned)ch >= (unsigned)ADC_SEM_COUNT) {
return NULL;
}
return &s_desc_stub[(unsigned)ch];
}
/* ---- dbg_log 桩观测计数定义(stub/dbg_log.h 声明为 extern) ---- */
int g_caltest_err_count = 0;
int g_caltest_info_count = 0;
int g_caltest_debug_count = 0;
/* ===================== 测试框架(极简,同 cfg_file 套件) ===================== */
static int s_total = 0, s_passed = 0, s_failed = 0;
#define CHECK(cond, name) do { \
s_total++; \
if (cond) { s_passed++; printf(" [PASS] %s\n", name); } \
else { s_failed++; printf(" [FAIL] %s (line %d)\n", name, __LINE__); } \
} while (0)
/* ===================== 被测代码 ===================== */
/* 真实引擎 + 真实标定层(顺序:cfg_file.c 先,其 DBG_TAG 宏被 adc_cal.c 重定义
* 仅产生主机侧 C4005 告警——MCU 端两者本就在不同 TU,无此现象) */
#include "cfg_file.c"
#include "adc_cal.c"
/* ===================== 独立推导的期望值 ===================== */
/* 期望一律用 **double 直接除**(10/65536、3.3/4096),不引用被测的 Q16 常量,
* 满足卡 2 DoD「期望值由 LSB 常数独立算得(不是抄实现)」;容差只需盖住
* float32 中间积舍入(相对 ~1e-7)与打印粒度。 */
static double exp_tpafe(int32_t code) { return (double)code * (10.0 / 65536.0); }
static double exp_stm32(int32_t code) { return (double)code * (3.3 / 4096.0); }
static double exp_q16(int32_t code, int32_t b, int32_t k_q16) {
return ((double)code + (double)b) * ((double)k_q16 / 65536.0) / 1.0e6;
}
static int near_d(double got, double want) {
double d = got - want;
if (d < 0.0) { d = -d; }
return (d <= 1.0e-4 + (want < 0.0 ? -want : want) * 1.0e-5) ? 1 : 0;
}
static long my_lround(double x) { return (long)((x >= 0.0) ? (x + 0.5) : (x - 0.5)); }
/* ===================== 辅助 ===================== */
/* 复位「引擎注册表 + 虚拟盘 + 全部观测计数」= 刚上电(白盒代价同 cfg_file 套件) */
static void reset_all(void) {
memset(s_vfs, 0, sizeof(s_vfs));
s_vfs_len = 0u;
s_vfs_mtime = 100u;
s_vfs_exists = 0;
memset((void *)s_sections, 0, sizeof(s_sections));
memset(&s_base, 0, sizeof(s_base));
memset(&s_last, 0, sizeof(s_last));
s_stable = 0u;
s_base_valid = 0u;
g_caltest_err_count = 0;
g_caltest_info_count = 0;
g_caltest_debug_count = 0;
s_syslog_count = 0;
}
static void vfs_put(const char *text) {
size_t n = strlen(text);
if (n > VFS_CAP) { n = VFS_CAP; }
(void)memcpy(s_vfs, text, n);
s_vfs_len = n;
s_vfs_exists = 1;
s_vfs_mtime++;
}
static const char *vfs_get(void) { return (const char *)s_vfs; }
/* ===================== 用例(白盒组) ===================== */
/* TC-ADCCAL-001:init 注册两段(名称/键数/data_size/无回调) */
static void tc_001_init_registers(void) {
printf("[TC-ADCCAL-001] init registers [adc_cal]/[adc_cal_ofs]\n");
reset_all();
adc_cal_init();
CHECK(s_sections[0].used == 1u, "section slot0 used");
CHECK(strcmp(s_sections[0].name, "adc_cal") == 0, "sec0 name = adc_cal");
CHECK(s_sections[0].key_count == 9u, "sec0 keys = 8k + valid");
CHECK(s_sections[0].data_size == 33u, "sec0 data_size = 33 (covers valid byte)");
CHECK(s_sections[0].p_cb == NULL, "sec0 no callback (RAM copy = live values)");
CHECK(s_sections[1].used == 1u && strcmp(s_sections[1].name, "adc_cal_ofs") == 0,
"sec1 name = adc_cal_ofs");
CHECK(s_sections[1].key_count == 8u, "sec1 keys = 8b");
CHECK(s_sections[1].data_size == 32u, "sec1 data_size = 32");
CHECK(s_sections[2].used == 0u, "only two sections registered");
}
/* TC-ADCCAL-002/003/004:8 通道 × ≥3 码值点折算,期望独立推导(卡 2 DoD) */
static void tc_002_004_apply(void) {
printf("[TC-ADCCAL-002] UA (TPAFE) apply\n");
reset_all();
adc_cal_init();
CHECK(near_d(adc_cal_apply(ADC_SEM_UA, 0), exp_tpafe(0)), "UA code 0 = 0V");
CHECK(near_d(adc_cal_apply(ADC_SEM_UA, 8192), exp_tpafe(8192)), "UA 8192 = 1.25V");
CHECK(near_d(adc_cal_apply(ADC_SEM_UA, 32767), exp_tpafe(32767)), "UA full scale minus 1 (bipolar top code)");
CHECK(near_d(adc_cal_apply(ADC_SEM_UA, -32768), exp_tpafe(-32768)), "UA -FS (bipolar)");
printf("[TC-ADCCAL-003] UB (STM32) apply\n");
CHECK(near_d(adc_cal_apply(ADC_SEM_UB, 0), exp_stm32(0)), "UB code 0 = 0V");
CHECK(near_d(adc_cal_apply(ADC_SEM_UB, 2048), exp_stm32(2048)), "UB mid = 1.65V");
CHECK(near_d(adc_cal_apply(ADC_SEM_UB, 4095), exp_stm32(4095)), "UB +FS 3.2992V");
printf("[TC-ADCCAL-004] other six channels one mid point each\n");
CHECK(near_d(adc_cal_apply(ADC_SEM_UC, 123), exp_stm32(123)), "UC 123");
CHECK(near_d(adc_cal_apply(ADC_SEM_UN, -10000), exp_tpafe(-10000)), "UN -10000");
CHECK(near_d(adc_cal_apply(ADC_SEM_IA, 11452), exp_tpafe(11452)), "IA 11452 (bench first-frame code)");
CHECK(near_d(adc_cal_apply(ADC_SEM_IB, 4000), exp_stm32(4000)), "IB 4000");
CHECK(near_d(adc_cal_apply(ADC_SEM_IC, 1), exp_stm32(1)), "IC 1");
CHECK(near_d(adc_cal_apply(ADC_SEM_IN, 4094), exp_stm32(4094)), "IN 4094");
}
/* TC-ADCCAL-005:TEMP 透传与守卫 */
static void tc_005_temp(void) {
printf("[TC-ADCCAL-005] TEMP passthrough + guards\n");
reset_all();
adc_cal_init();
CHECK(adc_cal_apply(ADC_SEM_TEMP, 3000) == 3000.0f, "TEMP raw passthrough");
CHECK(adc_cal_flags(ADC_SEM_TEMP) == 0u, "TEMP flags = 0 (both)");
CHECK(adc_cal_k_uv(ADC_SEM_TEMP) == 0.0f, "TEMP k_uv = 0");
CHECK(adc_cal_flags((adc_semantic_t)ADC_SEM_COUNT) == 0u, "flags sem OOB = 0");
CHECK(adc_cal_set((adc_semantic_t)ADC_SEM_COUNT, 10000000L, 0L) == -1, "set sem OOB rejected");
CHECK(adc_cal_apply(ADC_SEM_UA, 0) == 0.0f, "UA still applies after guards");
}
/* TC-ADCCAL-006:默认三态位图 */
static void tc_006_default_flags(void) {
uint32_t vl = 0u, cl = 0u;
uint8_t i;
printf("[TC-ADCCAL-006] default flags: no VALID, CLIPPED=0xE6\n");
reset_all();
adc_cal_init();
for (i = 0u; i < 8u; i++) {
const uint32_t f = adc_cal_flags((adc_semantic_t)i);
if ((f & ADC_CAL_F_VALID) != 0u) { vl |= (1uL << i); }
if ((f & ADC_CAL_F_CLIPPED) != 0u) { cl |= (1uL << i); }
}
CHECK(vl == 0u, "VALID map = 0x00 (uncalibrated default build)");
/* bit1 UB, bit2 UC, bit5 IB, bit6 IC, bit7 IN */
CHECK(cl == ((1uL << 1) | (1uL << 2) | (1uL << 5) | (1uL << 6) | (1uL << 7)),
"CLIPPED map = UB/UC/IB/IC/IN (0xE6)");
}
/* TC-ADCCAL-007:k_uv 精确值(Q16 无舍入口径) */
static void tc_007_k_uv(void) {
printf("[TC-ADCCAL-007] k_uv exact (Q16 no-rounding defaults)\n");
reset_all();
adc_cal_init();
CHECK(adc_cal_k_uv(ADC_SEM_UA) > 152.5877f && adc_cal_k_uv(ADC_SEM_UA) < 152.5881f,
"UA k_uv = 152.587890625");
CHECK(adc_cal_k_uv(ADC_SEM_UB) > 805.6639f && adc_cal_k_uv(ADC_SEM_UB) < 805.6643f,
"UB k_uv = 805.6640625");
}
/* TC-ADCCAL-008:set() 合法带边界与拒绝 */
static void tc_008_set(void) {
float before;
printf("[TC-ADCCAL-008] set band edges inclusive / rejects leave RAM\n");
reset_all();
adc_cal_init();
CHECK(adc_cal_set(ADC_SEM_UA, 10500000L, -12L) == 0, "set in-band ok");
CHECK((adc_cal_flags(ADC_SEM_UA) & ADC_CAL_F_VALID) != 0u, "VALID set after accepted");
CHECK(near_d(adc_cal_apply(ADC_SEM_UA, 1000), exp_q16(1000, -12, 10500000)),
"apply uses accepted k/b");
CHECK(adc_cal_set(ADC_SEM_UA, 5000000L, 0L) == 0, "ROM/2 boundary accepted");
CHECK(adc_cal_set(ADC_SEM_UA, 20000000L, 0L) == 0, "ROM*2 boundary accepted");
before = adc_cal_apply(ADC_SEM_UA, 100);
CHECK(adc_cal_set(ADC_SEM_UA, 4999999L, 0L) == -1, "below ROM/2 rejected");
CHECK(adc_cal_set(ADC_SEM_UA, 20000001L, 0L) == -1, "above ROM*2 rejected");
CHECK(adc_cal_set(ADC_SEM_UA, 10000000L, 8193L) == -1, "b +8193 rejected");
CHECK(adc_cal_set(ADC_SEM_UA, 10000000L, -8193L) == -1, "b -8193 rejected");
CHECK(adc_cal_set(ADC_SEM_TEMP, 10000000L, 0L) == -1, "TEMP rejected");
CHECK(adc_cal_apply(ADC_SEM_UA, 100) == before, "RAM untouched after all rejects");
}
/* TC-ADCCAL-009:合法带哨兵(白盒直写 RAM 模拟引擎收到坏值) */
static void tc_009_sanitize(void) {
double want;
printf("[TC-ADCCAL-009] band sentinel: ROM fallback + one-shot ERR + no VALID\n");
reset_all();
adc_cal_init();
s_kcfg.k[0] = 30000000L; /* > ROM*2:模拟坏文件/坏标定已写入 RAM */
s_kcfg.valid |= 0x01u; /* 位图也说「已标定」——更要用哨兵兜住 */
want = exp_tpafe(1000);
CHECK(near_d(adc_cal_apply(ADC_SEM_UA, 1000), want), "out-of-band k falls back to ROM");
CHECK((adc_cal_flags(ADC_SEM_UA) & ADC_CAL_F_VALID) == 0u, "VALID suppressed while k out of band");
CHECK(g_caltest_err_count == 1, "first bad observation logs exactly one ERR");
(void)adc_cal_apply(ADC_SEM_UA, 2000);
(void)adc_cal_apply(ADC_SEM_UA, 3000);
CHECK(g_caltest_err_count == 1, "repeat observations do not spam ERR");
/* b 越界同哨兵(k 合法、b 坏) */
s_bcfg.b[1] = 99999L;
CHECK(near_d(adc_cal_apply(ADC_SEM_UB, 1000), exp_stm32(1000)), "bad b -> ROM + b=0");
CHECK(g_caltest_err_count == 2, "bad b noted on its own channel");
/* init 重置留痕位 = 新观测窗 */
adc_cal_init();
CHECK(adc_cal_k_uv(ADC_SEM_UA) > 152.5f && s_kcfg.valid == 0u, "init restores ROM + clear valid");
/* stub 计数器是测试侧累计量,init 不清它——用增量断言 */
{
const int e0 = g_caltest_err_count;
s_kcfg.k[0] = 30000000L;
(void)adc_cal_apply(ADC_SEM_UA, 1);
CHECK(g_caltest_err_count == e0 + 1, "ERR window restarts after init (delta 1)");
}
}
/* TC-ADCCAL-014:b 生效(经 set 正规路径) */
static void tc_00a_b_effect(void) {
printf("[TC-ADCCAL-014] zero offset b via set path\n");
reset_all();
adc_cal_init();
CHECK(adc_cal_set(ADC_SEM_IA, 10000000L, -77L) == 0, "set IA b=-77");
CHECK(near_d(adc_cal_apply(ADC_SEM_IA, 1077), exp_q16(1077, -77, 10000000)),
"IA apply = (code-77)*ROMk");
}
/* TC-ADCCAL-015:两点标定公式 + 写回往返 */
static void tc_00b_two_point(void) {
int32_t kq = 0, bb = 0;
const int32_t k0 = 10000000L;
const int32_t b0 = -30L;
int32_t c1 = -250, c2 = 32000;
int32_t v1, v2;
int rc;
printf("[TC-ADCCAL-015] two-point formula recovers synthetic k/b\n");
/* 合成测量:v = (c + b0) * k0/65536 µV(lround 模拟量化读数) */
v1 = (int32_t)my_lround(((double)c1 + (double)b0) * ((double)k0 / 65536.0));
v2 = (int32_t)my_lround(((double)c2 + (double)b0) * ((double)k0 / 65536.0));
rc = adc_cal_two_point(c1, c2, v1, v2, &kq, &bb);
CHECK(rc == 0, "two_point ok");
CHECK(kq > (k0 - 5) && kq < (k0 + 5), "k recovered within +-5 q16");
CHECK(bb > (b0 - 3) && bb < (b0 + 3), "b recovered within +-3 codes");
/* 写回往返:set 后 apply(c2) 应还原 v2(µV 级) */
reset_all();
adc_cal_init();
CHECK(adc_cal_set(ADC_SEM_UA, kq, bb) == 0, "set recovered coeffs");
CHECK(near_d(adc_cal_apply(ADC_SEM_UA, c2), (double)v2 / 1.0e6), "roundtrip v(c2) = measured");
printf("[TC-ADCCAL-015] two_point guards\n");
CHECK(adc_cal_two_point(100, 100, 0L, 100000L, &kq, &bb) == -1, "dc=0 rejected");
CHECK(adc_cal_two_point(100, 200, 50000L, 50000L, &kq, &bb) == -1, "dv=0 rejected");
CHECK(adc_cal_two_point(100, 200, 100000L, 50000L, &kq, &bb) == -1, "inverted slope rejected");
CHECK(adc_cal_two_point(100, 200, 0L, 1000L, NULL, &bb) == -1, "NULL k rejected");
CHECK(adc_cal_two_point(100, 200, 0L, 1000L, &kq, NULL) == -1, "NULL b rejected");
/* 真零点 +9000 码 > B_MAX:斜率对但 b 越界 -> -2(先查前端,不用系数凑) */
v1 = (int32_t)my_lround((100.0 + 9000.0) * ((double)k0 / 65536.0));
v2 = (int32_t)my_lround((2000.0 + 9000.0) * ((double)k0 / 65536.0));
CHECK(adc_cal_two_point(100, 2000, v1, v2, &kq, &bb) == -2, "b beyond +-8192 -> -2");
}
/* TC-ADCCAL-016:贴轨门限表 */
static void tc_00c_rail_ths(void) {
int32_t lo = 0, hi = 0;
printf("[TC-ADCCAL-016] rail thresholds by source\n");
CHECK(adc_cal_rail_ths(ADC_SEM_UA, &lo, &hi) == 0 && hi == 32112L && lo == -32112L,
"TPAFE band = +-98% half-FS");
CHECK(adc_cal_rail_ths(ADC_SEM_UB, &lo, &hi) == 0 && hi == 4013L && lo == 81L,
"STM32 band = 98%/2% of 4095");
CHECK(adc_cal_rail_ths(ADC_SEM_TEMP, &lo, &hi) == -1, "TEMP not rail-watched");
CHECK(adc_cal_rail_ths(ADC_SEM_UA, NULL, &hi) == -1, "NULL guard");
}
/* TC-ADCCAL-017:init 幂等 */
static void tc_00d_idempotent(void) {
printf("[TC-ADCCAL-017] re-init restores defaults\n");
reset_all();
adc_cal_init();
(void)adc_cal_set(ADC_SEM_UA, 12345678L, 5L);
CHECK(near_d(adc_cal_apply(ADC_SEM_UA, 1000), exp_q16(1000, 5, 12345678)), "pre-check tuned");
adc_cal_init(); /* 幂等重入:RAM 复位 + 再注册 */
CHECK(near_d(adc_cal_apply(ADC_SEM_UA, 1000), exp_tpafe(1000)), "re-init back to ROM");
CHECK(adc_cal_flags(ADC_SEM_UA) == 0u, "re-init clears VALID too");
}
/* ===================== 用例(引擎集成组,卡 4 DoD) ===================== */
/* TC-ADCCAL-010:文件值经 load 生效 + VALID */
static void tc_010_file_apply(void) {
printf("[TC-ADCCAL-010] file values applied via cfg_file_load\n");
reset_all();
adc_cal_init();
vfs_put("[adc_cal]\r\nk_ua=12000000\r\nvalid=1\r\n"
"[adc_cal_ofs]\r\nb_ua=-40\r\n");
CHECK(cfg_file_load() == 0, "load ok");
CHECK(adc_cal_k_uv(ADC_SEM_UA) > 183.105f && adc_cal_k_uv(ADC_SEM_UA) < 183.106f,
"UA k = 12000000 q16 = 183.10546875 uV");
CHECK(near_d(adc_cal_apply(ADC_SEM_UA, 6557), exp_q16(6557, -40, 12000000)),
"apply uses file k and negative file b");
CHECK((adc_cal_flags(ADC_SEM_UA) & ADC_CAL_F_VALID) != 0u, "VALID from file bitmap");
/* 未列键保留默认 */
CHECK(adc_cal_k_uv(ADC_SEM_UB) > 805.663f && adc_cal_k_uv(ADC_SEM_UB) < 805.665f,
"UB untouched keeps ROM");
}
/* TC-ADCCAL-011:越界文件值由哨兵兜住(含位图说谎) */
static void tc_011_file_bad(void) {
printf("[TC-ADCCAL-011] out-of-band file k -> sentinel ROM fallback, VALID denied\n");
reset_all();
adc_cal_init();
vfs_put("[adc_cal]\r\nk_ua=999\r\nk_ub=-999\r\nvalid=7\r\n");
CHECK(cfg_file_load() == 0, "load ok (values structurally valid INT)");
CHECK(near_d(adc_cal_apply(ADC_SEM_UA, 500), exp_tpafe(500)), "UA sanitized to ROM");
CHECK(near_d(adc_cal_apply(ADC_SEM_UB, 500), exp_stm32(500)), "UB sanitized to ROM");
CHECK(adc_cal_flags(ADC_SEM_UA) == 0u, "UA VALID denied despite bitmap bit (k out of band)");
CHECK(adc_cal_flags(ADC_SEM_UB) == ADC_CAL_F_CLIPPED, "UB keeps only CLIPPED");
CHECK(g_caltest_err_count >= 2, "both sanitizations logged");
}
/* TC-ADCCAL-012:段损坏(超长文件)→ 回退默认且 flags 无 VALID(卡 4 DoD 原句) */
static void tc_012_file_corrupt(void) {
char big[CFG_FILE_MAX_LEN + 64];
size_t i;
printf("[TC-ADCCAL-012] oversized corrupt file -> ROM defaults + no VALID\n");
reset_all();
adc_cal_init();
/* 先把有效值 + valid 位图写入 RAM(模拟曾标定过),再用超长文件触发引擎复位 */
(void)adc_cal_set(ADC_SEM_UA, 11000000L, 3L);
for (i = 0u; i < (sizeof(big) - 1u); i++) {
big[i] = 'A';
}
big[sizeof(big) - 1u] = '\0';
vfs_put(big);
CHECK(cfg_file_load() == 0, "load survives corruption");
CHECK(near_d(adc_cal_apply(ADC_SEM_UA, 4096), exp_tpafe(4096)), "corrupt -> ROM coefficients");
CHECK(adc_cal_flags(ADC_SEM_UA) == 0u, "corrupt -> no VALID anywhere (DoD wording)");
CHECK(s_syslog_count >= 1, "corruption left self-test trail");
}
/* TC-ADCCAL-013:段缺失 → 引擎默认(=ROM),VALID 全 0 */
static void tc_013_section_missing(void) {
printf("[TC-ADCCAL-013] missing sections -> factory ROM, vl=0\n");
reset_all();
adc_cal_init();
vfs_put("; only a comment, no managed sections\n");
CHECK(cfg_file_load() == 0, "load ok with unrelated file");
CHECK(near_d(adc_cal_apply(ADC_SEM_IN, 2048), exp_stm32(2048)), "IN still ROM");
CHECK(adc_cal_flags(ADC_SEM_IA) == 0u, "no VALID with missing sections");
}
/* ===================== main ===================== */
int main(void) {
printf("=== ADC Cal Layer Tests ===\n");
tc_001_init_registers();
tc_002_004_apply();
tc_005_temp();
tc_006_default_flags();
tc_007_k_uv();
tc_008_set();
tc_009_sanitize();
tc_00a_b_effect();
tc_00b_two_point();
tc_00c_rail_ths();
tc_00d_idempotent();
tc_010_file_apply();
tc_011_file_bad();
tc_012_file_corrupt();
tc_013_section_missing();
printf("\n=== ADC Cal Layer Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);
return (s_failed == 0) ? 0 : 1;
}