adc: 节拍源 TIM6->TIM3 + ADC1 走 DMA 乒乓双缓冲,消除 2 ms 忙等(P1-5 单拍硬上界办结)
用户选定最彻底方案(B2):DMA + 定时器 TRGO 硬件触发 + 消费上一拍 已完成的半区。对应 P1-5 拆出的"单拍硬上界",现已办结。 硬件事实更正(查 CubeMX 器件库 + 本仓 HAL 头证实):F4 上 ADC1 的 外部触发清单里没有 TIM6/TIM7 TRGO(只接 DAC)。故 1 kHz 节拍源由 TIM6 改为 TIM3(PSC 83 / ARR 999,TRGO=UPDATE),同一个更新事件 同时 ① ISR 脉冲 TPAFE CONVST ② 经 TRGO 触发 ADC1 一轮 9 通道 ③ 置位 ADC_WAKE_FLAG 唤醒 adcTask。TIM6 从 .ioc 整体移除。 代码: - Src/tim.c/h:新增 MX_TIM3_Init(含 MasterSlaveMode),删 htim6 - Src/adc.c:ExternalTrigConv=T3_TRGO + EDGE_RISING + DMAContinuousRequests=ENABLE;MspInit 挂 hdma_adc1(DMA2_Stream4 循环模式)。DDS 位关掉 = 一轮只发一次请求,静默丢 8 路 - Src/dma.c:DMA2_Stream4 NVIC 优先级 6(CubeMX 换流会把它重置为 0, 优先级 0 会抢占 FreeRTOS 临界区,违反"外设中断 ≥5") - Src/main.c:周期回调加 TIM3 分支;stm32f4xx_it.c 向量同步 - App/algo/adc_input.c:stm32_scan() → stm32_take(),逐通道 PollForConversion 全删;乒乓半区由完成计数奇偶推出(只读一个 32 位量 → 与 ISR 无竞态);STM32 侧数据固定滞后 1 拍 - App/task/adc_task.c:心跳日志拆出四类计数 观测性:adc1_seq(完成计数)/ adc1_gap(seq 前进 >1 = 丢帧)/ adc1_err(连续 ≥2 拍无新帧 = 停摆)/ adc1_hw(HAL 错误回调)。 单拍无新帧是消费者相位伪影(DMA 完成落在唤醒后 ≈67 µs),不计故障。 验证: - 主机单测 79 → 101 全绿;反向验证四连均咬(半区奇偶取反 11 条、 停摆拍置 valid 4 条、STALL_RUN_MAX 改 1 为 6 条、gap 阈值 1 条) - @build 0 错 0 警,Code 256872 → 257660 - @test PASS:adcp 122 µs → 8 µs、adc1_err=0、adc1_hw=0、 adc1_gap=27/20002 拍、adc1_seq 与 seq 恒定偏移 2 - stack_report 7/7 PASS,adcTask 832 B / 余 59%(不变) - cubemx-survival 新增 5 项审计(TIM3 分支 / T3_TRGO / DDS / DMA 挂载 / 流优先级),28 通过 0 失败 文档:ADC 采样节拍设计说明新增 §11;看门狗停摆分析 §4.4 与 §8 收口; 算法规划 L1-1/L1-3、通道映射、测试说明、各 README 同步。
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
/*
|
||||
* 模块名称:ADC 输入层(L1)主机侧单元测试
|
||||
* 模块功能:在 PC 上对 App/algo/adc_input.c 做离线验证(6 个公开函数 + 2 个静态函数)。
|
||||
* 模块功能:在 PC 上对 App/algo/adc_input.c 做离线验证(8 个公开函数 + 2 个静态函数)。
|
||||
*
|
||||
* 被测对象:真实源文件 ../../../App/algo/adc_input.c(以 #include 原样纳入,
|
||||
* 保证测的是生产代码而非副本)。平台依赖由本文件提供的**可注入桩**实现:
|
||||
* - adc.h -> HAL_ADC_Start / PollForConversion / GetValue / Stop
|
||||
* (可注入 启动失败 / 第 N 次轮询超时 / 逐 Rank 返回值)
|
||||
* - adc.h -> HAL_ADC_Start_DMA(可注入挂载失败);DMA 完成由测试
|
||||
* 直接调用被测代码实现的 HAL_ADC_Conv[Half]CpltCallback
|
||||
* 注入(dma_beat()),并可往乒乓缓冲写任意码值
|
||||
* - tpafe5160.h-> tpafe5160_snapshot(可注入 新帧/无新帧/参数错 + 帧内容)
|
||||
*
|
||||
* 为什么要主机侧单测(而不是只用板端套件):
|
||||
@@ -29,19 +30,24 @@
|
||||
* 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 次
|
||||
* —— stm32_take(TC-AI-021..078,v2.0 DMA 模型)——
|
||||
* TC-AI-021..025 取到新帧:返回 0 / 状态码 / 不计失败 / DMA 只挂一次 / seq=1
|
||||
* 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 只覆盖"冷启动即失败",
|
||||
* 照不出"成功过之后失败 → 静默沿用旧值",故单独立一组。
|
||||
* TC-AI-02C stm32_take 不置 TPAFE 有效标志(两来源独立)
|
||||
* TC-AI-080..089 **乒乓双缓冲**:消费侧读"刚完成的半区",即使另一半正被 DMA
|
||||
* 改写也不得读到(撕裂帧防线);两拍交替半区、seq 推进;
|
||||
* 超拍未消费 -> 记一次丢帧(gap),与停摆(err)分开计数
|
||||
* TC-AI-090..09A HAL 错误回调计数 + 三个回调的 `hadc == &hadc1` 句柄守卫
|
||||
* (别的句柄不得污染本层 seq / hw_errs)
|
||||
* TC-AI-030..034 HAL_ADC_Start_DMA 失败:init 返回 -2 / 状态码 RC_START / 计数 +1
|
||||
* TC-AI-035..039 本拍无新帧:返回 RC_STALL / **单拍不计故障**(相位伪影)/ seq 不前进
|
||||
* TC-AI-03A..03E 连续两拍无新帧才计停摆;停摆与丢帧分开计数;恢复拍自愈
|
||||
* TC-AI-070..078 **停摆拍不得沿用上一拍旧值**(2026-10-10 补,真机实测发现的
|
||||
* 正确性缺陷;DMA 版改由"无新帧"触发同一路径):先成功一拍让
|
||||
* STM32 侧进入有效态,再注入无新帧拍 -> adc_input_raw() 必须返 -2;
|
||||
* 恢复拍重新置有效。TC-AI-034 只覆盖"挂载失败即无数据",
|
||||
* 照不出"成功过之后停摆 → 静默沿用旧值",故单独立一组。
|
||||
* —— adc_input_raw(TC-AI-040..04B)——
|
||||
* TC-AI-040..043 参数守卫:out=NULL / 越界 / 负数 -> -1,且不写 out
|
||||
* TC-AI-044/045 无有效数据 -> -2(两来源分别)
|
||||
@@ -71,58 +77,32 @@
|
||||
/* ===================== 平台桩(先纳入,覆盖真实头) ===================== */
|
||||
#include "adc.h"
|
||||
#include "tpafe5160.h"
|
||||
#include "adc_input.h" /* 取 ADC_INPUT_STM32_CH_NUM 等常量(桩辅助函数在被测源之前) */
|
||||
|
||||
/* ===================== 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 轮询次数 / 取数次数(观测:超时后是否立即退出)
|
||||
/* ===================== HAL ADC1 桩(v2.0: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_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;
|
||||
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(ADC_HandleTypeDef *hadc) {
|
||||
HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef *hadc, uint32_t *pData, uint32_t Length) {
|
||||
(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;
|
||||
s_start_dma_calls++;
|
||||
s_dma_buf = pData;
|
||||
s_dma_len = Length;
|
||||
return s_start_dma_rc;
|
||||
}
|
||||
|
||||
/* ===================== TPAFE5160 快照桩 ===================== */
|
||||
@@ -165,21 +145,16 @@ static int s_total = 0, s_passed = 0, s_failed = 0;
|
||||
* 入口参数:无
|
||||
* 返 回 值:无
|
||||
* 限定条件:无
|
||||
* 函数说明:默认 Start 成功、轮询永不超时、第 i 个 Rank 读到 100+i(互不相同,
|
||||
* 以便逐通道钉死 Rank->通道号映射)、TPAFE 快照无新帧。
|
||||
* 函数说明:默认 DMA 挂载成功、TPAFE 快照无新帧。DMA 缓冲由 adc_input_init() 交下来,
|
||||
* 复位时清空指针(此时不得调用 dma_fill / dma_beat)。
|
||||
*/
|
||||
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_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;
|
||||
@@ -189,6 +164,43 @@ static void fakes_reset(void) {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:往乒乓缓冲的指定半区写入一帧"采样值"(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-AI-001..006:init ===================== */
|
||||
|
||||
static void tc_init(void) {
|
||||
@@ -198,16 +210,20 @@ static void tc_init(void) {
|
||||
/* 前置:制造"STM32 失败一次 + TPAFE 有效一帧"的状态,再验 init 是否清干净 */
|
||||
s_snapshot_rc = 1;
|
||||
s_fake_tpafe[6] = 4321;
|
||||
s_start_rc = HAL_ERROR;
|
||||
(void)adc_input_poll();
|
||||
(void)adc_input_poll(); /* 单拍无新帧 = 相位伪影,不计故障 */
|
||||
(void)adc_input_poll(); /* 连续第二拍无新帧 = 真停摆 */
|
||||
|
||||
CHECK(adc_input_stm32_errs() == 1u, "TC-AI-001 poll 失败后 STM32 失败计数 = 1");
|
||||
CHECK(adc_input_stm32_errs() == 1u, "TC-AI-001 连续两拍无新帧 -> 停摆计数 = 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(s_start_dma_calls == 1, "TC-AI-003B init 挂载 DMA 恰好一次");
|
||||
CHECK(s_dma_len == (2u * ADC_INPUT_STM32_CH_NUM), "TC-AI-003C DMA 长度 = 2 x 9(乒乓双缓冲)");
|
||||
CHECK(s_dma_buf != NULL, "TC-AI-003D DMA 目的缓冲地址已交下来");
|
||||
CHECK(adc_input_stm32_seq() == 0u, "TC-AI-005B init 后 DMA 完成计数 = 0");
|
||||
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 两侧均无有效数据");
|
||||
@@ -261,20 +277,20 @@ static void tc_map_check(void) {
|
||||
|
||||
/* ===================== TC-AI-021..02C:stm32_scan 成功路径 ===================== */
|
||||
|
||||
static void tc_scan_ok(void) {
|
||||
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_scan() == ADC_INPUT_RC_OK, "TC-AI-021 stm32_scan 全通道成功 -> 0");
|
||||
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-AI-021 stm32_take 取到新帧 -> 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)");
|
||||
CHECK(s_start_dma_calls == 1, "TC-AI-024 DMA 只在 init 挂一次(每拍不重启)");
|
||||
CHECK(adc_input_stm32_seq() == 1u, "TC-AI-025 DMA 完成计数 = 1");
|
||||
|
||||
/* Rank -> 通道号映射:第 i 个 Rank 读到 100+i。
|
||||
/* 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");
|
||||
@@ -283,78 +299,139 @@ static void tc_scan_ok(void) {
|
||||
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 有效标志");
|
||||
CHECK(adc_input_raw(ADC_SEM_UA, &v) == -2, "TC-AI-02C stm32_take 不置 TPAFE 有效标志");
|
||||
}
|
||||
|
||||
/* ===================== TC-AI-030..034:stm32_scan 启动失败 ===================== */
|
||||
/* ============ TC-AI-080..085:乒乓双缓冲(读刚完成半区,不读正在写的半区) ============
|
||||
* 这是 DMA 方案最关键的一条:消费侧必须读"上一拍刚完成的那一半",而 DMA 此刻正在
|
||||
* 写另一半。若读错半区,取到的是**正在被改写**的数据 → 半新半旧的撕裂帧。 */
|
||||
|
||||
static void tc_scan_start_fail(void) {
|
||||
static void tc_dma_pingpong(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");
|
||||
dma_beat(100); /* 第 1 帧 -> 前半区(半传输回调) */
|
||||
dma_fill(1u, 900u); /* 模拟 DMA 正在写后半区(值 900 系) */
|
||||
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-AI-080 第一拍取帧成功");
|
||||
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 108),
|
||||
"TC-AI-081 读的是刚完成的前半区(108),不是正在写的后半区");
|
||||
|
||||
dma_beat(200); /* 第 2 帧 -> 后半区(传输完成回调) */
|
||||
dma_fill(0u, 800u); /* 模拟 DMA 正在写前半区(值 800 系) */
|
||||
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-AI-082 第二拍取帧成功");
|
||||
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 208),
|
||||
"TC-AI-083 第二拍读到后半区(208)");
|
||||
CHECK(adc_input_stm32_seq() == 2u, "TC-AI-084 DMA 完成计数 = 2");
|
||||
|
||||
/* 半区奇偶口径:seq=1 -> 前半区,seq=2 -> 后半区(与 dma_beat 独立推导,互相证伪) */
|
||||
CHECK(((adc_input_raw(ADC_SEM_UC, &v) == 0) && (v == 203)), "TC-AI-085 UC 同帧内一致(203)");
|
||||
|
||||
/* 消费者超拍:两拍都没取 -> 中间那帧已被乒乓缓冲覆盖 -> 记一次丢帧 */
|
||||
dma_beat(300);
|
||||
dma_beat(400);
|
||||
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-AI-086 超拍后仍取到最新帧");
|
||||
CHECK(adc_input_stm32_gaps() == 1u, "TC-AI-087 seq 前进 2 -> 记一次丢帧");
|
||||
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 408), "TC-AI-088 取到的是最新帧(408)");
|
||||
CHECK(adc_input_stm32_errs() == 0u, "TC-AI-089 丢帧不计入停摆(两类故障分开)");
|
||||
}
|
||||
|
||||
/* ============ TC-AI-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-AI-090 错误回调累计 = 2");
|
||||
|
||||
HAL_ADC_ErrorCallback(&hadc_other);
|
||||
CHECK(adc_input_stm32_hw_errs() == 2u, "TC-AI-091 别的 ADC 句柄不得计入(hadc 守卫)");
|
||||
|
||||
HAL_ADC_ConvHalfCpltCallback(&hadc_other);
|
||||
HAL_ADC_ConvCpltCallback(&hadc_other);
|
||||
CHECK(adc_input_stm32_seq() == 0u, "TC-AI-092 别的句柄的完成回调不得推进 seq");
|
||||
|
||||
HAL_ADC_ErrorCallback(&hadc1);
|
||||
CHECK(adc_input_stm32_hw_errs() == 3u, "TC-AI-093 hw_errs 与 errs 分开计数、互不干扰");
|
||||
CHECK(adc_input_stm32_errs() == 0u, "TC-AI-09A hw_errs 不污染 errs");
|
||||
}
|
||||
|
||||
/* ===================== TC-AI-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-AI-030 HAL_ADC_Start_DMA 失败 -> init 返回 -2");
|
||||
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 侧仍无有效数据");
|
||||
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-AI-034 挂载失败后 STM32 侧无有效数据");
|
||||
}
|
||||
|
||||
/* ===================== TC-AI-035..03A:stm32_scan 轮询超时 ===================== */
|
||||
/* ===================== TC-AI-035..03A:本拍无新帧(停摆) ===================== */
|
||||
|
||||
static void tc_scan_timeout(void) {
|
||||
static void tc_take_stall(void) {
|
||||
int32_t v = 0;
|
||||
|
||||
fakes_reset();
|
||||
(void)adc_input_init();
|
||||
s_poll_fail_at = 4; /* 第 5 个 Rank 轮询超时 */
|
||||
/* 没有任何 DMA 完成回调 = TIM3 未启动 / ADC 停摆 / DMA 出错 */
|
||||
|
||||
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 侧无有效数据");
|
||||
CHECK(stm32_take() == ADC_INPUT_RC_STALL, "TC-AI-035 无新帧 -> RC_STALL(-2)");
|
||||
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_STALL, "TC-AI-036 状态码 = RC_STALL");
|
||||
CHECK(adc_input_stm32_errs() == 0u, "TC-AI-037 单拍无新帧不计故障(相位伪影)");
|
||||
CHECK(adc_input_stm32_seq() == 0u, "TC-AI-038 停摆期间 seq 不前进");
|
||||
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-AI-039 停摆拍 STM32 侧无有效数据");
|
||||
|
||||
s_poll_fail_at = 0; /* 再失败一次 */
|
||||
(void)stm32_scan();
|
||||
CHECK(adc_input_stm32_errs() == 2u, "TC-AI-03A 失败计数跨次单调累计 = 2");
|
||||
(void)stm32_take(); /* 连续第二拍无新帧 = 真停摆 */
|
||||
CHECK(adc_input_stm32_errs() == 1u, "TC-AI-03A 连续两拍无新帧 -> 停摆计数 = 1");
|
||||
(void)stm32_take(); /* 第三拍仍无新帧 */
|
||||
CHECK(adc_input_stm32_errs() == 2u, "TC-AI-03B 停摆持续 -> 每拍累计 = 2");
|
||||
CHECK(adc_input_stm32_gaps() == 0u, "TC-AI-03C 停摆不计入丢帧(两类故障分开)");
|
||||
|
||||
dma_beat(100); /* 恢复 */
|
||||
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-AI-03D 恢复后取帧成功");
|
||||
CHECK(adc_input_stm32_gaps() == 0u, "TC-AI-03E 恢复拍前进 1 -> 不算丢帧");
|
||||
}
|
||||
|
||||
/* ============ 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),
|
||||
* 因此照不出这条路径——必须先成功一拍。 */
|
||||
/* ============ TC-AI-070..078:停摆拍不得沿用上一拍旧值(陈旧帧) ============
|
||||
* 背景(2026-10-10 真机实测发现):本层只有一份 s_stm32_raw[],而 s_stm32_valid
|
||||
* 此前只在成功时置 1、失败时**从不清零** → 消费侧照样取数、照样压入采样环,
|
||||
* s_frames_skip 抓不到。轮询版的触发路径是"超时拍半新半旧",DMA 版(v2.0)改成
|
||||
* "本拍无新帧仍沿用上一拍"——**同一条缺陷、同一个防线**,故本组用例保留。
|
||||
* TC-AI-034 是"挂载失败后本就无数据"(valid 一直是 0),照不出这条路径——
|
||||
* 必须先成功一拍。 */
|
||||
|
||||
static void tc_scan_stale(void) {
|
||||
static void tc_take_stale(void) {
|
||||
int32_t v = 0;
|
||||
|
||||
/* --- 超时支路:成功拍 -> 超时拍 -> 恢复拍 --- */
|
||||
/* --- 停摆支路:成功拍 -> 无新帧拍 -> 恢复拍 --- */
|
||||
fakes_reset();
|
||||
(void)adc_input_init();
|
||||
CHECK(stm32_scan() == ADC_INPUT_RC_OK, "TC-AI-070 前置:首拍成功");
|
||||
dma_beat(100);
|
||||
CHECK(stm32_take() == 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 也不得出数");
|
||||
/* 第二拍没有任何 DMA 完成(TIM3 停 / ADC 停摆 / DMA 出错) */
|
||||
CHECK(stm32_take() == ADC_INPUT_RC_STALL, "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 停摆拍连其它 STM32 通道也不得出数");
|
||||
CHECK(adc_input_raw(ADC_SEM_TEMP, &v) == -2, "TC-AI-074B 同上(TEMP 亦 -2)");
|
||||
|
||||
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 恢复拍重新可取数(自愈)");
|
||||
dma_beat(200);
|
||||
CHECK(stm32_take() == ADC_INPUT_RC_OK, "TC-AI-075 第三拍恢复成功");
|
||||
CHECK((adc_input_raw(ADC_SEM_UB, &v) == 0) && (v == 208), "TC-AI-076 恢复拍重新可取数(自愈)");
|
||||
|
||||
/* --- 启动失败支路:同样不得沿用 --- */
|
||||
/* --- 挂载失败支路:同样不得沿用(init 失败后整层无有效数据) --- */
|
||||
fakes_reset();
|
||||
s_start_dma_rc = HAL_ERROR;
|
||||
(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");
|
||||
CHECK(adc_input_raw(ADC_SEM_UB, &v) == -2, "TC-AI-078 挂载失败后不得取到任何值 -> -2");
|
||||
}
|
||||
|
||||
/* ===================== TC-AI-040..045:raw 参数守卫与无数据 ===================== */
|
||||
@@ -409,9 +486,10 @@ static void tc_poll(void) {
|
||||
fakes_reset();
|
||||
(void)adc_input_init();
|
||||
s_snapshot_rc = 0;
|
||||
dma_beat(100);
|
||||
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 扫描照常执行");
|
||||
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_OK, "TC-AI-052 同拍 STM32 取帧照常执行");
|
||||
|
||||
/* --- 参数错(-1):返回 -1,TPAFE 无效 --- */
|
||||
fakes_reset();
|
||||
@@ -436,11 +514,11 @@ static void tc_poll(void) {
|
||||
(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)");
|
||||
/* 不注入任何 DMA 完成 = STM32 侧停摆 */
|
||||
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-057 STM32 停摆不影响 TPAFE 取数");
|
||||
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_STALL, "TC-AI-058 STM32 停摆另由状态码暴露");
|
||||
}
|
||||
|
||||
/* ===================== TC-AI-060..066:rc / errs 观测 ===================== */
|
||||
@@ -452,22 +530,21 @@ static void tc_rc_errs(void) {
|
||||
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(); /* 成功 */
|
||||
dma_beat(100);
|
||||
(void)stm32_take(); /* 成功 */
|
||||
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)");
|
||||
(void)stm32_take(); /* 停摆 1(单拍 = 相位伪影,不计) */
|
||||
(void)stm32_take(); /* 停摆 2 -> 计 1 */
|
||||
CHECK(adc_input_stm32_errs() == 1u, "TC-AI-063 连续两拍停摆累计 = 1");
|
||||
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_STALL, "TC-AI-064 rc = 最近一次(STALL)");
|
||||
|
||||
s_poll_fail_at = -1;
|
||||
(void)stm32_scan(); /* 恢复成功 */
|
||||
dma_beat(200);
|
||||
(void)stm32_take(); /* 恢复成功 */
|
||||
CHECK(adc_input_stm32_rc() == ADC_INPUT_RC_OK, "TC-AI-065 恢复成功后 rc=OK");
|
||||
CHECK(adc_input_stm32_errs() == 2u, "TC-AI-066 errs 单调递增(不因成功回落)");
|
||||
CHECK(adc_input_stm32_errs() == 1u, "TC-AI-066 errs 单调递增(不因成功回落)");
|
||||
CHECK(adc_input_stm32_seq() == 2u, "TC-AI-067 seq 只由 DMA 完成回调推进");
|
||||
}
|
||||
|
||||
/* ===================== main ===================== */
|
||||
@@ -481,14 +558,18 @@ int main(void) {
|
||||
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("-- 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");
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
做什么:
|
||||
用本机 MSVC 编译 test/host/adc_input/adc_input_test.c(它以 #include 纳入
|
||||
**生产源文件** App/algo/adc_input.c),在 PC 上离线跑语义映射表 / STM32 ADC1
|
||||
顺序扫描 / TPAFE 快照消费 / 取数守卫 / poll 契约 / 状态码统计,失败即非 0 退出。
|
||||
DMA 乒乓帧消费(含半区选择)/ TPAFE 快照消费 / 取数守卫 / poll 契约 / 四类故障
|
||||
计数(seq / gap / err / hw),失败即非 0 退出。
|
||||
零硬件、零烧录、毫秒级。
|
||||
|
||||
工具链探测 / INCLUDE-LIB 拼接 / 编译运行 / 退出码判定全部委托给通用 harness
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
*
|
||||
* 保真点:F4 HAL 的 ADC_CHANNEL_x 宏值**即通道号 x**(0..15),本桩按此定义,
|
||||
* 故被测代码里 `(uint8_t)ADC_CHANNEL_14` 得到 14 —— 与真机一致。
|
||||
* 另声明 4 个 HAL_ADC_* 接口,由测试单元给强符号桩(可注入失败/超时/返回值)。
|
||||
* 另声明 1 个 HAL_ADC_Start_DMA 接口(由测试单元给强符号桩,可注入挂载失败)与
|
||||
* 3 个 HAL 回调(**由被测代码 adc_input.c 实现**,测试直接调用它们模拟 DMA 中断)。
|
||||
*
|
||||
* 创建日期:2026-10-10
|
||||
*/
|
||||
@@ -50,9 +51,14 @@ extern ADC_HandleTypeDef hadc1;
|
||||
#define ADC_CHANNEL_14 (0x0000000EU)
|
||||
#define ADC_CHANNEL_15 (0x0000000FU)
|
||||
|
||||
HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef *hadc);
|
||||
HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef *hadc, uint32_t Timeout);
|
||||
uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef *hadc);
|
||||
HAL_StatusTypeDef HAL_ADC_Stop(ADC_HandleTypeDef *hadc);
|
||||
/* v2.0:STM32 侧改 TIM3 TRGO 触发 + DMA 双缓冲,本层只调 Start_DMA 一次;
|
||||
* 逐通道 Start / PollForConversion / GetValue / Stop 已从被测代码中消失,桩同步删除 */
|
||||
HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef *hadc, uint32_t *pData, uint32_t Length);
|
||||
|
||||
/* 三个 HAL 弱回调:真机上由 DMA 中断上下文调用,这里由**被测代码实现**、
|
||||
* 由测试单元直接调用以模拟"一帧搬运完成" */
|
||||
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc);
|
||||
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc);
|
||||
void HAL_ADC_ErrorCallback(ADC_HandleTypeDef *hadc);
|
||||
|
||||
#endif /* ADC_H */
|
||||
|
||||
Reference in New Issue
Block a user