Files
STM32F4-Base/test/host/adc_input/adc_input_test.c
T
wandering1 5a9988b557 chore(注释): 清掉源码里不可检索的过程编码
清掉的(读者无从解析、且所指文档已不存在)
- 已删任务文档的编号:Task #32、其内部 §5 / §4.3、P1-5、P0-2、P1-4a、
  "方案 a"、"Lead 批准 2026-10-12"
- 正文里当代词用的文件版本号:"(v2.7 起为 AboveNormal)""已随 v2.0 事件
  驱动改造删除""(v2.0:DMA 消费)""见文件头 v1.3 说明"等 → 改写成陈述现状
- 测试与脚本同类:adc_input 桩头"v2.0:DMA 模型"、stub/adc.h 的 v2.0、
  check_cubemx_survival.py 段头的"2026-10-10 v2.8"、run_all_suites.py 的"方案 A"

刻意保留
- 可检索登记簿锚点:P-AUDIT 真条目(P0-1/P1-8/P2-1/P2-9/P3-11)、S6/S11、
  E-31/E-44、N-18/N-23/N-32、B1/B5/B7/B8、L1-x~L5-x 功能号
- 文件头修改记录块内的 vN.M(规范 V1.0 §2.1/§2.2 要求保留该块;块内编号
  在本文件内自洽可读)

顺带纠正注释与代码已经不一致的过时事实
- 仍把 TIM6 写成现行节拍源:Src/main.c 的 include 注释与回调注释、
  Src/freertos.c 的 adcTask 块 → 改 TIM3;adc_input.h 修订条目里"ADC1 只认
  TIM2/TIM3/TIM8 的 TRGO"不准确 → 换成指向判据(ADC采样节拍设计说明 §3.2)
- App/algo/phasor.c 双区注释称"唯一消费者是 adcTask、为后续保护任务预留",
  实际 protectTask 已跨上下文读(prot_elm_run -> phasor_get)→ 改为"读者跨
  adcTask 心跳与 protectTask 判据两个上下文,勿改加锁、勿取消双区",并同步
  修正 docs/算法/采集与算法层设计说明.md 同一句
- adc_tick_irq_notify() 的"拒绝 ISR 内计算"理由仍写"内含 2 ms 忙等"(DMA 化
  后已无忙等)→ 换成三条真实理由:任务级共享数据 / 计算量 / 生成区回调
- 裸 §5(指代不明)与"规划 §1.6 / 规划 §4"昵称式 → 写成完整路径紧邻式

字节卫生(Edit 会静默剥 BOM)
- App/task/ui_task.c 与 Drivers/BSP/UI/ui_net_sink.c 被剥掉 BOM 两次,均按
  3 字节 EF BB BF 前缀补回并复验;全部 20 个文件 CR=0、U+FFFD=0、无行尾空白

门禁
- @build 0 错误 0 警告,且 Program Size 与清理前逐项相同
  (Code=213356 RO=18580 RW=1184 ZI=131480)→ 纯注释改动、中文未被误重编码
- ui-golden 首跑红:新工作树缺被 gitignore 的黄金参考 simulator/tests/ref/
  (不入库、只在主工作树生成过);从主工作树复制该基线后复跑通过 → 注释改动
  零像素影响
- 全套 pre-commit 在这 20 个文件上 Passed(coding-standard / cppcheck /
  6 个主机单测 / doc-links / task-table / mem-numbers / cubemx-survival /
  registration-check / mem-budget / font-charset / ui-golden / id-check)
- id-check 可判定比例 82.71% -> 82.88%(删掉的是"形似 P-AUDIT 但查无定义"的
  假锚点),悬空引用 863 -> 854
2026-10-21 11:39:53 +08:00

586 lines
28 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 连续两拍无新帧 -> 停摆计数 = 1");
CHECK((adc_input_raw(ADC_SEM_UA, &v) == 0) && (v == 4321),
"TC-ADCIN-002 poll 后 UA 可取数且值正确");
CHECK(adc_input_init() == 0, "TC-ADCIN-003 adc_input_init 返回 0(映射自检通过)");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_NOT_RUN,
"TC-ADCIN-004 init 后 STM32 状态码 = NOT_RUN");
CHECK(s_start_dma_calls == 1, "TC-ADCIN-003B init 挂载 DMA 恰好一次");
CHECK(s_dma_len == (2u * ADC_INPUT_STM32_CH_NUM), "TC-ADCIN-003C DMA 长度 = 2 x 9(乒乓双缓冲)");
CHECK(s_dma_buf != NULL, "TC-ADCIN-003D DMA 目的缓冲地址已交下来");
CHECK(adc_input_stm32_seq() == 0u, "TC-ADCIN-005B init 后 DMA 完成计数 = 0");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-005 init 后 STM32 失败计数归零");
CHECK((adc_input_raw(ADC_SEM_UA, &v) == -2) && (adc_input_raw(ADC_SEM_UB, &v) == -2),
"TC-ADCIN-006 init 后 TPAFE / STM32 两侧均无有效数据");
}
/* ===================== 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 越界(ADC_SEM_COUNT) -> NULL");
CHECK(adc_input_desc((adc_semantic_t)(-1)) == NULL, "TC-ADCIN-01A 负数语义 -> 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 映射表自洽(TPAFE 下标与 STM32 Rank 均在范围内)");
}
/* ===================== 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 取到新帧 -> 0");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_OK, "TC-ADCIN-022 状态码 = RC_OK");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-023 成功不计入失败");
CHECK(s_start_dma_calls == 1, "TC-ADCIN-024 DMA 只在 init 挂一次(每拍不重启)");
CHECK(adc_input_stm32_seq() == 1u, "TC-ADCIN-025 DMA 完成计数 = 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 取到 CH14(rank9)=108");
CHECK((adc_input_raw(ADC_SEM_UC, &v) == 0) && (v == 103), "TC-ADCIN-027 UC 取到 CH3(rank4)=103");
CHECK((adc_input_raw(ADC_SEM_IB, &v) == 0) && (v == 106), "TC-ADCIN-028 IB 取到 CH6(rank7)=106");
CHECK((adc_input_raw(ADC_SEM_IC, &v) == 0) && (v == 101), "TC-ADCIN-029 IC 取到 CH1(rank2)=101");
CHECK((adc_input_raw(ADC_SEM_IN, &v) == 0) && (v == 102), "TC-ADCIN-02A IN 取到 CH2(rank3)=102");
CHECK((adc_input_raw(ADC_SEM_TEMP, &v) == 0) && (v == 105), "TC-ADCIN-02B TEMP 取到 CH5(rank6)=105");
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-ADCIN-02C stm32_take 不置 TPAFE 有效标志");
}
/* ============ 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 第一拍取帧成功");
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 108),
"TC-ADCIN-081 读的是刚完成的前半区(108),不是正在写的后半区");
dma_beat(200); /* 第 2 帧 -> 后半区(传输完成回调) */
dma_fill(0u, 800u); /* 模拟 DMA 正在写前半区(值 800 系) */
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-082 第二拍取帧成功");
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 208),
"TC-ADCIN-083 第二拍读到后半区(208)");
CHECK(adc_input_stm32_seq() == 2u, "TC-ADCIN-084 DMA 完成计数 = 2");
/* 半区奇偶口径:seq=1 -> 前半区,seq=2 -> 后半区(与 dma_beat 独立推导,互相证伪) */
CHECK(((adc_input_raw(ADC_SEM_UC, &v) == 0) && (v == 203)), "TC-ADCIN-085 UC 同帧内一致(203)");
/* 消费者超拍:两拍都没取 -> 中间那帧已被乒乓缓冲覆盖 -> 记一次丢帧 */
dma_beat(300);
dma_beat(400);
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-086 超拍后仍取到最新帧");
CHECK(adc_input_stm32_gaps() == 1u, "TC-ADCIN-087 seq 前进 2 -> 记一次丢帧");
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 408), "TC-ADCIN-088 取到的是最新帧(408)");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-089 丢帧不计入停摆(两类故障分开)");
}
/* ============ 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 错误回调累计 = 2");
HAL_ADC_ErrorCallback(&hadc_other);
CHECK(adc_input_stm32_hw_errs() == 2u, "TC-ADCIN-091 别的 ADC 句柄不得计入(hadc 守卫)");
HAL_ADC_ConvHalfCpltCallback(&hadc_other);
HAL_ADC_ConvCpltCallback(&hadc_other);
CHECK(adc_input_stm32_seq() == 0u, "TC-ADCIN-092 别的句柄的完成回调不得推进 seq");
HAL_ADC_ErrorCallback(&hadc1);
CHECK(adc_input_stm32_hw_errs() == 3u, "TC-ADCIN-093 hw_errs 与 errs 分开计数、互不干扰");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-09A hw_errs 不污染 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 失败 -> init 返回 -2");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_START, "TC-ADCIN-031 状态码 = RC_START");
CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-032 失败计数 +1");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-034 挂载失败后 STM32 侧无有效数据");
}
/* ===================== 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 无新帧 -> RC_STALL(-2)");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_STALL, "TC-ADCIN-036 状态码 = RC_STALL");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-037 单拍无新帧不计故障(相位伪影)");
CHECK(adc_input_stm32_seq() == 0u, "TC-ADCIN-038 停摆期间 seq 不前进");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-039 停摆拍 STM32 侧无有效数据");
(void)stm32_take(); /* 连续第二拍无新帧 = 真停摆 */
CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-03A 连续两拍无新帧 -> 停摆计数 = 1");
(void)stm32_take(); /* 第三拍仍无新帧 */
CHECK(adc_input_stm32_errs() == 2u, "TC-ADCIN-03B 停摆持续 -> 每拍累计 = 2");
CHECK(adc_input_stm32_gaps() == 0u, "TC-ADCIN-03C 停摆不计入丢帧(两类故障分开)");
dma_beat(100); /* 恢复 */
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-03D 恢复后取帧成功");
CHECK(adc_input_stm32_gaps() == 0u, "TC-ADCIN-03E 恢复拍前进 1 -> 不算丢帧");
}
/* ============ 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 前置:首拍成功");
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 108), "TC-ADCIN-071 前置:UB 取到 108(有效态)");
/* 第二拍没有任何 DMA 完成(TIM3 停 / ADC 停摆 / DMA 出错) */
CHECK(stm32_take() == ADC_INPUT_RC_STALL, "TC-ADCIN-072 第二拍无新帧");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-073 停摆拍不得沿用上一拍值 -> -2");
CHECK(adc_input_raw(ADC_SEM_UC, &v) == -2, "TC-ADCIN-074 停摆拍连其它 STM32 通道也不得出数");
CHECK(adc_input_raw(ADC_SEM_TEMP, &v) == -2, "TC-ADCIN-074B 同上(TEMP 亦 -2)");
dma_beat(200);
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-ADCIN-075 第三拍恢复成功");
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 208), "TC-ADCIN-076 恢复拍重新可取数(自愈)");
/* --- 挂载失败支路:同样不得沿用(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 挂载失败后不得取到任何值 -> -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 越界 -> -1");
CHECK(adc_input_raw((adc_semantic_t)(-1), &v) == -1, "TC-ADCIN-042 ch 负数 -> -1");
CHECK(v == 0, "TC-ADCIN-043 参数错时不写 out");
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-ADCIN-044 TPAFE 无有效帧 -> -2");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-ADCIN-045 STM32 无有效扫描 -> -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 透传 TPAFE 快照码 1");
CHECK(s_snapshot_count == (uint8_t)ADC_INPUT_TPAFE_CH_NUM, "TC-ADCIN-047 快照请求 8 通道");
CHECK(s_snapshot_dst != NULL, "TC-ADCIN-048 快照目的缓冲非空");
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(补码负值原样保留)");
}
/* ===================== 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 快照无新帧 -> poll 返回 0");
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-ADCIN-051 无新帧时 TPAFE 侧无效");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_OK, "TC-ADCIN-052 同拍 STM32 取帧照常执行");
/* --- 参数错(-1):返回 -1,TPAFE 无效 --- */
fakes_reset();
(void)adc_input_init();
s_snapshot_rc = -1;
CHECK(adc_input_poll() == -1, "TC-ADCIN-053 快照参数错 -> poll 返回 -1");
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-ADCIN-054 参数错时 TPAFE 侧无效");
/* --- 已有有效帧后再"无新帧":保持上次有效值(不清零) --- */
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 已有有效帧后无新帧不清零");
/* --- 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 STM32 停摆不影响 poll 返回值(仍为 1)");
CHECK((adc_input_raw(ADC_SEM_IA, &v) == 0) && (v == 55),
"TC-ADCIN-057 STM32 停摆不影响 TPAFE 取数");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_STALL, "TC-ADCIN-058 STM32 停摆另由状态码暴露");
}
/* ===================== 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 init 后未扫描 = NOT_RUN(-3)");
CHECK(adc_input_stm32_errs() == 0u, "TC-ADCIN-061 init 后失败计数 = 0");
dma_beat(100);
(void)stm32_take(); /* 成功 */
CHECK((adc_input_stm32_rc() == ADC_INPUT_RC_OK) && (adc_input_stm32_errs() == 0u),
"TC-ADCIN-062 成功: rc=OK 且 errs 不变");
(void)stm32_take(); /* 停摆 1(单拍 = 相位伪影,不计) */
(void)stm32_take(); /* 停摆 2 -> 计 1 */
CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-063 连续两拍停摆累计 = 1");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_STALL, "TC-ADCIN-064 rc = 最近一次(STALL)");
dma_beat(200);
(void)stm32_take(); /* 恢复成功 */
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_OK, "TC-ADCIN-065 恢复成功后 rc=OK");
CHECK(adc_input_stm32_errs() == 1u, "TC-ADCIN-066 errs 单调递增(不因成功回落)");
CHECK(adc_input_stm32_seq() == 2u, "TC-ADCIN-067 seq 只由 DMA 完成回调推进");
}
/* ===================== 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;
}