b623a406ad
这四笔在主树里已经互相纠缠(同一次 @build 编译、同一份 .htm 度量),拆成四笔就不
存在任何一个被真编过的中间态,所以合一笔入库;下面按模块分段陈述,作者与出证归属
逐段写明。
—— 一、崩溃证据路径的整行独占收尾(log-maint-writer,task-26)——
上午那笔 1d4b6a17 之后,stack_report 报 iec104Task 静态最坏链 1,544 / margin 1,016,
低于 1,024 的 ISR 底线。真因不是新增局部缓冲,而是本模块与内核临界区之间新长出
一条环:taskEXIT_CRITICAL ⇒ vPortExitCritical ⇒ configASSERT ⇒ vApplicationAssertFailed
⇒ dbg_console_write_raw ⇒ 回到本模块。armlink 把这一整坨计入**每一个含临界区的
任务链**(vApplicationAssertFailed 的 Max Depth 由约 40 B 涨到 128 B + In Cycle),
所以九条链同涨约 80 B——同涨本身就排除了任何单个调用方。
改法两处:① 本模块三处窗口(取凭证 / 还凭证 / 整行入环)改用 CON_CRIT_ENTER/EXIT
(PRIMASK 保存恢复到局部变量;注释写全为什么不能直接 __enable_irq()——嵌套时会
替别人打开任务级临界区——以及屏蔽面变宽是有意为之);② 异常/终点钩子里那对
自证空转的 begin/end 摘掉(异常态 begin 放行但不持有、end 是幂等空操作,调它只有
成本没有收益),任务态 STALL 行保留 begin/end,这个区分写进两处注释与文档 §4.4.1。
实测四棵树逐个 .htm 解析(身份钉 tree + Linker run 时刻 + 产物来源):A=77779d49
链 1,456;B=1d4b6a17 链 1,464 / margin 1,096 PASS;C=含未提交钩子接线的工作树
1,544 / 1,016 FAIL;D=本轮修复后(Linker run 22:52)回到 **1,464 / 1,096 PASS**,
九条链齐降 80 B。断环图证:vPortExitCritical 由 136 B + In Cycle 变 8 B 且不再
In Cycle,con_tx_release 子树 152→8;Error_Handler 回到 280 + In Cycle = A 树同值
(既有发送环无人拆动)。顺带删掉 Src/freertos.c 崩溃路径里最后一个 HAL_Delay(200)
(该文件真实调用现为 0)。
测试:console_log 152→**153** 条(只增不减);换钉不删防——015 现在钉「PRIMASK
窗口 2 次、深度 1、内核临界区 0 次」,新钉 015c 绝不等待,160 注入点搬到
con_pm_hook,176 强化到未调度路径连 PRIMASK 窗口都不开;红线水位
con_pm_at_tx_max 与 con_uart_crit_at_tx_max 双 0;判据⑦(延后的行与当场直发逐
字节相同)170b/171/178 仍全绿。作者自报并纠正一处:换钉时曾把 TC-CONLOG-015b 改
编号,而 015b 在生产注释与文档里都被引用——已恢复「编号不退役、语义搬过去」。
覆盖率 App/log/console_log.c 199/200 → 256/257 行、21/21 函数(只增)。
—— 二、未标定闭锁判据收成单点访问器(ui-param-writer,task-16)——
「UA/UN/IA 三相 VALID 全置位才允许跑判据」这条门控原先在两处各自镜像实现
(protect_task.c 的 s_cal_locked 与 ui_data_feed.c 的 feed_cal_block),改一处另一处
静默失效且无门禁可照。现新增 adc_cal_prot_blocked() 落在 App/algo/adc_cal,DTU_BENCH
特例收进一处;两个消费者同批改调、本地副本删除。作者还查实并纠正了我先前一句
错判:全仓 App/app→App/task 的 include 实为 0 条,故访问器不能落在 protect_task。
新增 TC-ADCCAL-018 八条(含冷启动即闭锁),adc_cal 套件 99/99。
—— 三、共享卷锁五态可观测性(ui-param-writer,task-17)——
原状比登记簿写的更糟:五个可区分态(未调用 / try 超时 / 分片超时 / fail-open /
未持锁却 release)生产侧零留痕,且 vol_lock.c 的返回值注释与代码相反——
fail-open(锁没建成、互斥已失效却在读写盘)被返 1 **谎报成取到锁**,而那正是
vol_lock.h 记录的台架实证故障(并发写坏 FTL → 喂狗点饥饿 → IWDG 误复位)的前置。
锁层加四个 volatile 计数(16 B 静态,零新局部、零新 callee、零日志、零 FatFs)与
不取锁的 vol_lock_get_stats();13 处 try 调用点一字未动。**刻意不在锁层记时刻**:
记时刻要读 tick,等于给 defaultTask 取锁链添帧——诊断不值那几个字节;消费方若
自盖时间戳,那只能是被观测到的时刻而非发生时刻,.h 里已写明不得写成首次/末次。
四套本地假件各加 s_last_wait_to(字段名逐字一致),把「喂狗点取锁一律 timeout=0」
与「ftpTask 分片一律 100 ms」从注释纪律升级成会 FAIL 的断言;红-绿反证:临时把
sys_log_mcu.c 的 0u 改 50u → TC-SYSLOG-010/012 双红,把 lftpd_io.c 的 100u 改 0u →
TC-LFTPIO-034 红,两处按原串改回并用 SHA-256 证明逐字节还原。TC-VOLLOCK-015~019
共 45/45(无新桩)。
—— 四、UI 死接口清理(ui-maint-writer,task-6 收尾)——
删掉 ui_data_maint_ready():全仓零消费者、只有它自己的用例在读。**更值得记的是
它为什么能活过一整轮评审**:头注释写的是「页面用它决定这页能不能操作」,而页面
从来没用过它——注释陈述的是意图,评审读的是意图,门禁读的是可达路径,两者都不
反驳「意图未实现」。作者对「连用例一起删」做了显式抗命:TC-UIDATA-025 的 13 条里
只有 4 条读那个死口,另 9 条钉的是真防线(六口未注入安全返回 / 残表整表拒绝 /
被拒不覆盖已生效的好表),故删冗余的 025a、把 025i/025k/025m 换成用 reset_req()
的可观察返回值判定,性质不丢。ui_data 套件 244 → 243(净减 1),黄金 40 页与
池 5448 B 未变(该刀零像素影响,参考图无 diff)。
—— 门禁与未跑项(本笔如实留账)——
cubemx-survival 基线已 --write(漂移 6 项全部是上述意图内区域:断言/栈溢出钩子
实现、看门狗模块行等),基线文件同笔入库,随后 --check 通过。
本笔未跑的钩子只有两条:mem-numbers 与 mem-budget——磁盘 .map 出自 22:52 那次
构建、含尚未入库的 UI 与工具改动,与 HEAD 台账必然不同码;按仓库既有约定允许
--no-verify + 正文留证,回填在紧随的台账一笔统一做(含 ram_baseline --update 与
覆盖率基线两条 [NOTE]:ui_data.c 65→316、iec104_comm.c 36/36)。
两条已挂着的账:① id-check 不可判定引用 1,162 / 上限 1,166,**余量只剩 4**,
主要吃进者是我今天推进的 UI 那笔(68 条文本断言的注释引用),账在 Lead;
② 崩溃注入腿(故意复位)台架复验未跑,需先由代码道提交触发器,且判据只判原子性
不判 [ERR]=0——IPSR 路径的主张归主机侧套件,硬件照不出来。
产物身份(按 D 表要求钉死,避免数字与树脱钩):本笔包含的固件树就是那张 D 表对应的
那一棵——MDK-ARM/.../STM32F407-Demo.htm 的 mtime = 2026-10-26 22:52:12(.htm/.map 按
约定不入库,故把时刻写进正文)。提交前在本树复跑 stack_report.py:exit 0,
iec104Task 1,464 / margin 1,096(42%)PASS、defaultTask 3,040 / 2,080(40%)PASS,
与 D 表逐项一致;本次运行不含任何未提交固件源码之外的新改动。
552 lines
25 KiB
C
552 lines
25 KiB
C
/*
|
||
* 模块名称:通道标定层(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-ADCCAL-018 未标定闭锁单点 adc_cal_prot_blocked()(道F task-16):开机态必须
|
||
* 闭锁 / 两路齐第三路缺仍闭锁(全或无)/ 三路齐放行且 UB 仍无 VALID
|
||
* (证明它是"三路"而不是 feed_eng 那个"8 路")/ 复位后立刻回闭锁
|
||
* (现查、无自有状态可残留)
|
||
*
|
||
* 通过:全部 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");
|
||
}
|
||
|
||
/* TC-ADCCAL-018:未标定闭锁判定单点(「数据可信」卡 5 / 道F task-16)
|
||
* 为什么值得单独钉:这条谓词此前在 App/task/protect_task.c 与
|
||
* App/app/ui_data_feed.c 各写一份,**没有任何一份被主机测到**(ui_data 套件只编
|
||
* 快照层,不编组装器)。收敛到本层后它跟着被测源文件一起进来,第一次拿到
|
||
* 机器证据。
|
||
* 注:DTU_BENCH 旁路这一档在主机侧测不到(宿主构建不定义它,编译进来的就是产品
|
||
* 路径),它的正确性由"全仓只剩一处 #ifndef"+ 台架目检保证,不假装有覆盖。 */
|
||
static void tc_018_prot_blocked(void) {
|
||
printf("[TC-ADCCAL-018] adc_cal_prot_blocked: boot blocks / three required / stateless\n");
|
||
|
||
/* ① 开机首拍前(任何 set 之前)必须判闭锁。**这条最贵**:若实现偷懒回读了
|
||
* 某个"上拍态"变量,刚上电那拍就是 0 = 谎称已标定,屏上「未标定」徽标
|
||
* 不出现,现场读到的是"改了定值就会动作" */
|
||
reset_all();
|
||
adc_cal_init();
|
||
CHECK(adc_cal_prot_blocked() == 1u, "boot (no calibration at all) -> blocked");
|
||
|
||
/* ② 全或无:两路齐、第三路缺 → 仍闭锁(不是多数表决,也不是"任一路齐就放行") */
|
||
CHECK(adc_cal_set(ADC_SEM_UA, 10500000L, 0L) == 0, "UA in-band accepted");
|
||
CHECK(adc_cal_prot_blocked() == 1u, "UA only -> still blocked");
|
||
CHECK(adc_cal_set(ADC_SEM_UN, 10000000L, 0L) == 0, "UN in-band accepted");
|
||
CHECK(adc_cal_prot_blocked() == 1u, "UA+UN with IA missing -> still blocked");
|
||
|
||
/* ③ 三路齐 → 放行;同一时刻 UB 仍无 VALID,证明本判定用的是**三路**口径,
|
||
* 不是电气 8 路全 VALID(那是 eng 的量纲口径,两者不可互换) */
|
||
CHECK(adc_cal_set(ADC_SEM_IA, 10000000L, 0L) == 0, "IA in-band accepted");
|
||
CHECK(adc_cal_prot_blocked() == 0u, "UA/UN/IA all VALID -> released");
|
||
CHECK((adc_cal_flags(ADC_SEM_UB) & ADC_CAL_F_VALID) == 0u,
|
||
"UB still uncalibrated at release (3-channel rule, not the 8-channel eng rule)");
|
||
|
||
/* ④ 现查、无残留:init 复位(等价于配置被重载/文件损坏回退)后立刻回闭锁 */
|
||
adc_cal_init();
|
||
CHECK(adc_cal_prot_blocked() == 1u, "re-init -> blocked again (no cached state)");
|
||
CHECK(adc_cal_prot_blocked() == 1u, "repeat call stable (idempotent pure read)");
|
||
}
|
||
|
||
/* ===================== 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();
|
||
tc_018_prot_blocked();
|
||
|
||
printf("\n=== ADC Cal Layer Tests: %d/%d PASSED (failed=%d) ===\n",
|
||
s_passed, s_total, s_failed);
|
||
return (s_failed == 0) ? 0 : 1;
|
||
}
|