Files
STM32F4-Base/test/host/adc_input/adc_input_test.c
T
wandering1 924ac3d428 test(host): 断言字面量统一纯 ASCII,30 文件 1535 行
用户裁定「统一」。口径就是 AGENTS 铁律:字符串字面量必须纯 ASCII
(无 BOM 文件里奇数个汉字会吞掉结束引号,偶数才侥幸通过),中文只写注释。
四名队友按目录分四组并行、写范围互斥:net_socket 418 行(6 文件)、
console_log/sys_log_mcu/soe_store/cfg_file_store_mcu 268 行(4 文件)、
11 个存量套件 404 行 + adc_input 89 行、iec104 与 lftpd 与 NET 四套等 356 行
(9 文件)。runner 的 .py 文档串不在口径内(Python 源不经 ARMCC 代码页)。

两套口径必须分清:粗口径(按行切引号)普查 = 34 文件 / 1,552 行;严格口径
(状态机剥掉块/行注释与字符常量后再取字符串字面量)= 30 文件 / 1,535 行。
差值那 4 个文件的命中全在注释里(ns_peers.c、net_socket/stub/ns_rtos.h、
common/ch395f_fake.h、common/fatfs_fake.c)——按口径一处都不该改,改了等于
把中文从注释里搬走、正好违背口径。粗口径绝不能当交付判据,否则要么白改
注释、要么留下「这行为什么没改」的悬案。

Lead 独立终检(不采信自报)五条硬判据,结果 30 文件漂移 0:① 每文件 TC 号
集合与 HEAD 逐字相同 ② 断言宏调用点数不变 ③ 行数与 HEAD 完全相等(专抓
整文件回写把尾部含 main() 吃掉的截断,第 1 轮真发生过两次)④ 剥注释后代码区
非 ASCII 字符 = 0 ⑤ 字节卫生(BOM 与 HEAD 一致、无 CR、无 U+FFFD、单尾换行)。
另用「逐行把字面量整体替换成空串后与 HEAD 必须逐行相等」机械证明只改了字面量;
各套 --module 覆盖率与断言数逐字不变(net_socket 887/889·53/53、console_log
199/200·16/16、sys_log_mcu 324/326·13/13、adc_input 103/103、sys_clock 100/100)
→ 生产码零改动,基线不需重登。

闸拦下一处真实回归(我的验收器没抓到):ns_s6_edges.c 翻译时把一个上一轮
已整改成平实描述的编号又写回了 CHECK 标签。原因很清楚:「只改字面量」并不
等于「只改无害内容」,标签里完全可以带进未登记族的编号形态;而 Lead 的自动
验收器只比对了 TC 形 id 集合,未覆盖 L/E/N/S/TRAP 等族。教训:批量改标签的
守卫必须加一条「新串不得出现任何已登记前缀族之外的编号形态」,且结构不变式
只覆盖自己想到的维度时不能替代真闸。
Lead 自查另清掉 126 处写坏的反斜杠(JS→Python 转义层数翻倍,把 markdown
反引号写成反斜杠+反引号),恰好发生在记录这条教训的那一节里。

文档:docs/测试说明/主机单测覆盖率口径.md §9 新增「第 2 轮」小节(两套口径对照
表、五条终检判据、六条手法含「转换说明符序列必须逐位相同」取代「禁止裸 %」、
三条协作纪律含先改板再派工与临时文件带组前缀、后续项 3 条)+ 变更记录一行。

后续项(本轮不做):把剥注释词法扫描器收进 scripts 当门禁(需用户点头并先定
test/ 是否纳入 coding-standard);iec104 的简写编号归族(牵 id_registry,
不能顺手做);替换器加「编号形态白名单」校验。

gate: pre-commit run --all-files -> 39 hooks, 37 Passed; failed = mem-numbers +
mem-budget(受管快照与本树 .map 的漂移,属第 1 轮生产码改动的遗留,非本批
增量)。本批只碰 test/host/**,不进 Keil 编译面也不进模拟器 CMake,**RAM/Flash
增量 = 0**;实测水位 RW_IRAM1 57.4%、RW_CCM 87.9%,均低于 92% 红线。
id-check 0 台账外新账(可判定 84.63% > 基线 82.6%);test-coverage 33/33 全绿;
cppcheck / coding-standard / bsp-err / layer-deps / doc-links 全 Passed。
按仓内分支约定 --no-verify 提交,台账 --write 回填留待主工作树合并后。
2026-10-24 21:00:17 +08:00

596 lines
30 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称:ADC 输入层(L1)主机侧单元测试
* 模块功能:在 PC 上对 App/algo/adc_input.c 做离线验证(8 个公开函数 + 2 个静态函数)。
*
* 被测对象:真实源文件 ../../../App/algo/adc_input.c(以 #include 原样纳入,
* 保证测的是生产代码而非副本)。平台依赖由本文件提供的**可注入桩**实现:
* - adc.h -> HAL_ADC_Start_DMA(可注入挂载失败);DMA 完成由测试
* 直接调用被测代码实现的 HAL_ADC_Conv[Half]CpltCallback
* 注入(dma_beat()),并可往乒乓缓冲写任意码值
* - tpafe5160.h-> tpafe5160_snapshot(可注入 新帧/无新帧/参数错 + 帧内容)
*
* 为什么要主机侧单测(而不是只用板端套件):
* 1. 本层核心是"**通道号 <-> Rank 顺序 <-> 语义**"三者的映射正确性。板端**无信号源**
* (见 docs/硬件平台/ADC通道映射说明.md §8),无法注入已知电压去验证"读到的是
* 哪只脚";主机侧可直接给每个 Rank 注入互不相同的码值,从而**逐通道钉死映射**
* (这是唯一能在离线端证伪"通道宏写错 -> 读到另一只脚"的手段)。
* 2. HAL 启动失败 / 转换超时 / 参数错等分支在真机上极难稳定复现,主机侧一次钉死。
* 3. 不占 MCU 的 RAM,毫秒级完成。
*
* 覆盖点(对应 docs/算法/继电保护功能复现规划.md §2.1 的 L1-3 / L1-4):
* —— adc_input_init(TC-ADCIN-001..006)——
* TC-ADCIN-001 poll 失败后失败计数可见(前置状态构造)
* TC-ADCIN-002 poll 成功后 TPAFE 侧可取数(前置状态构造)
* TC-ADCIN-003 init 返回 0(映射表自检通过)
* TC-ADCIN-004 init 后 STM32 状态码 = NOT_RUN
* TC-ADCIN-005 init 后失败计数归零
* TC-ADCIN-006 init 后 TPAFE / STM32 两侧均无有效数据(有效标志被清)
* —— adc_input_desc(TC-ADCIN-010..01A)——
* TC-ADCIN-010..018 九个语义通道 -> (来源, 下标) 与权威映射表逐项一致
* TC-ADCIN-019/01A 越界(ADC_SEM_COUNT)与负数语义 -> NULL
* —— map_check(TC-ADCIN-020)——
* TC-ADCIN-020 真实映射表自洽(反向路径结构上不可达,见文件末说明)
* —— stm32_take(TC-ADCIN-021..078,v2.0 DMA 模型)——
* TC-ADCIN-021..025 取到新帧:返回 0 / 状态码 / 不计失败 / DMA 只挂一次 / seq=1
* TC-ADCIN-026..02B **Rank -> 通道号映射**:UB->CH14(rank9) / UC->CH3(rank4) /
* IB->CH6(rank7) / IC->CH1(rank2) / IN->CH2(rank3) / TEMP->CH5(rank6)
* TC-ADCIN-02C stm32_take 不置 TPAFE 有效标志(两来源独立)
* TC-ADCIN-080..089 **乒乓双缓冲**:消费侧读"刚完成的半区",即使另一半正被 DMA
* 改写也不得读到(撕裂帧防线);两拍交替半区、seq 推进;
* 超拍未消费 -> 记一次丢帧(gap),与停摆(err)分开计数
* TC-ADCIN-090..09A HAL 错误回调计数 + 三个回调的 `hadc == &hadc1` 句柄守卫
* (别的句柄不得污染本层 seq / hw_errs)
* TC-ADCIN-030..034 HAL_ADC_Start_DMA 失败:init 返回 -2 / 状态码 RC_START / 计数 +1
* TC-ADCIN-035..039 本拍无新帧:返回 RC_STALL / **单拍不计故障**(相位伪影)/ seq 不前进
* TC-ADCIN-03A..03E 连续两拍无新帧才计停摆;停摆与丢帧分开计数;恢复拍自愈
* TC-ADCIN-070..078 **停摆拍不得沿用上一拍旧值**(2026-10-10 补,真机实测发现的
* 正确性缺陷;DMA 版改由"无新帧"触发同一路径):先成功一拍让
* STM32 侧进入有效态,再注入无新帧拍 -> adc_input_raw() 必须返 -2;
* 恢复拍重新置有效。TC-ADCIN-034 只覆盖"挂载失败即无数据",
* 照不出"成功过之后停摆 → 静默沿用旧值",故单独立一组。
* —— adc_input_raw(TC-ADCIN-040..04B)——
* TC-ADCIN-040..043 参数守卫:out=NULL / 越界 / 负数 -> -1,且不写 out
* TC-ADCIN-044/045 无有效数据 -> -2(两来源分别)
* TC-ADCIN-046..048 poll 快照契约:返回码透传 / 请求 8 通道 / 目的缓冲非空
* TC-ADCIN-049..04B TPAFE 按**下标**取数(UA=6 / UN=5 / IA=2)且补码负值原样保留
* —— adc_input_poll(TC-ADCIN-050..058)——
* TC-ADCIN-050..052 无新帧(0):返回 0 / TPAFE 无效 / STM32 侧照常扫描
* TC-ADCIN-053/054 参数错(-1):返回 -1 / TPAFE 无效
* TC-ADCIN-055 已有有效帧后再"无新帧"**不清零**(保持上次有效值)
* TC-ADCIN-056..058 STM32 失败**不阻断** TPAFE、**不影响** poll 返回值,另由状态码暴露
* —— adc_input_stm32_rc / _errs(TC-ADCIN-060..066)——
* TC-ADCIN-060/061 init 后 = NOT_RUN / 计数 0
* TC-ADCIN-062 成功: rc=OK 且 errs 不变
* TC-ADCIN-063/064 两次失败累计 = 2,rc = 最近一次(TIMEOUT)
* TC-ADCIN-065/066 恢复成功 rc=OK,errs 单调不回退
*
* 通过:全部 TC PASS(main 返回 0)。
* 入口:int main(void),返回 0=PASS / 1=FAIL(由 run 脚本判读)。
*
* 创建日期:2026-10-10
*/
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
/* ===================== 平台桩(先纳入,覆盖真实头) ===================== */
#include "adc.h"
#include "tpafe5160.h"
#include "adc_input.h" /* 取 ADC_INPUT_STM32_CH_NUM 等常量(桩辅助函数在被测源之前) */
/* ===================== HAL ADC1 桩(DMA 模型) =====================
* 被测代码经 HAL 只做一件事:init 时挂一次 DMA。此后"一帧完成"由 DMA 中断的
* HAL 回调告知——回调本体由被测代码实现,测试直接调它们即可注入节拍。
* s_start_dma_rc HAL_ADC_Start_DMA 返回值(注入挂载失败)
* s_start_dma_calls 挂载次数(正常应恒为 1:本层不反复重启 DMA)
* s_dma_buf 被测代码交下来的乒乓缓冲地址(测试据此写"采样值")
* s_dma_len 被测代码请求的搬运长度(应为 2 x 9 = 18)
*/
static HAL_StatusTypeDef s_start_dma_rc = HAL_OK;
static int s_start_dma_calls = 0;
static uint32_t *s_dma_buf = NULL;
static uint32_t s_dma_len = 0u;
/* 被测代码把 &hadc1 透传给 HAL 桩;桩不读其字段 */
ADC_HandleTypeDef hadc1;
/* 用于验证回调里的 `hadc == &hadc1` 守卫:别的句柄不得影响本层计数 */
ADC_HandleTypeDef hadc_other;
HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef *hadc, uint32_t *pData, uint32_t Length) {
(void)hadc;
s_start_dma_calls++;
s_dma_buf = pData;
s_dma_len = Length;
return s_start_dma_rc;
}
/* ===================== TPAFE5160 快照桩 ===================== */
static int16_t s_fake_tpafe[8];
static int s_snapshot_rc = 0;
static int s_snapshot_calls = 0;
static uint8_t s_snapshot_count = 0u;
static int16_t *s_snapshot_dst = NULL;
int tpafe5160_snapshot(int16_t *dst, uint8_t count) {
uint8_t i;
s_snapshot_calls++;
s_snapshot_count = count;
s_snapshot_dst = dst;
if (s_snapshot_rc == 1) {
for (i = 0u; (i < count) && (i < 8u); i++) {
dst[i] = s_fake_tpafe[i];
}
}
return s_snapshot_rc;
}
/* ===================== 断言 ===================== */
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)
/* ===================== 被测代码(原样纳入生产源) ===================== */
#include "adc_input.c"
/* ===================== 工具 ===================== */
/*
* 函数功能:把全部桩复位到"健康"初值
* 入口参数:无
* 返 回 值:无
* 限定条件:无
* 函数说明:默认 DMA 挂载成功、TPAFE 快照无新帧。DMA 缓冲由 adc_input_init() 交下来,
* 复位时清空指针(此时不得调用 dma_fill / dma_beat)。
*/
static void fakes_reset(void) {
uint8_t i;
s_start_dma_rc = HAL_OK;
s_start_dma_calls = 0;
s_dma_buf = NULL;
s_dma_len = 0u;
s_snapshot_rc = 0;
s_snapshot_calls = 0;
s_snapshot_count = 0u;
s_snapshot_dst = NULL;
for (i = 0u; i < 8u; i++) {
s_fake_tpafe[i] = 0;
}
}
/*
* 函数功能:往乒乓缓冲的指定半区写入一帧"采样值"(base + Rank 序号)
* 入口参数:half - 半区下标(0 / 1);base - 该帧首通道的码值
* 返 回 值:无
* 限定条件:须在 adc_input_init() 之后(缓冲地址由 Start_DMA 交下来)
* 函数说明:值按 **Rank 序号** 递增,故第 i 个 Rank = base+i;配合
* k_stm32_rank_ch=[CH0..CH7,CH14] 可逐通道钉死映射(如 UB=base+8)。
*/
static void dma_fill(uint8_t half, uint16_t base) {
uint16_t *p = (uint16_t *)(void *)s_dma_buf;
uint8_t i;
for (i = 0u; i < ADC_INPUT_STM32_CH_NUM; i++) {
p[((uint32_t)half * ADC_INPUT_STM32_CH_NUM) + i] = (uint16_t)(base + i);
}
}
/*
* 函数功能:模拟"一拍 DMA 搬运完成"(写对应半区 + 触发对应 HAL 回调)
* 入口参数:base - 本帧首通道码值
* 返 回 值:无
* 限定条件:须在 adc_input_init() 之后
* 函数说明:DMA 长度 18、每拍搬 9 个 → 奇数次完成落在前半区(半传输回调)、
* 偶数次落在后半区(传输完成回调)。半区下标由"已完成帧数"的奇偶推出,
* 与被测代码 stm32_take() 的口径互为镜像(两边独立推导,能互相证伪)。
*/
static void dma_beat(uint16_t base) {
uint8_t half = (uint8_t)(adc_input_stm32_seq() & 1u);
dma_fill(half, base);
if (half == 0u) {
HAL_ADC_ConvHalfCpltCallback(&hadc1);
} else {
HAL_ADC_ConvCpltCallback(&hadc1);
}
}
/* ===================== TC-ADCIN-001..006:init ===================== */
static void tc_init(void) {
int32_t v = -999;
fakes_reset();
/* 前置:制造"STM32 失败一次 + TPAFE 有效一帧"的状态,再验 init 是否清干净 */
s_snapshot_rc = 1;
s_fake_tpafe[6] = 4321;
(void)adc_input_poll(); /* 单拍无新帧 = 相位伪影,不计故障 */
(void)adc_input_poll(); /* 连续第二拍无新帧 = 真停摆 */
CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-001 two rounds in a row with no new frame -> stall count = 1");
CHECK((adc_input_raw(ADC_SEM_UA, &v) == 0) && (v == 4321),
"TC-ADCIN-002 after poll UA is readable and the value is right");
CHECK(adc_input_init() == 0, "TC-ADCIN-003 adc_input_init returns 0 (mapping self-check passed)");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_NOT_RUN,
"TC-ADCIN-004 after init the STM32 status code = NOT_RUN");
CHECK(s_start_dma_calls == 1, "TC-ADCIN-003B init arms DMA exactly once");
CHECK(s_dma_len == (2u * ADC_INPUT_STM32_CH_NUM), "TC-ADCIN-003C DMA length = 2 x 9 (ping-pong double buffer)");
CHECK(s_dma_buf != NULL, "TC-ADCIN-003D the DMA destination buffer address was handed down");
CHECK(adc_input_stm32_seq() == 0u, "TC-ADCIN-005B after init the DMA completion counter = 0");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-005 after init the STM32 failure counter is zeroed");
CHECK((adc_input_raw(ADC_SEM_UA, &v) == -2) && (adc_input_raw(ADC_SEM_UB, &v) == -2),
"TC-ADCIN-006 after init neither TPAFE nor STM32 side holds valid data");
}
/* ===================== TC-ADCIN-010..01A:desc 映射表 ===================== */
static void tc_desc(void) {
const adc_channel_desc_t *d;
d = adc_input_desc(ADC_SEM_UA);
CHECK((d != NULL) && (d->source == ADC_SRC_TPAFE) && (d->index == 6u),
"TC-ADCIN-010 UA -> TPAFE buf[6]");
d = adc_input_desc(ADC_SEM_UB);
CHECK((d != NULL) && (d->source == ADC_SRC_STM32) && (d->index == 14u),
"TC-ADCIN-011 UB -> STM32 CH14");
d = adc_input_desc(ADC_SEM_UC);
CHECK((d != NULL) && (d->source == ADC_SRC_STM32) && (d->index == 3u),
"TC-ADCIN-012 UC -> STM32 CH3");
d = adc_input_desc(ADC_SEM_UN);
CHECK((d != NULL) && (d->source == ADC_SRC_TPAFE) && (d->index == 5u),
"TC-ADCIN-013 UN -> TPAFE buf[5]");
d = adc_input_desc(ADC_SEM_IA);
CHECK((d != NULL) && (d->source == ADC_SRC_TPAFE) && (d->index == 2u),
"TC-ADCIN-014 IA -> TPAFE buf[2]");
d = adc_input_desc(ADC_SEM_IB);
CHECK((d != NULL) && (d->source == ADC_SRC_STM32) && (d->index == 6u),
"TC-ADCIN-015 IB -> STM32 CH6");
d = adc_input_desc(ADC_SEM_IC);
CHECK((d != NULL) && (d->source == ADC_SRC_STM32) && (d->index == 1u),
"TC-ADCIN-016 IC -> STM32 CH1");
d = adc_input_desc(ADC_SEM_IN);
CHECK((d != NULL) && (d->source == ADC_SRC_STM32) && (d->index == 2u),
"TC-ADCIN-017 IN -> STM32 CH2");
d = adc_input_desc(ADC_SEM_TEMP);
CHECK((d != NULL) && (d->source == ADC_SRC_STM32) && (d->index == 5u),
"TC-ADCIN-018 TEMP -> STM32 CH5");
CHECK(adc_input_desc(ADC_SEM_COUNT) == NULL, "TC-ADCIN-019 out of range (ADC_SEM_COUNT) -> NULL");
CHECK(adc_input_desc((adc_semantic_t)(-1)) == NULL, "TC-ADCIN-01A negative index -> NULL");
}
/* ===================== TC-ADCIN-020:map_check ===================== */
static void tc_map_check(void) {
/* 真实映射表自洽:TPAFE 下标 < 8 且每个 STM32 通道号都出现在扫描 Rank 内。
* 反向路径(表不自洽 -> -1)需在运行时改表,而 k_adc_map / k_stm32_rank_ch 均为
* 编译期 static const、无运行时输入可使其不自洽 -> 结构上不可达(见文件末说明)。 */
CHECK(map_check() == 0, "TC-ADCIN-020 mapping table is self-consistent (TPAFE index and STM32 Rank both in range)");
}
/* ===================== TC-ADCIN-021..02C:stm32_scan 成功路径 ===================== */
static void tc_take_ok(void) {
int32_t v = 0;
fakes_reset();
(void)adc_input_init();
dma_beat(100); /* 前半区一帧:Rank i -> 100+i */
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-021 stm32_take got a new frame -> 0");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_OK, "TC-ADCIN-022 status code = RC_OK");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-023 a success is not counted as a failure");
CHECK(s_start_dma_calls == 1, "TC-ADCIN-024 DMA is armed once at init (never restarted per round)");
CHECK(adc_input_stm32_seq() == 1u, "TC-ADCIN-025 DMA completion counter = 1");
/* Rank -> 通道号映射:第 i 个 Rank = 100+i。
* k_stm32_rank_ch = [CH0..CH7, CH14],故 CH3->103 / CH5->105 / CH6->106 / CH14->108。 */
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 108), "TC-ADCIN-026 UB reads CH14(rank9)=108");
CHECK((adc_input_raw(ADC_SEM_UC, &v) == 0) && (v == 103), "TC-ADCIN-027 UC reads CH3(rank4)=103");
CHECK((adc_input_raw(ADC_SEM_IB, &v) == 0) && (v == 106), "TC-ADCIN-028 IB reads CH6(rank7)=106");
CHECK((adc_input_raw(ADC_SEM_IC, &v) == 0) && (v == 101), "TC-ADCIN-029 IC reads CH1(rank2)=101");
CHECK((adc_input_raw(ADC_SEM_IN, &v) == 0) && (v == 102), "TC-ADCIN-02A IN reads CH2(rank3)=102");
CHECK((adc_input_raw(ADC_SEM_TEMP, &v) == 0) && (v == 105), "TC-ADCIN-02B TEMP reads CH5(rank6)=105");
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-ADCIN-02C stm32_take does not set the TPAFE valid flag");
}
/* ============ TC-ADCIN-080..085:乒乓双缓冲(读刚完成半区,不读正在写的半区) ============
* 这是 DMA 方案最关键的一条:消费侧必须读"上一拍刚完成的那一半",而 DMA 此刻正在
* 写另一半。若读错半区,取到的是**正在被改写**的数据 → 半新半旧的撕裂帧。 */
static void tc_dma_pingpong(void) {
int32_t v = 0;
fakes_reset();
(void)adc_input_init();
dma_beat(100); /* 第 1 帧 -> 前半区(半传输回调) */
dma_fill(1u, 900u); /* 模拟 DMA 正在写后半区(值 900 系) */
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-080 first round fetch succeeded");
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 108),
"TC-ADCIN-081 reads the just completed half (108), not the half being written");
dma_beat(200); /* 第 2 帧 -> 后半区(传输完成回调) */
dma_fill(0u, 800u); /* 模拟 DMA 正在写前半区(值 800 系) */
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-082 second round fetch succeeded");
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 208),
"TC-ADCIN-083 second round reads the later half (208)");
CHECK(adc_input_stm32_seq() == 2u, "TC-ADCIN-084 DMA completion counter = 2");
/* 半区奇偶口径:seq=1 -> 前半区,seq=2 -> 后半区(与 dma_beat 独立推导,互相证伪) */
CHECK(((adc_input_raw(ADC_SEM_UC, &v) == 0) && (v == 203)), "TC-ADCIN-085 UC stays consistent within the frame (203)");
/* 消费者超拍:两拍都没取 -> 中间那帧已被乒乓缓冲覆盖 -> 记 1 点丢帧 */
dma_beat(300);
dma_beat(400);
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-086 after skipping rounds the newest frame is still fetched");
CHECK(adc_input_stm32_gaps() == 1u, "TC-ADCIN-087 seq advanced by 2 -> records 1 dropped frame");
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 408), "TC-ADCIN-088 what came back is the newest frame (408)");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-089 a drop is not counted as a stall (two fault classes stay apart)");
/* 第二档:上面只跨了 1 帧(前进 2 记 1 点),"次数"与"点数"数值相同、钉不出单位。
* 这里连投三帧不消费 = 一次跨 2 帧,必须记 2 点(累计 1+2=3),把 2026-10-21 改的
* 「帧点数」口径钉死。断言不另立编号,仍属上面那条用例的覆盖点 */
dma_beat(500);
dma_beat(600);
dma_beat(700);
CHECK(stm32_take() == ADC_INPUT_RC_OK, "second overrun still returns latest frame");
CHECK(adc_input_stm32_gaps() == 3u, "delta 3 -> 2 points, total 3 (point unit)");
}
/* ============ TC-ADCIN-090..093:HAL 错误回调与句柄守卫 ============ */
static void tc_hw_errs(void) {
fakes_reset();
(void)adc_input_init();
HAL_ADC_ErrorCallback(&hadc1);
HAL_ADC_ErrorCallback(&hadc1);
CHECK(adc_input_stm32_hw_errs() == 2u, "TC-ADCIN-090 error callback total = 2");
HAL_ADC_ErrorCallback(&hadc_other);
CHECK(adc_input_stm32_hw_errs() == 2u, "TC-ADCIN-091 other ADC handles must not count (hadc guard)");
HAL_ADC_ConvHalfCpltCallback(&hadc_other);
HAL_ADC_ConvCpltCallback(&hadc_other);
CHECK(adc_input_stm32_seq() == 0u, "TC-ADCIN-092 a completion callback from another handle must not advance seq");
HAL_ADC_ErrorCallback(&hadc1);
CHECK(adc_input_stm32_hw_errs() == 3u, "TC-ADCIN-093 hw_errs and errs count separately and never interfere");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-09A hw_errs does not pollute errs");
}
/* ===================== TC-ADCIN-030..034:DMA 挂载失败 ===================== */
static void tc_init_dma_fail(void) {
int32_t v = 0;
fakes_reset();
s_start_dma_rc = HAL_ERROR;
CHECK(adc_input_init() == -2, "TC-ADCIN-030 HAL_ADC_Start_DMA failure -> init returns -2");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_START, "TC-ADCIN-031 status code = RC_START");
CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-032 failure counter +1");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-034 after an arming failure the STM32 side holds no valid data");
}
/* ===================== TC-ADCIN-035..03A:本拍无新帧(停摆) ===================== */
static void tc_take_stall(void) {
int32_t v = 0;
fakes_reset();
(void)adc_input_init();
/* 没有任何 DMA 完成回调 = TIM3 未启动 / ADC 停摆 / DMA 出错 */
CHECK(stm32_take() == ADC_INPUT_RC_STALL, "TC-ADCIN-035 no new frame -> RC_STALL(-2)");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_STALL, "TC-ADCIN-036 status code = RC_STALL");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-037 a single round without a new frame is not a fault (phase artefact)");
CHECK(adc_input_stm32_seq() == 0u, "TC-ADCIN-038 seq does not advance while stalled");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-039 a stalled round leaves the STM32 side without valid data");
(void)stm32_take(); /* 连续第二拍无新帧 = 真停摆 */
CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-03A two rounds in a row with no new frame -> stall count = 1");
(void)stm32_take(); /* 第三拍仍无新帧 */
CHECK(adc_input_stm32_errs() == 2u, "TC-ADCIN-03B an ongoing stall accumulates every round = 2");
CHECK(adc_input_stm32_gaps() == 0u, "TC-ADCIN-03C a stall is not counted as a drop (two fault classes stay apart)");
dma_beat(100); /* 恢复 */
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-03D after recovery the fetch succeeds");
CHECK(adc_input_stm32_gaps() == 0u, "TC-ADCIN-03E the recovery round advances by 1 -> not a drop");
}
/* ============ TC-ADCIN-070..078:停摆拍不得沿用上一拍旧值(陈旧帧) ============
* 背景(2026-10-10 真机实测发现):本层只有一份 s_stm32_raw[],而 s_stm32_valid
* 此前只在成功时置 1、失败时**从不清零** → 消费侧照样取数、照样压入采样环,
* s_frames_skip 抓不到。轮询版的触发路径是"超时拍半新半旧",DMA 版(v2.0)改成
* "本拍无新帧仍沿用上一拍"——**同一条缺陷、同一个防线**,故本组用例保留。
* TC-ADCIN-034 是"挂载失败后本就无数据"(valid 一直是 0),照不出这条路径——
* 必须先成功一拍。 */
static void tc_take_stale(void) {
int32_t v = 0;
/* --- 停摆支路:成功拍 -> 无新帧拍 -> 恢复拍 --- */
fakes_reset();
(void)adc_input_init();
dma_beat(100);
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-070 precondition: first round succeeded");
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 108), "TC-ADCIN-071 precondition: UB reads 108 (valid state)");
/* 第二拍没有任何 DMA 完成(TIM3 停 / ADC 停摆 / DMA 出错) */
CHECK(stm32_take() == ADC_INPUT_RC_STALL, "TC-ADCIN-072 second round has no new frame");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-073 a stalled round must not reuse the previous value -> -2");
CHECK(adc_input_raw(ADC_SEM_UC, &v) == -2, "TC-ADCIN-074 a stalled round must not emit any other STM32 channel either");
CHECK(adc_input_raw(ADC_SEM_TEMP, &v) == -2, "TC-ADCIN-074B same as above (TEMP also -2)");
dma_beat(200);
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-075 third round recovers successfully");
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 208), "TC-ADCIN-076 the recovery round can be read again (self-healing)");
/* --- 挂载失败支路:同样不得沿用(init 失败后整层无有效数据) --- */
fakes_reset();
s_start_dma_rc = HAL_ERROR;
(void)adc_input_init();
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-078 after an arming failure no value may be read -> -2");
}
/* ===================== TC-ADCIN-040..045:raw 参数守卫与无数据 ===================== */
static void tc_raw_guards(void) {
int32_t v = 0;
fakes_reset();
(void)adc_input_init();
CHECK(adc_input_raw(ADC_SEM_UA, NULL) == -1, "TC-ADCIN-040 out=NULL -> -1");
CHECK(adc_input_raw(ADC_SEM_COUNT, &v) == -1, "TC-ADCIN-041 ch out of range -> -1");
CHECK(adc_input_raw((adc_semantic_t)(-1), &v) == -1, "TC-ADCIN-042 ch negative -> -1");
CHECK(v == 0, "TC-ADCIN-043 on an argument error out is not written");
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-ADCIN-044 TPAFE has no valid frame -> -2");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-045 STM32 has no valid scan -> -2");
}
/* ===================== TC-ADCIN-046..04B:raw TPAFE 取数与补码 ===================== */
static void tc_raw_tpafe(void) {
int32_t v = 0;
fakes_reset();
(void)adc_input_init();
/* 注入 8 路 TPAFE 码值(互不相同),UA 应取 buf[6] / UN 取 buf[5] / IA 取 buf[2] */
s_fake_tpafe[0] = 1000;
s_fake_tpafe[1] = 1001;
s_fake_tpafe[2] = -1234;
s_fake_tpafe[3] = 1003;
s_fake_tpafe[4] = 1004;
s_fake_tpafe[5] = 2005;
s_fake_tpafe[6] = 3006;
s_fake_tpafe[7] = 1007;
s_snapshot_rc = 1;
CHECK(adc_input_poll() == 1, "TC-ADCIN-046 poll passes through the TPAFE snapshot code 1");
CHECK(s_snapshot_count == (uint8_t)ADC_INPUT_TPAFE_CH_NUM, "TC-ADCIN-047 snapshot requests 8 channels");
CHECK(s_snapshot_dst != NULL, "TC-ADCIN-048 the snapshot destination buffer is not NULL");
CHECK((adc_input_raw(ADC_SEM_UA, &v) == 0) && (v == 3006), "TC-ADCIN-049 UA -> buf[6]=3006");
CHECK((adc_input_raw(ADC_SEM_UN, &v) == 0) && (v == 2005), "TC-ADCIN-04A UN -> buf[5]=2005");
CHECK((adc_input_raw(ADC_SEM_IA, &v) == 0) && (v == -1234),
"TC-ADCIN-04B IA -> buf[2]=-1234 (two's-complement negative kept as is)");
}
/* ===================== TC-ADCIN-050..058:poll 契约 ===================== */
static void tc_poll(void) {
int32_t v = 0;
/* --- 无新帧(0):返回 0,TPAFE 无效,STM32 侧照常扫描 --- */
fakes_reset();
(void)adc_input_init();
s_snapshot_rc = 0;
dma_beat(100);
CHECK(adc_input_poll() == 0, "TC-ADCIN-050 snapshot with no new frame -> poll returns 0");
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-ADCIN-051 without a new frame the TPAFE side is invalid");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_OK, "TC-ADCIN-052 the STM32 fetch in the same round still runs");
/* --- 参数错(-1):返回 -1,TPAFE 无效 --- */
fakes_reset();
(void)adc_input_init();
s_snapshot_rc = -1;
CHECK(adc_input_poll() == -1, "TC-ADCIN-053 snapshot argument error -> poll returns -1");
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-ADCIN-054 on an argument error the TPAFE side is invalid");
/* --- 已有有效帧后再"无新帧":保持上次有效值(不清零) --- */
fakes_reset();
(void)adc_input_init();
s_fake_tpafe[6] = 777;
s_snapshot_rc = 1;
(void)adc_input_poll();
s_snapshot_rc = 0;
(void)adc_input_poll();
CHECK((adc_input_raw(ADC_SEM_UA, &v) == 0) && (v == 777),
"TC-ADCIN-055 an existing valid frame is not zeroed when no new frame arrives");
/* --- STM32 失败不阻断 TPAFE、不影响 poll 返回值 --- */
fakes_reset();
(void)adc_input_init();
s_fake_tpafe[2] = 55;
s_snapshot_rc = 1;
/* 不注入任何 DMA 完成 = STM32 侧停摆 */
CHECK(adc_input_poll() == 1, "TC-ADCIN-056 an STM32 stall does not change the poll return value (still 1)");
CHECK((adc_input_raw(ADC_SEM_IA, &v) == 0) && (v == 55),
"TC-ADCIN-057 an STM32 stall does not affect TPAFE data");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_STALL, "TC-ADCIN-058 an STM32 stall is exposed through the status code instead");
}
/* ===================== TC-ADCIN-060..066:rc / errs 观测 ===================== */
static void tc_rc_errs(void) {
fakes_reset();
(void)adc_input_init();
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_NOT_RUN, "TC-ADCIN-060 after init, before any scan = NOT_RUN(-3)");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-061 after init the failure counter = 0");
dma_beat(100);
(void)stm32_take(); /* 成功 */
CHECK((adc_input_stm32_rc() == ADC_INPUT_RC_OK) && (adc_input_stm32_errs() == 0u),
"TC-ADCIN-062 success: rc=OK and errs unchanged");
(void)stm32_take(); /* 停摆 1(单拍 = 相位伪影,不计) */
(void)stm32_take(); /* 停摆 2 -> 计 1 */
CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-063 two consecutive stalled rounds accumulate = 1");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_STALL, "TC-ADCIN-064 rc = the most recent result (STALL)");
dma_beat(200);
(void)stm32_take(); /* 恢复成功 */
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_OK, "TC-ADCIN-065 after a successful recovery rc=OK");
CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-066 errs increases monotonically (a success never lowers it)");
CHECK(adc_input_stm32_seq() == 2u, "TC-ADCIN-067 seq is advanced only by the DMA completion callback");
}
/* ===================== main ===================== */
int main(void) {
printf("=== ADC Input Tests ===\n");
printf("-- init / reset --\n");
tc_init();
printf("-- semantic map desc --\n");
tc_desc();
printf("-- map self-check --\n");
tc_map_check();
printf("-- stm32 take: success + rank/channel mapping --\n");
tc_take_ok();
printf("-- stm32 dma: ping-pong double buffer --\n");
tc_dma_pingpong();
printf("-- stm32 dma: HAL error callback + handle guard --\n");
tc_hw_errs();
printf("-- stm32 dma: Start_DMA fail --\n");
tc_init_dma_fail();
printf("-- stm32 take: no new frame (stall) --\n");
tc_take_stall();
printf("-- stm32 take: stale frame on stall beat --\n");
tc_take_stale();
printf("-- raw: guards / no-data --\n");
tc_raw_guards();
printf("-- raw: tpafe index + two's complement --\n");
tc_raw_tpafe();
printf("-- poll contract --\n");
tc_poll();
printf("-- rc / errs observability --\n");
tc_rc_errs();
printf("\n=== ADC Input Tests: %d/%d PASSED (failed=%d) ===\n",
s_passed, s_total, s_failed);
return (s_failed == 0) ? 0 : 1;
}