Files
STM32F4-Base/test/host/adc_input/adc_input_test.c
T
wandering1 dd8210867a adc_input: 失败拍清 s_stm32_valid,杜绝超时沿用上一拍旧值(单测 70->79)
缺陷(真机实测发现):本层只有一份 s_stm32_raw[],有效标志此前只在成功时
置 1、失败时从不清零 -> 超时拍消费侧照样取数、照样压入采样环(且超时前
几个 Rank 已写本拍新值、后面几个是上一拍旧值 = 半新半旧混合帧),直接污
染 20 点 DFT 相量窗;adc_input_poll() 返回值只由 TPAFE 决定,s_frames_skip
也抓不到它。

修复:stm32_scan() 两条失败支路(HAL_ADC_Start 失败 / 轮询超时)都清
s_stm32_valid -> 本拍 adc_input_raw() 对 STM32 语义通道返 -2 -> adc_task
走既有 skip 整帧跳过路径;下一拍扫描成功自动置回 1(自愈)。

测试:新增 TC-AI-070..078(70 -> 79 用例)。旧用例 TC-AI-034/039b 都是
init 后立刻失败(valid 本就是 0),照不出成功过之后再失败这条路径,故新
用例先成功一拍进入有效态再注入失败。**反向验证已做**:把支路改回旧行为 ->
TC-AI-039b/073/074 立即 FAIL(76/79),证明用例有效。

验证:主机单测 79/79 PASS;@build 0 错 0 警(Code 256868 -> 256872);
@test PASS(本轮无超时:adc1_err=0、skip=0、idle=0、frames=20001);
18 钩子全绿。文档同步 4 处(停摆分析 4.4 / ADC采样节拍 10.4 / ADC输入层
单测说明 / test-host README)。

遗留:2 ms 忙等本身未消(硬化 2 = DMA,方案 B2 待 CubeMX 配置)。
2026-10-10 23:08:01 +08:00

505 lines
22 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 模块名称:ADC 输入层(L1)主机侧单元测试
* 模块功能:在 PC 上对 App/algo/adc_input.c 做离线验证(6 个公开函数 + 2 个静态函数)。
*
* 被测对象:真实源文件 ../../../App/algo/adc_input.c(以 #include 原样纳入,
* 保证测的是生产代码而非副本)。平台依赖由本文件提供的**可注入桩**实现:
* - adc.h -> HAL_ADC_Start / PollForConversion / GetValue / Stop
* (可注入 启动失败 / 第 N 次轮询超时 / 逐 Rank 返回值)
* - 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-AI-001..006)——
* TC-AI-001 poll 失败后失败计数可见(前置状态构造)
* TC-AI-002 poll 成功后 TPAFE 侧可取数(前置状态构造)
* TC-AI-003 init 返回 0(映射表自检通过)
* TC-AI-004 init 后 STM32 状态码 = NOT_RUN
* TC-AI-005 init 后失败计数归零
* TC-AI-006 init 后 TPAFE / STM32 两侧均无有效数据(有效标志被清)
* —— adc_input_desc(TC-AI-010..01A)——
* TC-AI-010..018 九个语义通道 -> (来源, 下标) 与权威映射表逐项一致
* TC-AI-019/01A 越界(ADC_SEM_COUNT)与负数语义 -> NULL
* —— map_check(TC-AI-020)——
* TC-AI-020 真实映射表自洽(反向路径结构上不可达,见文件末说明)
* —— stm32_scan(TC-AI-021..078)——
* TC-AI-021..025 全通道成功:返回 0 / 状态码 / 不计失败 / Start-Stop 各一次 / 轮询 9 次
* TC-AI-026..02B **Rank -> 通道号映射**:UB->CH14(rank9) / UC->CH3(rank4) /
* IB->CH6(rank7) / IC->CH1(rank2) / IN->CH2(rank3) / TEMP->CH5(rank6)
* TC-AI-02C stm32_scan 不置 TPAFE 有效标志(两来源独立)
* TC-AI-030..034 HAL_ADC_Start 失败:返回 -1 / 状态码 RC_START / 计数 +1 / 不调 Stop
* TC-AI-035..039 轮询超时:返回 -2 / 状态码 RC_TIMEOUT / 计数 +1 / 调 Stop 收尾 / 立即退出
* TC-AI-03A 失败计数跨次单调累计
* TC-AI-070..078 **失败拍不得沿用上一拍旧值**(2026-10-10 补,真机实测发现的
* 正确性缺陷):先成功一拍让 STM32 侧进入有效态,再注入超时拍 /
* Start 失败拍 -> adc_input_raw() 必须返 -2(而非上一拍的值);
* 恢复拍重新置有效。TC-AI-034/039b 只覆盖"冷启动即失败",
* 照不出"成功过之后失败 → 静默沿用旧值",故单独立一组。
* —— adc_input_raw(TC-AI-040..04B)——
* TC-AI-040..043 参数守卫:out=NULL / 越界 / 负数 -> -1,且不写 out
* TC-AI-044/045 无有效数据 -> -2(两来源分别)
* TC-AI-046..048 poll 快照契约:返回码透传 / 请求 8 通道 / 目的缓冲非空
* TC-AI-049..04B TPAFE 按**下标**取数(UA=6 / UN=5 / IA=2)且补码负值原样保留
* —— adc_input_poll(TC-AI-050..058)——
* TC-AI-050..052 无新帧(0):返回 0 / TPAFE 无效 / STM32 侧照常扫描
* TC-AI-053/054 参数错(-1):返回 -1 / TPAFE 无效
* TC-AI-055 已有有效帧后再"无新帧"**不清零**(保持上次有效值)
* TC-AI-056..058 STM32 失败**不阻断** TPAFE、**不影响** poll 返回值,另由状态码暴露
* —— adc_input_stm32_rc / _errs(TC-AI-060..066)——
* TC-AI-060/061 init 后 = NOT_RUN / 计数 0
* TC-AI-062 成功: rc=OK 且 errs 不变
* TC-AI-063/064 两次失败累计 = 2,rc = 最近一次(TIMEOUT)
* TC-AI-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"
/* ===================== HAL ADC1 桩 =====================
* 被测代码经 HAL 只做四件事:启动、逐 Rank 轮询、读 DR、停止。桩把它们变成可注入:
* s_start_rc HAL_ADC_Start 返回值(注入启动失败)
* s_poll_fail_at PollForConversion 第几次(0 基)失败;-1 = 永不失败(注入超时)
* s_poll_vals[i] 第 i 个 Rank 读到的码值(默认 100+i,用于钉死 Rank->通道映射)
* s_poll_calls / s_rank_idx 轮询次数 / 取数次数(观测:超时后是否立即退出)
*/
static HAL_StatusTypeDef s_start_rc = HAL_OK;
static int s_poll_fail_at = -1;
static uint32_t s_poll_vals[16];
static int s_poll_calls = 0;
static int s_rank_idx = 0;
static int s_start_calls = 0;
static int s_stop_calls = 0;
/* 被测代码把 &hadc1 透传给 HAL 桩;桩不读其字段 */
ADC_HandleTypeDef hadc1;
HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef *hadc) {
(void)hadc;
s_start_calls++;
s_poll_calls = 0;
s_rank_idx = 0;
return s_start_rc;
}
HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef *hadc, uint32_t Timeout) {
(void)hadc;
(void)Timeout;
{
int idx = s_poll_calls;
s_poll_calls++;
if (s_poll_fail_at >= 0 && idx == s_poll_fail_at) {
return HAL_ERROR;
}
}
return HAL_OK;
}
uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef *hadc) {
uint32_t v;
(void)hadc;
v = (s_rank_idx < 16) ? s_poll_vals[s_rank_idx] : 0u;
s_rank_idx++;
return v;
}
HAL_StatusTypeDef HAL_ADC_Stop(ADC_HandleTypeDef *hadc) {
(void)hadc;
s_stop_calls++;
return HAL_OK;
}
/* ===================== 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"
/* ===================== 工具 ===================== */
/*
* 函数功能:把全部桩复位到"健康"初值
* 入口参数:无
* 返 回 值:无
* 限定条件:无
* 函数说明:默认 Start 成功、轮询永不超时、第 i 个 Rank 读到 100+i(互不相同,
* 以便逐通道钉死 Rank->通道号映射)、TPAFE 快照无新帧。
*/
static void fakes_reset(void) {
uint8_t i;
s_start_rc = HAL_OK;
s_poll_fail_at = -1;
s_poll_calls = 0;
s_rank_idx = 0;
s_start_calls = 0;
s_stop_calls = 0;
for (i = 0u; i < 16u; i++) {
s_poll_vals[i] = 100u + (uint32_t)i;
}
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;
}
}
/* ===================== TC-AI-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;
s_start_rc = HAL_ERROR;
(void)adc_input_poll();
CHECK(adc_input_stm32_errs() == 1u, "TC-AI-001 poll 失败后 STM32 失败计数 = 1");
CHECK((adc_input_raw(ADC_SEM_UA, &v) == 0) && (v == 4321),
"TC-AI-002 poll 后 UA 可取数且值正确");
CHECK(adc_input_init() == 0, "TC-AI-003 adc_input_init 返回 0(映射自检通过)");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_NOT_RUN,
"TC-AI-004 init 后 STM32 状态码 = NOT_RUN");
CHECK(adc_input_stm32_errs() == 0u, "TC-AI-005 init 后 STM32 失败计数归零");
CHECK((adc_input_raw(ADC_SEM_UA, &v) == -2) && (adc_input_raw(ADC_SEM_UB, &v) == -2),
"TC-AI-006 init 后 TPAFE / STM32 两侧均无有效数据");
}
/* ===================== TC-AI-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-AI-010 UA -> TPAFE buf[6]");
d = adc_input_desc(ADC_SEM_UB);
CHECK((d != NULL) && (d->source == ADC_SRC_STM32) && (d->index == 14u),
"TC-AI-011 UB -> STM32 CH14");
d = adc_input_desc(ADC_SEM_UC);
CHECK((d != NULL) && (d->source == ADC_SRC_STM32) && (d->index == 3u),
"TC-AI-012 UC -> STM32 CH3");
d = adc_input_desc(ADC_SEM_UN);
CHECK((d != NULL) && (d->source == ADC_SRC_TPAFE) && (d->index == 5u),
"TC-AI-013 UN -> TPAFE buf[5]");
d = adc_input_desc(ADC_SEM_IA);
CHECK((d != NULL) && (d->source == ADC_SRC_TPAFE) && (d->index == 2u),
"TC-AI-014 IA -> TPAFE buf[2]");
d = adc_input_desc(ADC_SEM_IB);
CHECK((d != NULL) && (d->source == ADC_SRC_STM32) && (d->index == 6u),
"TC-AI-015 IB -> STM32 CH6");
d = adc_input_desc(ADC_SEM_IC);
CHECK((d != NULL) && (d->source == ADC_SRC_STM32) && (d->index == 1u),
"TC-AI-016 IC -> STM32 CH1");
d = adc_input_desc(ADC_SEM_IN);
CHECK((d != NULL) && (d->source == ADC_SRC_STM32) && (d->index == 2u),
"TC-AI-017 IN -> STM32 CH2");
d = adc_input_desc(ADC_SEM_TEMP);
CHECK((d != NULL) && (d->source == ADC_SRC_STM32) && (d->index == 5u),
"TC-AI-018 TEMP -> STM32 CH5");
CHECK(adc_input_desc(ADC_SEM_COUNT) == NULL, "TC-AI-019 越界(ADC_SEM_COUNT) -> NULL");
CHECK(adc_input_desc((adc_semantic_t)(-1)) == NULL, "TC-AI-01A 负数语义 -> NULL");
}
/* ===================== TC-AI-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-AI-020 映射表自洽(TPAFE 下标与 STM32 Rank 均在范围内)");
}
/* ===================== TC-AI-021..02C:stm32_scan 成功路径 ===================== */
static void tc_scan_ok(void) {
int32_t v = 0;
fakes_reset();
(void)adc_input_init();
CHECK(stm32_scan() == ADC_INPUT_RC_OK, "TC-AI-021 stm32_scan 全通道成功 -> 0");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_OK, "TC-AI-022 状态码 = RC_OK");
CHECK(adc_input_stm32_errs() == 0u, "TC-AI-023 成功不计入失败");
CHECK((s_start_calls == 1) && (s_stop_calls == 1), "TC-AI-024 Start / Stop 各一次");
CHECK((s_poll_calls == (int)ADC_INPUT_STM32_CH_NUM) && (s_rank_idx == (int)ADC_INPUT_STM32_CH_NUM),
"TC-AI-025 轮询与取数各 9 次(Rank1..9)");
/* 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-AI-026 UB 取到 CH14(rank9)=108");
CHECK((adc_input_raw(ADC_SEM_UC, &v) == 0) && (v == 103), "TC-AI-027 UC 取到 CH3(rank4)=103");
CHECK((adc_input_raw(ADC_SEM_IB, &v) == 0) && (v == 106), "TC-AI-028 IB 取到 CH6(rank7)=106");
CHECK((adc_input_raw(ADC_SEM_IC, &v) == 0) && (v == 101), "TC-AI-029 IC 取到 CH1(rank2)=101");
CHECK((adc_input_raw(ADC_SEM_IN, &v) == 0) && (v == 102), "TC-AI-02A IN 取到 CH2(rank3)=102");
CHECK((adc_input_raw(ADC_SEM_TEMP, &v) == 0) && (v == 105), "TC-AI-02B TEMP 取到 CH5(rank6)=105");
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-AI-02C stm32_scan 不置 TPAFE 有效标志");
}
/* ===================== TC-AI-030..034:stm32_scan 启动失败 ===================== */
static void tc_scan_start_fail(void) {
int32_t v = 0;
fakes_reset();
(void)adc_input_init();
s_start_rc = HAL_ERROR;
CHECK(stm32_scan() == ADC_INPUT_RC_START, "TC-AI-030 HAL_ADC_Start 失败 -> -1");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_START, "TC-AI-031 状态码 = RC_START");
CHECK(adc_input_stm32_errs() == 1u, "TC-AI-032 失败计数 +1");
CHECK(s_stop_calls == 0, "TC-AI-033 启动失败不调用 Stop");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-AI-034 启动失败后 STM32 侧仍无有效数据");
}
/* ===================== TC-AI-035..03A:stm32_scan 轮询超时 ===================== */
static void tc_scan_timeout(void) {
int32_t v = 0;
fakes_reset();
(void)adc_input_init();
s_poll_fail_at = 4; /* 第 5 个 Rank 轮询超时 */
CHECK(stm32_scan() == ADC_INPUT_RC_TIMEOUT, "TC-AI-035 轮询超时 -> -2");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_TIMEOUT, "TC-AI-036 状态码 = RC_TIMEOUT");
CHECK(adc_input_stm32_errs() == 1u, "TC-AI-037 超时计入失败");
CHECK(s_stop_calls == 1, "TC-AI-038 超时前已 Start -> 调 Stop 收尾");
CHECK(s_poll_calls == 5, "TC-AI-039 超时后立即退出(只轮询 5 次)");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-AI-039b 超时后 STM32 侧无有效数据");
s_poll_fail_at = 0; /* 再失败一次 */
(void)stm32_scan();
CHECK(adc_input_stm32_errs() == 2u, "TC-AI-03A 失败计数跨次单调累计 = 2");
}
/* ============ TC-AI-070..077:失败拍不得沿用上一拍旧值(陈旧帧) ============
* 背景(2026-10-10 真机实测发现):本层只有一份 s_stm32_raw[],超时拍里"超时前
* 几个 Rank 已写本拍新值、后面几个还是上一拍旧值",而 s_stm32_valid 此前只在
* 成功时置 1、失败时**从不清零** → 消费侧照样取数、照样压入采样环,
* s_frames_skip 抓不到。TC-AI-034/039b 都是"init 后立刻失败"(valid 本就是 0),
* 因此照不出这条路径——必须先成功一拍。 */
static void tc_scan_stale(void) {
int32_t v = 0;
/* --- 超时支路:成功拍 -> 超时拍 -> 恢复拍 --- */
fakes_reset();
(void)adc_input_init();
CHECK(stm32_scan() == ADC_INPUT_RC_OK, "TC-AI-070 前置:首拍成功");
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 108), "TC-AI-071 前置:UB 取到 108(有效态)");
s_poll_fail_at = 4; /* 第 5 个 Rank 超时(前 4 个已写新值) */
CHECK(stm32_scan() == ADC_INPUT_RC_TIMEOUT, "TC-AI-072 第二拍轮询超时");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-AI-073 超时拍不得沿用上一拍值 -> -2");
CHECK(adc_input_raw(ADC_SEM_UC, &v) == -2, "TC-AI-074 超时拍连本拍已写入的 Rank 也不得出数");
s_poll_fail_at = -1;
CHECK(stm32_scan() == ADC_INPUT_RC_OK, "TC-AI-075 第三拍恢复成功");
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 108), "TC-AI-076 恢复拍重新可取数(自愈)");
/* --- 启动失败支路:同样不得沿用 --- */
fakes_reset();
(void)adc_input_init();
(void)stm32_scan(); /* 先成功一拍 */
s_start_rc = HAL_ERROR;
CHECK(stm32_scan() == ADC_INPUT_RC_START, "TC-AI-077 Start 失败拍");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-AI-078 Start 失败拍不得沿用上一拍值 -> -2");
}
/* ===================== TC-AI-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-AI-040 out=NULL -> -1");
CHECK(adc_input_raw(ADC_SEM_COUNT, &v) == -1, "TC-AI-041 ch 越界 -> -1");
CHECK(adc_input_raw((adc_semantic_t)(-1), &v) == -1, "TC-AI-042 ch 负数 -> -1");
CHECK(v == 0, "TC-AI-043 参数错时不写 out");
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-AI-044 TPAFE 无有效帧 -> -2");
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-AI-045 STM32 无有效扫描 -> -2");
}
/* ===================== TC-AI-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-AI-046 poll 透传 TPAFE 快照码 1");
CHECK(s_snapshot_count == (uint8_t)ADC_INPUT_TPAFE_CH_NUM, "TC-AI-047 快照请求 8 通道");
CHECK(s_snapshot_dst != NULL, "TC-AI-048 快照目的缓冲非空");
CHECK((adc_input_raw(ADC_SEM_UA, &v) == 0) && (v == 3006), "TC-AI-049 UA -> buf[6]=3006");
CHECK((adc_input_raw(ADC_SEM_UN, &v) == 0) && (v == 2005), "TC-AI-04A UN -> buf[5]=2005");
CHECK((adc_input_raw(ADC_SEM_IA, &v) == 0) && (v == -1234),
"TC-AI-04B IA -> buf[2]=-1234(补码负值原样保留)");
}
/* ===================== TC-AI-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;
CHECK(adc_input_poll() == 0, "TC-AI-050 快照无新帧 -> poll 返回 0");
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-AI-051 无新帧时 TPAFE 侧无效");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_OK, "TC-AI-052 同拍 STM32 扫描照常执行");
/* --- 参数错(-1):返回 -1,TPAFE 无效 --- */
fakes_reset();
(void)adc_input_init();
s_snapshot_rc = -1;
CHECK(adc_input_poll() == -1, "TC-AI-053 快照参数错 -> poll 返回 -1");
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-AI-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-AI-055 已有有效帧后无新帧不清零");
/* --- STM32 失败不阻断 TPAFE、不影响 poll 返回值 --- */
fakes_reset();
(void)adc_input_init();
s_fake_tpafe[2] = 55;
s_snapshot_rc = 1;
s_start_rc = HAL_ERROR;
CHECK(adc_input_poll() == 1, "TC-AI-056 STM32 失败不影响 poll 返回值(仍为 1)");
CHECK((adc_input_raw(ADC_SEM_IA, &v) == 0) && (v == 55),
"TC-AI-057 STM32 失败不影响 TPAFE 取数");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_START, "TC-AI-058 STM32 失败另由状态码暴露");
}
/* ===================== TC-AI-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-AI-060 init 后未扫描 = NOT_RUN(-3)");
CHECK(adc_input_stm32_errs() == 0u, "TC-AI-061 init 后失败计数 = 0");
(void)stm32_scan(); /* 成功 */
CHECK((adc_input_stm32_rc() == ADC_INPUT_RC_OK) && (adc_input_stm32_errs() == 0u),
"TC-AI-062 成功: rc=OK 且 errs 不变");
s_start_rc = HAL_ERROR;
(void)stm32_scan(); /* 失败 1 */
s_start_rc = HAL_OK;
s_poll_fail_at = 0;
(void)stm32_scan(); /* 失败 2 */
CHECK(adc_input_stm32_errs() == 2u, "TC-AI-063 两次失败累计 = 2");
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_TIMEOUT, "TC-AI-064 rc = 最近一次(TIMEOUT)");
s_poll_fail_at = -1;
(void)stm32_scan(); /* 恢复成功 */
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_OK, "TC-AI-065 恢复成功后 rc=OK");
CHECK(adc_input_stm32_errs() == 2u, "TC-AI-066 errs 单调递增(不因成功回落)");
}
/* ===================== 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 scan: success + rank/channel mapping --\n");
tc_scan_ok();
printf("-- stm32 scan: HAL_ADC_Start fail --\n");
tc_scan_start_fail();
printf("-- stm32 scan: poll timeout --\n");
tc_scan_timeout();
printf("-- stm32 scan: stale frame on failure beat --\n");
tc_scan_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;
}