/* * 模块名称: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 #include #include /* ===================== 平台桩(先纳入,覆盖真实头) ===================== */ #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), "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, "second round fetch succeeded"); CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 208), "second round reads the later half (208)"); CHECK(adc_input_stm32_seq() == 2u, "DMA completion counter = 2"); /* 半区奇偶口径:seq=1 -> 前半区,seq=2 -> 后半区(与 dma_beat 独立推导,互相证伪) */ CHECK(((adc_input_raw(ADC_SEM_UC, &v) == 0) && (v == 203)), "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, "seq advanced by 2 -> records 1 dropped frame"); CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 408), "what came back is the newest frame (408)"); CHECK(adc_input_stm32_errs() == 0u, "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, "other ADC handles must not count (hadc guard)"); HAL_ADC_ConvHalfCpltCallback(&hadc_other); HAL_ADC_ConvCpltCallback(&hadc_other); CHECK(adc_input_stm32_seq() == 0u, "a completion callback from another handle must not advance seq"); HAL_ADC_ErrorCallback(&hadc1); CHECK(adc_input_stm32_hw_errs() == 3u, "hw_errs and errs count separately and never interfere"); CHECK(adc_input_stm32_errs() == 0u, "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, "status code = RC_START"); CHECK(adc_input_stm32_errs() == 1u, "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, "status code = RC_STALL"); CHECK(adc_input_stm32_errs() == 0u, "a single round without a new frame is not a fault (phase artefact)"); CHECK(adc_input_stm32_seq() == 0u, "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, "an ongoing stall accumulates every round = 2"); CHECK(adc_input_stm32_gaps() == 0u, "a stall is not counted as a drop (two fault classes stay apart)"); dma_beat(100); /* 恢复 */ CHECK(stm32_take() == ADC_INPUT_RC_OK, "after recovery the fetch succeeds"); CHECK(adc_input_stm32_gaps() == 0u, "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), "precondition: UB reads 108 (valid state)"); /* 第二拍没有任何 DMA 完成(TIM3 停 / ADC 停摆 / DMA 出错) */ CHECK(stm32_take() == ADC_INPUT_RC_STALL, "second round has no new frame"); CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "a stalled round must not reuse the previous value -> -2"); CHECK(adc_input_raw(ADC_SEM_UC, &v) == -2, "a stalled round must not emit any other STM32 channel either"); CHECK(adc_input_raw(ADC_SEM_TEMP, &v) == -2, "a stalled round must not emit TEMP either -> -2"); dma_beat(200); CHECK(stm32_take() == ADC_INPUT_RC_OK, "third round recovers successfully"); CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 208), "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, "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; }