merge: v2-on-ui 追平至 task/P1-10-test-fw(含 A-3 卡5~7 与 B-3 记录 reader)
按 lead 信号 sync 追平:主干 59da40b(cal-lockout-dim)+ 891ca81(UI-rec-reader)+ 383b450(台账与凭证回填)合入本任务分支,为下一轮台架做准备(台架镜像必须建立在 追平后的代码上,否则烧进去的 hex 与主干不同码,体积门禁的凭证也对不上)。 唯一冲突:scripts/size_attestation.json —— **取主干侧**(383b450 刚按合并后的固件重盖 过,数字更新;本分支侧是7c4628b的 v2 格式戳,覆盖的是追平前的树)。取完即 `git add` 解决,没有手工拼接。**已知副作用**:主干那份是 v1 格式(schema=1),而本 分支的 check_size_gate.py 已升到 schema=2 → 在下一步 `ram_baseline.py --update` 重盖 之前,ci_local 的 L0c 会按设计报"凭证 schema 不符"。这不是回归,是合并两侧脚本版本 差一步,下面第 4 步就把它治好(也正是要请 lead 合这笔的原因:合完主干两边同版)。 合并后产品构建(实跑,非推演):@build 0 错误 0 警告, Code=215744 RO=21396 RW=1224 ZI=131984 —— 与 lead 在信号里给的主干数字**逐项相同**; RW_IRAM1=75,784 B / 57.82%(追平前 75,632 B / 57.70%,+152 B 与"两笔合计 +152B"吻合)、 RW_CCM=57,376 B / 87.89% 未动、ER_IROM1 22.62%。说明本树固件内容已与主干同码。 本分支带来的(前四笔已在分支上,这里只登记合并面): -7c4628b体积凭证指纹改索引 blob;16917bf CH395F 三档实测 + 阶段开关注释陷阱; -adabb6a主机单测/覆盖率产物按工作树分家 + runs 独占(task-10); -a9ba8ec看门狗 HW 层掩码死分支复活(App/util,lead 背书写域)+ 存活基线 --write; -5f1057a套件 forbidden 换显式四 Fault 标签(GD5F 旧豁免的漏口一并堵上)。 与主干侧的文件交集只有 size_attestation.json 一处,已按上解决;.pre-commit-config.yaml 本轮是主干侧改的(道A 加了钩子相关条目),本分支没碰它,无并集冲突。 gate:本笔合并提交走完整 pre-commit(含 16 根单测钩子与 test-coverage 全量覆盖率 基线比对);未用 --no-verify。
This commit is contained in:
@@ -0,0 +1,510 @@
|
||||
/*
|
||||
* 模块名称:通道标定层(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;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""通道标定层(L1.5,adc_cal)主机侧单元测试运行器(L0.5,纯标准库)。
|
||||
|
||||
做什么:
|
||||
用本机 MSVC 编译 test/host/adc_cal/adc_cal_test.c(它以 #include 纳入
|
||||
**生产源文件** App/algo/adc_cal.c 与 App/cfg/cfg_file.c),在 PC 上离线跑
|
||||
Q16 系数折算 / 三态 flags / 合法带哨兵回退 / 两点标定公式 / 两段 cfg
|
||||
落盘契约(含段损坏回退),失败即非 0 退出。零硬件、零烧录、毫秒级。
|
||||
工具链探测 / 编译运行全部委托 test/host/common/unit_test_harness.py。
|
||||
|
||||
为什么要主机侧单测(而不是只用板端套件):
|
||||
标定折算的判据是"读数 = 系数 × 码值"——板端**无标准源**(数据可信 §2.6),
|
||||
系数错、Q16 舍入错、哨兵漏放、负零点落盘错,在台架上全都看不出来;
|
||||
主机侧用独立推导的期望值 + 坏文件注入把它们一次性钉死。
|
||||
|
||||
用法(仓库根目录):
|
||||
python test/host/adc_cal/run_unit_test.py
|
||||
|
||||
退出码:0=PASS / 1=测试失败 / 2=环境错误。
|
||||
设计依据:docs/任务规划/数据可信.md 卡 2 / 卡 4(道A);
|
||||
创建日期:2026-10-25
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(HERE)))
|
||||
sys.path.insert(0, os.path.join(ROOT, "test", "host", "common"))
|
||||
|
||||
from unit_test_harness import TestBuild, run_host_unit_test # noqa: E402
|
||||
|
||||
# 顺序敏感:stub 先于生产目录(覆盖 dbg_log.h)。cfg_file.h / cfg_file_store.h /
|
||||
# adc_input.h / sys_log.h 用真实头;adc_input_desc 与 syslog_post 由测试单元给
|
||||
# 强符号桩(后者供 cfg_file.c 的错误留痕路径)。
|
||||
BUILD = TestBuild(
|
||||
name="ADC Cal Layer Tests",
|
||||
src=os.path.join(HERE, "adc_cal_test.c"),
|
||||
include_dirs=[
|
||||
os.path.join(HERE, "stub"),
|
||||
os.path.join(ROOT, "App", "algo"),
|
||||
os.path.join(ROOT, "App", "cfg"),
|
||||
os.path.join(ROOT, "App", "log"),
|
||||
HERE,
|
||||
],
|
||||
summary_marker="=== ADC Cal Layer Tests:",
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run_host_unit_test(BUILD))
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 主机侧单测桩:dbg_log.h
|
||||
* 模块功能:为 adc_cal.c / cfg_file.c 提供主机可编译的日志宏(映射为计数)。
|
||||
* ⚠ 仅供 test/host/adc_cal/ 使用,不参与 MCU 构建。同 cfg_file 套件先例。
|
||||
* 创建日期:2026-10-25
|
||||
*/
|
||||
|
||||
#ifndef DBG_LOG_H
|
||||
#define DBG_LOG_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
extern int g_caltest_err_count;
|
||||
extern int g_caltest_info_count;
|
||||
extern int g_caltest_debug_count;
|
||||
|
||||
#define DBG_INFO(fmt, ...) do { g_caltest_info_count++; } while (0)
|
||||
#define DBG_DEBUG(fmt, ...) do { g_caltest_debug_count++; } while (0)
|
||||
#define DBG_ERROR(fmt, ...) do { g_caltest_err_count++; } while (0)
|
||||
|
||||
#endif /* DBG_LOG_H */
|
||||
@@ -31,6 +31,14 @@
|
||||
* TC-ADCHEALTH-015 帧年龄纯判据:tick 与序号跨 0xFFFFFFFF 不误判、停滞仍判老
|
||||
* TC-ADCHEALTH-016 帧年龄纯判据:序号倒退按「有变化」处理,不判老(判据分工)
|
||||
* TC-ADCHEALTH-017 帧年龄纯判据:两个 age 对象互不干扰;空指针不判老
|
||||
* —— L5-2 残余「码值越界(贴轨)检出」(2026-10-25 道A,卡 2 分支加入)——
|
||||
* TC-ADCHEALTH-018 释放沿后连续 20 拍贴上轨 -> 锁存 + 边沿只报一次
|
||||
* TC-ADCHEALTH-019 无释放沿(开机/空载恒 0)永不锁存(合法工况不误报)
|
||||
* TC-ADCHEALTH-020 中带样本清零连续计数(19+19 不锁);触轨含等号边界
|
||||
* TC-ADCHEALTH-021 上下轨交替互清对侧计数,永不锁存
|
||||
* TC-ADCHEALTH-022 下轨带对称锁存(STM32 削顶 = 贴底签名)
|
||||
* TC-ADCHEALTH-023 rail_init 清 sticky 与释放沿记录;health_init 内含复位;
|
||||
* 通道越界忽略不污染
|
||||
*
|
||||
* 通过:全部 TC PASS(main 返回 0)。
|
||||
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
|
||||
@@ -474,6 +482,105 @@ static void tc_age_two_observers(void) {
|
||||
"TC-ADCHEALTH-017 NULL age never reports stale (no false lock)");
|
||||
}
|
||||
|
||||
/* ===================== L5-2 贴轨观察器(2026-10-25 道A) ===================== */
|
||||
|
||||
/* 便捷喂数:连发 n 拍同值,返回最后一次 watch 的边沿结果 */
|
||||
static uint32_t feed(uint8_t ch, int32_t code, int n) {
|
||||
uint32_t edge = 0u;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < n; i++) {
|
||||
edge = adc_health_rail_watch(ch, code, -32112, 32112);
|
||||
}
|
||||
return edge;
|
||||
}
|
||||
|
||||
/* TC-ADCHEALTH-018:释放沿后连续 20 拍贴上轨 -> 锁存,边沿只报一次 */
|
||||
static void tc_rail_latch_after_release(void) {
|
||||
uint32_t edge;
|
||||
|
||||
adc_health_rail_init();
|
||||
(void)feed(0, 0, 1);
|
||||
edge = feed(0, 32500, 19);
|
||||
CHECK(edge == 0u && adc_health_rail_sticky() == 0u,
|
||||
"018: 19 hi beats after release -> no latch");
|
||||
edge = feed(0, 32600, 1);
|
||||
CHECK(edge == 1u, "018: 20th consecutive hi beat -> edge");
|
||||
CHECK(adc_health_rail_sticky() == (1uL << 0), "018: sticky bit0");
|
||||
edge = feed(0, 32700, 5);
|
||||
CHECK(edge == 0u, "018: no repeat edge after latched");
|
||||
}
|
||||
|
||||
/* TC-ADCHEALTH-019:无释放沿(恒 0)永不锁存——空载/开机合法工况 */
|
||||
static void tc_rail_static_zero_ok(void) {
|
||||
adc_health_rail_init();
|
||||
(void)feed(3, 0, 30);
|
||||
CHECK(adc_health_rail_sticky() == 0u,
|
||||
"019: static in-band zero (no release) never latches");
|
||||
}
|
||||
|
||||
/* TC-ADCHEALTH-020:计数被中带清零;再连续 20 才锁存(含触轨带含等号边界) */
|
||||
static void tc_rail_reset_by_mid(void) {
|
||||
uint32_t edge;
|
||||
|
||||
adc_health_rail_init();
|
||||
(void)feed(1, 0, 1);
|
||||
(void)feed(1, 32113, 19);
|
||||
(void)feed(1, 0, 1);
|
||||
edge = feed(1, 32112, 19);
|
||||
CHECK(edge == 0u && adc_health_rail_sticky() == 0u,
|
||||
"020: mid sample resets run counter (19+19 never hits 20)");
|
||||
edge = feed(1, 32112, 1);
|
||||
CHECK(edge == 1u && adc_health_rail_sticky() == (1uL << 1),
|
||||
"020: fresh 20-run latches; boundary code == hi_th counts as rail");
|
||||
}
|
||||
|
||||
/* TC-ADCHEALTH-021:上下轨交替互不清锁存但各自清零对侧计数 */
|
||||
static void tc_rail_alt_no_latch(void) {
|
||||
int i;
|
||||
|
||||
adc_health_rail_init();
|
||||
(void)feed(2, 0, 1);
|
||||
for (i = 0; i < 60; i++) {
|
||||
(void)adc_health_rail_watch(2, ((i & 1) != 0) ? 32500 : -32500, -32112, 32112);
|
||||
}
|
||||
CHECK(adc_health_rail_sticky() == 0u,
|
||||
"021: alternating rails clear each other, never latch");
|
||||
}
|
||||
|
||||
/* TC-ADCHEALTH-022:下轨对称路径(STM32 削顶 = 贴底签名,同样先要释放沿) */
|
||||
static void tc_rail_low_side(void) {
|
||||
uint32_t edge = 0u;
|
||||
int i;
|
||||
|
||||
adc_health_rail_init();
|
||||
(void)adc_health_rail_watch(5, 2000, 81, 4013);
|
||||
for (i = 0; i < 20; i++) {
|
||||
edge |= adc_health_rail_watch(5, 0, 81, 4013);
|
||||
}
|
||||
CHECK(edge == 1u && adc_health_rail_sticky() == (1uL << 5),
|
||||
"022: low rail band latches symmetrically (ch5)");
|
||||
}
|
||||
|
||||
/* TC-ADCHEALTH-023:rail_init 全清(含释放沿记录);health_init 内含;越界通道忽略 */
|
||||
static void tc_rail_reset_and_oob(void) {
|
||||
adc_health_rail_init();
|
||||
(void)feed(0, 0, 1);
|
||||
(void)feed(0, 32500, 20);
|
||||
CHECK(adc_health_rail_sticky() != 0u, "023: pre-check latch happens");
|
||||
adc_health_rail_init();
|
||||
CHECK(adc_health_rail_sticky() == 0u, "023: rail_init clears sticky");
|
||||
(void)feed(0, 32500, 25);
|
||||
CHECK(adc_health_rail_sticky() == 0u, "023: release history cleared too");
|
||||
(void)feed(0, 0, 1);
|
||||
(void)feed(0, 32500, 20);
|
||||
CHECK(adc_health_rail_sticky() != 0u, "023: pre-check relatch for health_init test");
|
||||
adc_health_init();
|
||||
CHECK(adc_health_rail_sticky() == 0u, "023: adc_health_init() also clears rail");
|
||||
CHECK(adc_health_rail_watch(9, 32767, -32112, 32112) == 0u,
|
||||
"023: ch OOB ignored, no sticky pollution");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("=== ADC Health Tests ===\n");
|
||||
|
||||
@@ -498,6 +605,13 @@ int main(void) {
|
||||
tc_tick_wrap();
|
||||
tc_null_guard();
|
||||
tc_reinit();
|
||||
printf("-- L5-2 rail watch --\n");
|
||||
tc_rail_latch_after_release();
|
||||
tc_rail_static_zero_ok();
|
||||
tc_rail_reset_by_mid();
|
||||
tc_rail_alt_no_latch();
|
||||
tc_rail_low_side();
|
||||
tc_rail_reset_and_oob();
|
||||
|
||||
printf("\n=== ADC Health Tests: %d/%d PASSED (failed=%d) ===\n",
|
||||
s_passed, s_total, s_failed);
|
||||
|
||||
@@ -511,14 +511,15 @@ static void tc_cov_yc(void)
|
||||
handshake(now);
|
||||
s_f_yc[0] = 100.0f;
|
||||
iec104_link_poll(&g_lk, now); /* 快照 = 100 */
|
||||
s_f_yc[0] = 105.0f; /* 死区 10 内 */
|
||||
s_f_yc[0] = 100.4f; /* 死区 0.5 V 内(卡 6 二次值口径:0.5 %×100 V,
|
||||
* 旧拟定码值死区 10 已由「数据可信」卡 6 重定) */
|
||||
iec104_link_poll(&g_lk, now + 100u);
|
||||
CHECK(cap_i_count() == 0, "TC-008 inside the deadband nothing is uploaded");
|
||||
s_f_yc[0] = 130.0f; /* 130-105=25 > 10 */
|
||||
s_f_yc[0] = 101.25f; /* 101.25-100.4=0.85 > 0.5 */
|
||||
iec104_link_poll(&g_lk, now + 200u);
|
||||
CHECK((cap_i_count() == 1) && (cap_i(0, &c) == 0) && (c.type == 13u)
|
||||
&& (c.cot == IEC104_COT_SPONT) && (c.ioa == 0x4011u)
|
||||
&& (iec104_get_float(c.obj) == 130.0f), "TC-008 crossing the deadband uploads");
|
||||
&& (iec104_get_float(c.obj) == 101.25f), "TC-008 crossing the deadband uploads");
|
||||
}
|
||||
|
||||
static void feed_rd(uint32_t now)
|
||||
|
||||
@@ -0,0 +1,475 @@
|
||||
/*
|
||||
* 模块名称:量纲回归主机侧单测(「数据可信」卡 7 · 道A)
|
||||
* 被测对象:真实源文件(#include 原样纳入,全链路 L1.5→L2→L3):
|
||||
* adc_cal.c + prot_set.c + sample_ring.c + phasor.c + freq.c +
|
||||
* prot_elm.c
|
||||
* 平台依赖桩:dbg_log.h(stub/)、cfg_file_register(捕获计数)、
|
||||
* adc_input_desc(V0.1 权威来源表,同 adc_cal 套件)。
|
||||
*
|
||||
* 本卡的存在理由(短板评测 + 「数据可信」§2.2):台架此前"通过"是因为没有
|
||||
* 一条判据检查**不该动作时不动作**——"码值当工程量"的错配曾让 OC 拿 ~11000
|
||||
* 码值比 10 A 定值恒假动作。本套件把这条固化成回归:用例从 **ADC 码值** 出发、
|
||||
* **显式写出码值↔二次值换算**,走真实 折算→采样环→全周傅氏→元件判据 链;
|
||||
* 任何一环把量纲弄丢(比如折算点被旁路),断言立刻翻转。
|
||||
*
|
||||
* ROM 系数覆写(#undef/#define):把"板级理论系数"替换为合成端子侧口径
|
||||
* (10 mV/码,K_DIM),模拟卡 12 标定完成后的 ROM/默认状态——合法带随之
|
||||
* 锚定合成值,adc_cal_set 走正规路径置 VALID,测试不白盒改引擎内存。
|
||||
*
|
||||
* 覆盖点(族 TC-HOST-PROTDIM,属主登记处 = 本目录):
|
||||
* TC-PROTDIM-001 标定链基线:0.5 A/100 V/0 V 注入 → 七类元件全静默(含
|
||||
* OL:0.5 < 1 A 定值)+ IA 相量有效值 = 0.5 A ±0.5 %
|
||||
* TC-PROTDIM-002 反错配对照:同一批码值,raw 直灌 → OC1 假动作(复现历史
|
||||
* 事故);经 adc_cal_apply → OC1 静默(折算点不可旁路)
|
||||
* TC-PROTDIM-003 OC1(10 A,dly=0):9.995 不动作 / 10.5 启动即动作 /
|
||||
* 9.6 滞回保持 / 9.49 释放
|
||||
* TC-PROTDIM-004 OC2(5 A,500 ms):6 A 启动,启动后 499 ms 不动作、
|
||||
* 501 ms 动作;4.8 保持(ret 4.75)、4.6 释放
|
||||
* TC-PROTDIM-005 OC3(2 A,3000 ms):同上时序(2.1 A)
|
||||
* TC-PROTDIM-006 OL(1 A,10 s):1.05 A 启动、9999/10001 ms 时序、
|
||||
* 0.96 保持(ret 0.95)、0.93 释放
|
||||
* TC-PROTDIM-007 OV(120 V,10 s):100 V 静默;121 V 启动、到点动作;
|
||||
* 115 保持(ret 114)、113 释放
|
||||
* TC-PROTDIM-008 UV(20 V,10 s,欠量方向):21 V 静默;19.9 V 启动、
|
||||
* 到点动作;20.5 保持(ret 21)、21.6 释放
|
||||
* TC-PROTDIM-009 3U0(15 V,5 s):14 V 静默;15.1 启动到点;
|
||||
* 14.4 保持(ret 14.25)、14.1 释放
|
||||
* TC-PROTDIM-010 聚合与独立性:OC1+OV 同拍动作位图 = 双位;撤 IA →
|
||||
* OC1 返回、OV 保持(互不污染)
|
||||
*
|
||||
* 通过:全部 TC PASS(main 返回 0)。
|
||||
* 创建日期:2026-10-25(道A 卡 7)
|
||||
*/
|
||||
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
/* 桩头先于生产源码 */
|
||||
#include "dbg_log.h"
|
||||
int g_dimtest_err_count = 0;
|
||||
int g_dimtest_info_count = 0;
|
||||
int g_dimtest_debug_count = 0;
|
||||
|
||||
#include "cfg_file.h"
|
||||
|
||||
static int s_reg_count = 0;
|
||||
|
||||
int cfg_file_register(const cfg_section_reg_t *p_reg) {
|
||||
(void)p_reg;
|
||||
s_reg_count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* V0.1 权威来源表(docs/硬件平台/ADC通道映射说明.md §3/§5,同 adc_cal 套件桩) */
|
||||
#include "adc_input.h"
|
||||
|
||||
static const adc_channel_desc_t s_desc_stub[ADC_SEM_COUNT] = {
|
||||
{ ADC_SRC_TPAFE, 6u }, { ADC_SRC_STM32, 14u }, { ADC_SRC_STM32, 3u },
|
||||
{ ADC_SRC_TPAFE, 5u }, { ADC_SRC_TPAFE, 2u }, { ADC_SRC_STM32, 6u },
|
||||
{ ADC_SRC_STM32, 1u }, { ADC_SRC_STM32, 2u }, { ADC_SRC_STM32, 5u },
|
||||
};
|
||||
|
||||
const adc_channel_desc_t *adc_input_desc(adc_semantic_t ch) {
|
||||
if ((unsigned)ch >= (unsigned)ADC_SEM_COUNT) {
|
||||
return NULL;
|
||||
}
|
||||
return &s_desc_stub[(unsigned)ch];
|
||||
}
|
||||
|
||||
/* ---- 测试框架(同 prot_elm 套件) ---- */
|
||||
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)
|
||||
|
||||
/* ---- 合成"标定后"板级口径(覆写 adc_cal.h 的 ROM 常量,必须在纳入 .c 之前) ----
|
||||
* K_DIM = 655360000 Q16 = 10000 µV/码 = 10 mV/码:
|
||||
* 电压类 100 V rms → 峰值 141.42 V → 14142 码(TPAFE 半量程内,量级刻意
|
||||
* 贴近卡 12 端子变比标定后的真实形态);电流类二次值经变换器落到同一定标。
|
||||
* B_IA = -30 码(零点偏移,经 apply 精确对消——验证 b 参与全链而非仅落盘)。 */
|
||||
#include "adc_cal.h"
|
||||
#undef ADC_CAL_K_Q16_TPAFE
|
||||
#undef ADC_CAL_K_Q16_STM32
|
||||
#define ADC_CAL_K_Q16_TPAFE (655360000L)
|
||||
#define ADC_CAL_K_Q16_STM32 (655360000L)
|
||||
#define K_DIM ADC_CAL_K_Q16_TPAFE
|
||||
#define B_IA_DIM (-30L)
|
||||
|
||||
/* ===================== 被测生产源文件 ===================== */
|
||||
#include "prot_set.c"
|
||||
#include "sample_ring.c"
|
||||
#include "phasor.c"
|
||||
#include "freq.c"
|
||||
#include "prot_elm.c"
|
||||
#include "adc_cal.c"
|
||||
|
||||
/* ===================== 波形/时基(同 prot_elm 套件 run() 级用例口径) ===================== */
|
||||
static uint32_t s_t = 0u;
|
||||
|
||||
static long my_lround(double x) { return (long)((x >= 0.0) ? (x + 0.5) : (x - 0.5)); }
|
||||
|
||||
/* 显式换算(卡 7 的核心手法,两处方向都要有):
|
||||
* code = round(eng[V 或 A] × 65536×1e6 / k_q16) − b (期望 → 码值)
|
||||
* eng = (code + b) × k_q16 / 65536 / 1e6 = adc_cal_apply (码值 → 工程量)
|
||||
* 上面两式必须互逆——若实现(adc_cal_apply)与期望推导脱钩,全部 TC 翻转。 */
|
||||
static int32_t dim_code(double eng, double b_codes) {
|
||||
return (int32_t)(my_lround(eng * 65536.0e6 / (double)K_DIM) - b_codes);
|
||||
}
|
||||
|
||||
/* 一拍:三相量按 50 Hz 正弦(峰值 = rms×√2),其余电气量通道给 0 码值,
|
||||
* TEMP 透传 0;随后跑真实 L2 链。 */
|
||||
static void dim_beat(double ia_rms, double ua_rms, double un_rms) {
|
||||
float frame[ADC_SEM_COUNT];
|
||||
double s50 = sin(2.0 * 3.14159265358979 * 50.0 * (double)s_t / 1000.0);
|
||||
int32_t ci_ia, ci_ua, ci_un;
|
||||
uint8_t c;
|
||||
|
||||
ci_ia = dim_code(ia_rms * 1.4142135623730951 * s50, (double)B_IA_DIM);
|
||||
ci_ua = dim_code(ua_rms * 1.4142135623730951 * s50, 0.0);
|
||||
ci_un = dim_code(un_rms * 1.4142135623730951 * s50, 0.0);
|
||||
|
||||
frame[ADC_SEM_IA] = adc_cal_apply(ADC_SEM_IA, ci_ia);
|
||||
frame[ADC_SEM_UA] = adc_cal_apply(ADC_SEM_UA, ci_ua);
|
||||
frame[ADC_SEM_UN] = adc_cal_apply(ADC_SEM_UN, ci_un);
|
||||
for (c = 0u; c < (uint8_t)ADC_SEM_COUNT; c++) {
|
||||
if ((c != (uint8_t)ADC_SEM_IA) && (c != (uint8_t)ADC_SEM_UA) &&
|
||||
(c != (uint8_t)ADC_SEM_UN)) {
|
||||
frame[c] = adc_cal_apply((adc_semantic_t)c, 0L);
|
||||
}
|
||||
}
|
||||
(void)sample_ring_push_frame(frame, (uint8_t)ADC_SEM_COUNT);
|
||||
phasor_run();
|
||||
freq_run();
|
||||
prot_elm_run(s_t);
|
||||
s_t++;
|
||||
}
|
||||
|
||||
/* TC 起手:定值/元件全复位 + 标定注入(set 正规路径)+ 中性 45 拍收敛 */
|
||||
static void dim_setup(void) {
|
||||
prot_set_init();
|
||||
adc_cal_init();
|
||||
(void)adc_cal_set(ADC_SEM_UA, K_DIM, 0L);
|
||||
(void)adc_cal_set(ADC_SEM_UN, K_DIM, 0L);
|
||||
(void)adc_cal_set(ADC_SEM_IA, K_DIM, B_IA_DIM);
|
||||
prot_elm_init();
|
||||
}
|
||||
|
||||
static void set_en(prot_set_id_t id, int on) {
|
||||
(void)prot_set_set(id, PROT_SET_Q10(on ? 1.0 : 0.0));
|
||||
}
|
||||
|
||||
/* 只留一个电气量元件家族:默认全 EN=1,逐个关到只剩目标 */
|
||||
static void isolate_oc_family(void) {
|
||||
set_en(PS_OC1_EN, 1); set_en(PS_OC2_EN, 0); set_en(PS_OC3_EN, 0);
|
||||
set_en(PS_OL_EN, 0); set_en(PS_OV_EN, 0); set_en(PS_UV_EN, 0);
|
||||
set_en(PS_3U0_EN, 0);
|
||||
}
|
||||
|
||||
static int pick_of(prot_elm_id_t e) {
|
||||
prot_elm_state_t st;
|
||||
prot_elm_state(e, &st);
|
||||
return (st.picked != 0u) ? 1 : 0;
|
||||
}
|
||||
|
||||
static int assert_of(prot_elm_id_t e) {
|
||||
prot_elm_state_t st;
|
||||
prot_elm_state(e, &st);
|
||||
return (st.asserted != 0u) ? 1 : 0;
|
||||
}
|
||||
|
||||
/* 把 IA 拉到 far_below 收敛 45 拍(确保 picked 干净归零、t0 无效) */
|
||||
static void settle_oc(double ia_rms, uint32_t beats) {
|
||||
uint32_t i;
|
||||
for (i = 0u; i < beats; i++) { dim_beat(ia_rms, 100.0, 0.0); }
|
||||
}
|
||||
|
||||
/* ===================== TC-PROTDIM-001 标定链基线 ===================== */
|
||||
static void tc_001_baseline(void) {
|
||||
phasor_t p;
|
||||
double want_rms;
|
||||
double got;
|
||||
uint32_t i;
|
||||
int quiet = 1;
|
||||
|
||||
printf("[TC-PROTDIM-001] calibrated chain baseline: no element moves\n");
|
||||
dim_setup();
|
||||
for (i = 0u; i < 60u; i++) { dim_beat(0.5, 100.0, 0.0); }
|
||||
|
||||
/* 换算链可验证:C01-IA(idx0)有效值 = 注入 0.5 A ±0.5 %
|
||||
* (b=-30 码应被 apply 精确对消;若折算丢失,这里会是 ~700 量级码值) */
|
||||
want_rms = 0.5;
|
||||
CHECK(phasor_get(0u, &p) == 0, "001: C01-IA phasor ready");
|
||||
got = (double)p.fAmSQ;
|
||||
CHECK((got > want_rms * 0.995) && (got < want_rms * 1.005),
|
||||
"001: IA rms = 0.5 A (secondary domain, b cancelled)");
|
||||
|
||||
for (i = 0u; i < (uint32_t)PE_OC1; i++) {
|
||||
if ((pick_of((prot_elm_id_t)i) != 0) || (assert_of((prot_elm_id_t)i) != 0)) {
|
||||
quiet = 0;
|
||||
}
|
||||
}
|
||||
CHECK(quiet, "001: OC1/OC2/OC3/OL/OV/UV/3U0 all silent at 0.5A/100V/0V");
|
||||
CHECK(prot_elm_active_mask() == 0u, "001: active mask 0");
|
||||
}
|
||||
|
||||
/* ===================== TC-PROTDIM-002 反错配对照 ===================== */
|
||||
static void tc_002_mismatch_trap(void) {
|
||||
float raw[ADC_SEM_COUNT];
|
||||
uint32_t i;
|
||||
|
||||
printf("[TC-PROTDIM-002] raw-codes-as-engineering trap / apply guard\n");
|
||||
dim_setup();
|
||||
isolate_oc_family();
|
||||
set_en(PS_OC2_EN, 0);
|
||||
|
||||
/* (a) 复现历史事故:把"码值本身"当工程量灌环(等价于折算点被旁路)。
|
||||
* 3.36 A 物理电流的码值 ~672 → 比 OC1(10 A) → 必假动作 */
|
||||
for (i = 0u; i < (uint32_t)ADC_SEM_COUNT; i++) {
|
||||
raw[i] = 0.0f;
|
||||
}
|
||||
for (i = 0u; i < 45u; i++) {
|
||||
double s50 = sin(2.0 * 3.14159265358979 * 50.0 * (double)s_t / 1000.0);
|
||||
raw[ADC_SEM_IA] = (float)((double)dim_code(3.36 * 1.4142135623730951 * s50,
|
||||
(double)B_IA_DIM));
|
||||
(void)sample_ring_push_frame(raw, (uint8_t)ADC_SEM_COUNT);
|
||||
phasor_run();
|
||||
prot_elm_run(s_t);
|
||||
s_t++;
|
||||
}
|
||||
CHECK((pick_of(PE_OC1) != 0) && (assert_of(PE_OC1) != 0),
|
||||
"002a: raw code domain falsely trips OC1 (documents the incident)");
|
||||
|
||||
/* (b) 同一物理量经正规折算链:3.36 A < 10 A → 必须静默。
|
||||
* (若生产链有人绕过 adc_cal_apply,本断言与 001 一起翻红) */
|
||||
for (i = 0u; i < 45u; i++) { dim_beat(3.36, 100.0, 0.0); }
|
||||
CHECK((pick_of(PE_OC1) == 0) && (assert_of(PE_OC1) == 0),
|
||||
"002b: same physical current via apply() stays silent");
|
||||
}
|
||||
|
||||
/* ===================== TC-PROTDIM-003 OC1:滞回 + dly0 ===================== */
|
||||
static void tc_003_oc1(void) {
|
||||
printf("[TC-PROTDIM-003] OC1 10A/dly0 hysteresis\n");
|
||||
dim_setup();
|
||||
isolate_oc_family();
|
||||
|
||||
settle_oc(3.0, 45u);
|
||||
settle_oc(9.995, 45u);
|
||||
CHECK(pick_of(PE_OC1) == 0, "003: 9.995 A below set: no pickup");
|
||||
|
||||
settle_oc(10.5, 45u);
|
||||
CHECK((pick_of(PE_OC1) != 0) && (assert_of(PE_OC1) != 0),
|
||||
"003: 10.5 A -> picked AND asserted (dly=0 same-beat)");
|
||||
|
||||
settle_oc(9.6, 45u);
|
||||
CHECK((pick_of(PE_OC1) != 0) && (assert_of(PE_OC1) != 0),
|
||||
"003: 9.6 A inside hysteresis (ret=9.5): holds");
|
||||
|
||||
settle_oc(9.49, 45u);
|
||||
CHECK((pick_of(PE_OC1) == 0) && (assert_of(PE_OC1) == 0),
|
||||
"003: 9.49 A below return threshold: drops out");
|
||||
}
|
||||
|
||||
/* 过量定时限通用核:启动拍受控(先远低收敛),到点前后各验一次 */
|
||||
static void timed_over_case(prot_elm_id_t e, prot_set_id_t cur_id, double trip,
|
||||
double hold, double drop, float dly_ms,
|
||||
const char *tag) {
|
||||
uint32_t t_pick = 0u;
|
||||
uint32_t i;
|
||||
uint32_t settle;
|
||||
char nm[96];
|
||||
int pre_ok = 1, post_ok = 0, hold_ok = 0, drop_ok = 0, trip_ok = 0;
|
||||
|
||||
(void)cur_id;
|
||||
(void)tag;
|
||||
/* 1) 远低收敛;2) 阶跃到 trip,逐拍轮询到 picked(记录启动拍) */
|
||||
for (i = 0u; i < 45u; i++) { dim_beat(0.1 * trip, 100.0, 0.0); }
|
||||
for (i = 0u; i < 400u; i++) {
|
||||
dim_beat(trip, 100.0, 0.0);
|
||||
if (pick_of(e) != 0) { t_pick = s_t - 1u; trip_ok = 1; break; }
|
||||
}
|
||||
if (trip_ok == 0) {
|
||||
(void)snprintf(nm, sizeof(nm), "%s: step to trip level picks up", tag);
|
||||
CHECK(0, nm);
|
||||
return;
|
||||
}
|
||||
/* 3) 到点前 2 ms 不许动作、到点后 2 ms 必须动作(同幅值持续注入) */
|
||||
settle = 0u;
|
||||
for (i = t_pick; i < (t_pick + (uint32_t)dly_ms - 2u); i++) {
|
||||
dim_beat(trip, 100.0, 0.0);
|
||||
settle++;
|
||||
}
|
||||
(void)settle;
|
||||
if (assert_of(e) != 0) { pre_ok = 0; }
|
||||
for (i = 0u; i < 5u; i++) { dim_beat(trip, 100.0, 0.0); }
|
||||
post_ok = assert_of(e);
|
||||
|
||||
(void)snprintf(nm, sizeof(nm), "%s: no assert before setpoint-2ms", tag);
|
||||
CHECK(pre_ok != 0 && post_ok != 0, nm);
|
||||
|
||||
/* 4) 返回系数滞回带内保持、跌破释放 */
|
||||
for (i = 0u; i < 45u; i++) { dim_beat(hold, 100.0, 0.0); }
|
||||
hold_ok = (pick_of(e) != 0) && (assert_of(e) != 0);
|
||||
for (i = 0u; i < 45u; i++) { dim_beat(drop, 100.0, 0.0); }
|
||||
drop_ok = (pick_of(e) == 0) && (assert_of(e) == 0);
|
||||
|
||||
(void)snprintf(nm, sizeof(nm), "%s: holds inside return band", tag);
|
||||
CHECK(hold_ok != 0, nm);
|
||||
(void)snprintf(nm, sizeof(nm), "%s: returns below return threshold", tag);
|
||||
CHECK(drop_ok != 0, nm);
|
||||
}
|
||||
|
||||
/* ===================== TC-PROTDIM-004~006 OC2/OC3/OL ===================== */
|
||||
static void tc_004_oc2(void) {
|
||||
printf("[TC-PROTDIM-004] OC2 5A/500ms timed+return\n");
|
||||
dim_setup();
|
||||
isolate_oc_family();
|
||||
set_en(PS_OC2_EN, 1);
|
||||
timed_over_case(PE_OC2, PS_OC2_CUR, 6.0, 4.8, 4.6, 500.0f, "004:OC2");
|
||||
}
|
||||
|
||||
static void tc_005_oc3(void) {
|
||||
printf("[TC-PROTDIM-005] OC3 2A/3000ms timed+return\n");
|
||||
dim_setup();
|
||||
isolate_oc_family();
|
||||
set_en(PS_OC3_EN, 1);
|
||||
timed_over_case(PE_OC3, PS_OC3_CUR, 2.1, 1.95, 1.89, 3000.0f, "005:OC3");
|
||||
}
|
||||
|
||||
static void tc_006_ol(void) {
|
||||
printf("[TC-PROTDIM-006] OL 1A/10s timed+return\n");
|
||||
dim_setup();
|
||||
isolate_oc_family();
|
||||
set_en(PS_OL_EN, 1);
|
||||
timed_over_case(PE_OL, PS_OL_CUR, 1.05, 0.96, 0.93, 10000.0f, "006:OL");
|
||||
}
|
||||
|
||||
/* ===================== TC-PROTDIM-007 OV(电压域走 UA) ===================== */
|
||||
static void tc_007_ov(void) {
|
||||
uint32_t t_pick = 0u;
|
||||
uint32_t i;
|
||||
int ok;
|
||||
|
||||
printf("[TC-PROTDIM-007] OV 120V/10s (UA secondary domain)\n");
|
||||
dim_setup();
|
||||
isolate_oc_family();
|
||||
set_en(PS_OV_EN, 1);
|
||||
|
||||
for (i = 0u; i < 45u; i++) { dim_beat(0.0, 100.0, 0.0); }
|
||||
CHECK(pick_of(PE_OV) == 0, "007: nominal 100 V silent");
|
||||
for (i = 0u; i < 400u; i++) {
|
||||
dim_beat(0.0, 121.0, 0.0);
|
||||
if (pick_of(PE_OV) != 0) { t_pick = s_t - 1u; break; }
|
||||
}
|
||||
CHECK(pick_of(PE_OV) != 0, "007: 121 V picks up");
|
||||
for (i = t_pick; i < (t_pick + 9998u); i++) { dim_beat(0.0, 121.0, 0.0); }
|
||||
ok = (assert_of(PE_OV) == 0);
|
||||
for (i = 0u; i < 5u; i++) { dim_beat(0.0, 121.0, 0.0); }
|
||||
CHECK((ok != 0) && (assert_of(PE_OV) != 0),
|
||||
"007: asserts exactly at +10000ms window (not earlier)");
|
||||
for (i = 0u; i < 45u; i++) { dim_beat(0.0, 115.0, 0.0); }
|
||||
CHECK(assert_of(PE_OV) != 0, "007: 115 V holds (ret=114)");
|
||||
for (i = 0u; i < 45u; i++) { dim_beat(0.0, 113.0, 0.0); }
|
||||
CHECK((pick_of(PE_OV) == 0) && (assert_of(PE_OV) == 0),
|
||||
"007: 113 V returns");
|
||||
}
|
||||
|
||||
/* ===================== TC-PROTDIM-008 UV(欠量方向) ===================== */
|
||||
static void tc_008_uv(void) {
|
||||
uint32_t t_pick = 0u;
|
||||
uint32_t i;
|
||||
int ok;
|
||||
|
||||
printf("[TC-PROTDIM-008] UV 20V/10s under-direction\n");
|
||||
dim_setup();
|
||||
isolate_oc_family();
|
||||
set_en(PS_UV_EN, 1);
|
||||
|
||||
for (i = 0u; i < 45u; i++) { dim_beat(0.0, 21.0, 0.0); }
|
||||
CHECK(pick_of(PE_UV) == 0, "008: 21 V above set(20): silent");
|
||||
for (i = 0u; i < 400u; i++) {
|
||||
dim_beat(0.0, 19.9, 0.0);
|
||||
if (pick_of(PE_UV) != 0) { t_pick = s_t - 1u; break; }
|
||||
}
|
||||
CHECK(pick_of(PE_UV) != 0, "008: 19.9 V picks up");
|
||||
for (i = t_pick; i < (t_pick + 9998u); i++) { dim_beat(0.0, 19.9, 0.0); }
|
||||
ok = (assert_of(PE_UV) == 0);
|
||||
for (i = 0u; i < 5u; i++) { dim_beat(0.0, 19.9, 0.0); }
|
||||
CHECK((ok != 0) && (assert_of(PE_UV) != 0), "008: asserts at +10000ms");
|
||||
for (i = 0u; i < 45u; i++) { dim_beat(0.0, 20.5, 0.0); }
|
||||
CHECK(assert_of(PE_UV) != 0, "008: 20.5 V holds under return(21)");
|
||||
for (i = 0u; i < 45u; i++) { dim_beat(0.0, 21.6, 0.0); }
|
||||
CHECK((pick_of(PE_UV) == 0) && (assert_of(PE_UV) == 0), "008: 21.6 V returns");
|
||||
}
|
||||
|
||||
/* ===================== TC-PROTDIM-009 3U0(UN 通道) ===================== */
|
||||
static void tc_009_3u0(void) {
|
||||
uint32_t t_pick = 0u;
|
||||
uint32_t i;
|
||||
int ok;
|
||||
|
||||
printf("[TC-PROTDIM-009] 3U0 15V/5s via UN channel\n");
|
||||
dim_setup();
|
||||
isolate_oc_family();
|
||||
set_en(PS_3U0_EN, 1);
|
||||
|
||||
for (i = 0u; i < 45u; i++) { dim_beat(0.0, 100.0, 14.0); }
|
||||
CHECK(pick_of(PE_3U0) == 0, "009: 14 V below set(15): silent");
|
||||
for (i = 0u; i < 400u; i++) {
|
||||
dim_beat(0.0, 100.0, 15.1);
|
||||
if (pick_of(PE_3U0) != 0) { t_pick = s_t - 1u; break; }
|
||||
}
|
||||
CHECK(pick_of(PE_3U0) != 0, "009: 15.1 V picks up");
|
||||
for (i = t_pick; i < (t_pick + 4998u); i++) { dim_beat(0.0, 100.0, 15.1); }
|
||||
ok = (assert_of(PE_3U0) == 0);
|
||||
for (i = 0u; i < 5u; i++) { dim_beat(0.0, 100.0, 15.1); }
|
||||
CHECK((ok != 0) && (assert_of(PE_3U0) != 0), "009: asserts at +5000ms");
|
||||
for (i = 0u; i < 45u; i++) { dim_beat(0.0, 100.0, 14.4); }
|
||||
CHECK(assert_of(PE_3U0) != 0, "009: 14.4 V holds (ret=14.25)");
|
||||
for (i = 0u; i < 45u; i++) { dim_beat(0.0, 100.0, 14.1); }
|
||||
CHECK((pick_of(PE_3U0) == 0) && (assert_of(PE_3U0) == 0), "009: 14.1 V returns");
|
||||
}
|
||||
|
||||
/* ===================== TC-PROTDIM-010 聚合位图与独立性 ===================== */
|
||||
static void tc_010_mask(void) {
|
||||
uint32_t i;
|
||||
|
||||
printf("[TC-PROTDIM-010] dual assert mask independence\n");
|
||||
dim_setup();
|
||||
isolate_oc_family();
|
||||
set_en(PS_OV_EN, 1);
|
||||
(void)prot_set_set(PS_OV_DLY, PROT_SET_Q10(0.0)); /* 同拍聚合,聚焦位图 */
|
||||
|
||||
for (i = 0u; i < 45u; i++) { dim_beat(10.5, 121.0, 0.0); }
|
||||
CHECK((assert_of(PE_OC1) != 0) && (assert_of(PE_OV) != 0),
|
||||
"010: OC1+OV both asserted");
|
||||
CHECK(prot_elm_active_mask() == ((1uL << PE_OC1) | (1uL << PE_OV)),
|
||||
"010: active mask = bit0|bit4 exactly");
|
||||
|
||||
for (i = 0u; i < 45u; i++) { dim_beat(3.0, 121.0, 0.0); }
|
||||
CHECK((assert_of(PE_OC1) == 0) && (assert_of(PE_OV) != 0),
|
||||
"010: IA withdrawn -> OC1 returns, OV untouched");
|
||||
CHECK(prot_elm_active_mask() == (1uL << PE_OV), "010: mask = OV only");
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
printf("=== Prot Dim Regression Tests ===\n");
|
||||
adc_cal_init(); /* 先把 RAM 系数置成合成 ROM(各 TC 内还会重跑) */
|
||||
tc_001_baseline();
|
||||
tc_002_mismatch_trap();
|
||||
tc_003_oc1();
|
||||
tc_004_oc2();
|
||||
tc_005_oc3();
|
||||
tc_006_ol();
|
||||
tc_007_ov();
|
||||
tc_008_uv();
|
||||
tc_009_3u0();
|
||||
tc_010_mask();
|
||||
printf("\n=== Prot Dim Regression Tests: %d/%d PASSED (failed=%d) ===\n",
|
||||
s_passed, s_total, s_failed);
|
||||
return (s_failed == 0) ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""量纲回归(卡 7,道A)主机侧单元测试运行器(L0.5,纯标准库)。
|
||||
|
||||
做什么:
|
||||
MSVC 编译 test/host/prot_dim/prot_dim_test.c(#include 纳入真实链:
|
||||
adc_cal + prot_set + sample_ring + phasor + freq + prot_elm),在 PC 上
|
||||
用"码值→二次值显式换算"的全链路合成波驱动七类保护元件,钉死
|
||||
「不该动作时不动作」。工具链细节委托 unit_test_harness.py。
|
||||
|
||||
为什么要这个套件(数据可信 §2.2 / 卡 7):
|
||||
台架此前"通过"是因为没有一条判据检查不该动作时不动作——"码值当工程量"
|
||||
的错配曾让 OC 恒假动作。本套件把换算写进用例:折算点被旁路或量纲再错位,
|
||||
断言立刻翻转;纯合成数据,零硬件零台架。
|
||||
|
||||
用法(仓库根目录):
|
||||
python test/host/prot_dim/run_unit_test.py
|
||||
|
||||
退出码:0=PASS / 1=测试失败 / 2=环境错误。
|
||||
设计依据:docs/任务规划/数据可信.md 卡 7(M4 退出判据⑥);
|
||||
创建日期:2026-10-25
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.dirname(os.path.dirname(os.path.dirname(HERE)))
|
||||
sys.path.insert(0, os.path.join(ROOT, "test", "host", "common"))
|
||||
|
||||
from unit_test_harness import TestBuild, run_host_unit_test # noqa: E402
|
||||
|
||||
# 顺序敏感:stub 先(dbg_log.h 覆盖);App/algo 供被测链与 adc_input.h;
|
||||
# App/cfg 供 cfg_file.h(adc_cal.c 的接口头,注册函数打桩)。
|
||||
BUILD = TestBuild(
|
||||
name="Prot Dim Regression Tests",
|
||||
src=os.path.join(HERE, "prot_dim_test.c"),
|
||||
include_dirs=[
|
||||
os.path.join(HERE, "stub"),
|
||||
os.path.join(ROOT, "App", "algo"),
|
||||
os.path.join(ROOT, "App", "cfg"),
|
||||
HERE,
|
||||
],
|
||||
summary_marker="=== Prot Dim Regression Tests:",
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run_host_unit_test(BUILD))
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 主机侧单测桩:dbg_log.h(prot_dim 量纲回归套件)
|
||||
* 模块功能:为 adc_cal.c 提供主机可编译的日志宏(映射为计数)。
|
||||
* ⚠ 仅供 test/host/prot_dim/ 使用,不参与 MCU 构建。同 adc_cal 套件先例。
|
||||
* 创建日期:2026-10-25
|
||||
*/
|
||||
|
||||
#ifndef DBG_LOG_H
|
||||
#define DBG_LOG_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
extern int g_dimtest_err_count;
|
||||
extern int g_dimtest_info_count;
|
||||
extern int g_dimtest_debug_count;
|
||||
|
||||
#define DBG_INFO(fmt, ...) do { g_dimtest_info_count++; } while (0)
|
||||
#define DBG_DEBUG(fmt, ...) do { g_dimtest_debug_count++; } while (0)
|
||||
#define DBG_ERROR(fmt, ...) do { g_dimtest_err_count++; } while (0)
|
||||
|
||||
#endif /* DBG_LOG_H */
|
||||
Reference in New Issue
Block a user