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 配置)。
This commit is contained in:
@@ -65,11 +65,17 @@ static uint32_t s_stm32_errs; /* STM32 扫描累计失败次数 */
|
||||
* DISABLE),按 Rank 顺序逐通道轮询 EOC 并读 DR,结果按 ADC1 通道号
|
||||
* 存入 s_stm32_raw[]。EOCSelection=SINGLE_CONV 且未用 DMA → 每个通道
|
||||
* 产生一次 EOC,可逐通道轮询(见 HAL_ADC_PollForConversion 说明)。
|
||||
* 两条失败支路(启动失败 / 轮询超时)**都会清 s_stm32_valid**:本拍
|
||||
* STM32 侧判为无有效数据,绝不沿用上一拍旧值(详见支路内注释)。
|
||||
* 超时在**首个**失败通道处即 Stop+return,不轮询剩余通道 → 单拍耗时
|
||||
* 上界 = 一次超时(≈2~3 ms,HAL_GetTick 1 ms 粒度),不是 9 次。
|
||||
*/
|
||||
static int stm32_scan(void) {
|
||||
uint8_t i;
|
||||
|
||||
if (HAL_ADC_Start(&hadc1) != HAL_OK) {
|
||||
/* 失败拍必须清有效标志,见下方超时分支说明(同一语义) */
|
||||
s_stm32_valid = 0u;
|
||||
s_stm32_rc = ADC_INPUT_RC_START;
|
||||
s_stm32_errs++;
|
||||
return ADC_INPUT_RC_START;
|
||||
@@ -78,6 +84,14 @@ static int stm32_scan(void) {
|
||||
for (i = 0u; i < ADC_INPUT_STM32_CH_NUM; i++) {
|
||||
if (HAL_ADC_PollForConversion(&hadc1, ADC_INPUT_POLL_TIMEOUT_MS) != HAL_OK) {
|
||||
(void)HAL_ADC_Stop(&hadc1);
|
||||
/* ⚠ 失败拍**不得**沿用上一拍的旧值:本层只有一份 s_stm32_raw[],
|
||||
* 超时前几个 Rank 已写入**本拍新值**、后面几个还是**上一拍旧值** →
|
||||
* 若保持 s_stm32_valid=1,消费侧会拿到"半新半旧"的混合帧并照样压入
|
||||
* 采样环(adc_input_poll() 的返回值只由 TPAFE 决定,s_frames_skip
|
||||
* 也抓不到它),直接污染 20 点 DFT 相量窗。清标志后 adc_input_raw()
|
||||
* 对 STM32 语义通道返 -2 → adc_task 走既有的"整帧跳过"路径。
|
||||
* 下一拍扫描成功会重新置 1(自愈),无需额外恢复动作。 */
|
||||
s_stm32_valid = 0u;
|
||||
s_stm32_rc = ADC_INPUT_RC_TIMEOUT;
|
||||
s_stm32_errs++;
|
||||
return ADC_INPUT_RC_TIMEOUT;
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
|
||||
| 区域 | 用量 | 容量 | 占比 |
|
||||
|---|---|---|---|
|
||||
| ER_IROM1 | 273,028 B (266.63 KB) | 1,048,576 B (1024 KB) | 26.0% |
|
||||
| ER_IROM1 | 273,032 B (266.63 KB) | 1,048,576 B (1024 KB) | 26.0% |
|
||||
| RW_IRAM1 | 91,336 B (89.20 KB) | 131,072 B (128 KB) | 69.7% |
|
||||
| RW_CCM | 49,184 B (48.03 KB) | 65,280 B (64 KB) | 75.3% |
|
||||
| **Flash (ROM 合计)** | **273,836 B (267.4 KB)** | 1,048,576 B (1024 KB) | **26.1%** |
|
||||
| **Flash (ROM 合计)** | **273,840 B (267.4 KB)** | 1,048,576 B (1024 KB) | **26.1%** |
|
||||
<!-- MEM_SNAPSHOT:END -->
|
||||
|
||||
各区的容量与常量来源:
|
||||
|
||||
@@ -239,6 +239,7 @@ t=1000 µs 下一个 TIM6 更新事件
|
||||
| 2026-10-10 | **事件驱动消费方案 a 已实施(Task #32 P1-5)** | 四处改动落地:`Src/main.c` TIM6 分支调 `adc_tick_irq_notify()`;`adc_task.c` 循环尾改 `osThreadFlagsWait(ADC_WAKE_FLAG, osFlagsWaitAny, osWaitForever)` 并删 `ADC_TASK_BEAT_MS`/`next`;`Src/freertos.c` 优先级 `Normal`→**`AboveNormal`**;`App/task/adc_task.h` 加 `ADC_WAKE_FLAG` 与 ISR 入口。实施时补**就绪门**(§10.6,规划未预见:TIM6 比调度器早起 ≈100 ms)。`@build` 0 错 0 警(Code 256368→256728);`@test` PASS:**`idle = 0`/19999 拍**(改前 1~649)、`frames` 1000.0 Hz、`seq−frames = 7` 正是就绪窗口、UI 侧 `uip/uiw/uis` 无退化 |
|
||||
| 2026-10-10 | **§10.1/§10.3/§10.4 复核修正(无功能改动)** | ① 2 ms 忙等的单拍上界由"9 通道 ×2 = 18 ms"更正为**一次超时 ≈2~3 ms**(`stm32_scan()` 在首个超时处即 `Stop`+`return`);② 6 次上板对照 `adcp` = **122 µs**(超时 0 次的 5 轮)/ **2209 µs**(超时 1 次的那轮)→ 2 ms 不是常态开销;③ `adcm` 2.1 ms 峰值**归因改判**为每 10 s 一次的心跳日志(`idle=0`/20000 反证无每拍 2 ms);④ 新增超时的**正确性**代价(超时拍 STM32 侧静默沿用上一拍值,`skip` 抓不到)与硬化排序 |
|
||||
| 2026-10-10 | **补 ADC1 上电自校准(§10.4)** | 发现 F4 必需的自校准在本项目**完全缺失**(CubeMX 不生成 + 裁剪 HAL 无 `HAL_ADCEx_Calibration_Start` + 裁剪 CMSIS 头无 `ADC_CR2_CAL`)→ 在 `Src/adc.c` 的 `USER CODE` 区实现 `adc1_calibrate()`(RM0090 §13.3.6 时序),`MX_ADC1_Init()` 末尾调用。新增审计项 `adc1-calibration-call`/`-impl`。`@build` 0 错 0 警(Code 256728→256868,+140 B);`@test` PASS,`adcp` 仍 122 µs、`adc1_err=0`;实测 STM32 侧 6 路同向偏移 +10~+63 LSB、TPAFE 侧不变 |
|
||||
| 2026-10-10 | **修"失败拍沿用旧值"(§10.4 硬化 ①)** | `stm32_scan()` 两条失败支路都清 `s_stm32_valid` → 失败拍走既有 `skip` 整帧跳过,不再产出"半新半旧"混合帧污染相量窗;下一拍成功自愈。主机单测 70 → **79** 用例(新增 TC-AI-070..078),并做反向验证(改回旧行为 3 条立即 FAIL)。`@build` 0 错 0 警(Code 256868→256872)、`@test` PASS。硬化 ②(DMA 消忙等)仍未做,并更正原表述:**F4 的 ADC EOC 没有 EXTI 通道**,只有 `ADC1_2_IRQn` |
|
||||
|
||||
---
|
||||
|
||||
@@ -314,9 +315,13 @@ t=1000 µs 下一个 TIM6 更新事件
|
||||
而 `adc_input_poll()` 的返回值只由 TPAFE 决定 → 该拍照样 `r == 1`、照样压环,STM32 侧几路
|
||||
(UB/UC/IB/IC/IN/温度)**静默沿用上一拍的值**(`adc_input.c:209-212`),`s_frames_skip`
|
||||
只统计"从未成功过"、抓不到它。
|
||||
**硬化方向(待做,按性价比排序)**:① 超时即置 `s_stm32_valid = 0` → 本拍 STM32 侧判为无有效
|
||||
数据,走既有 `skip` 整帧跳过路径(一行改动、无时序风险,先把"静默陈旧帧"这条正确性问题关掉);
|
||||
② EOC 改 EXTI 或 DMA + 非阻塞查询,彻底消除忙等;③ DMA 双缓冲属 V0.2 硬件项(§10.5)。
|
||||
**硬化排序**:① ✅ **已实施(2026-10-10)**——`stm32_scan()` 两条失败支路(启动失败 / 轮询
|
||||
超时)都清 `s_stm32_valid` → 本拍 STM32 侧判为无有效数据,走既有 `skip` 整帧跳过路径,
|
||||
下一拍成功自动恢复;主机侧新增 **TC-AI-070..078** 钉死并做反向验证(改回旧行为 3 条立即
|
||||
FAIL)。② **消忙等 = DMA**:`HAL_ADC_Start_DMA(&hadc1, buf, 9)`,scan 模式下每个通道 EOC
|
||||
由 DMA 自动搬进缓冲,任务侧不再逐通道 `PollForConversion`(⚠ **F4 的 ADC EOC 没有 EXTI
|
||||
通道**,只有 `ADC1_2_IRQn`——"EOC 改 EXTI"在 F4 上不成立,别照 F3/G4 的做法抄)。
|
||||
③ 双缓冲 / 等周波触发属 V0.2 硬件项(§10.5)。
|
||||
- **已做的一项(2026-10-10):补 F4 ADC 上电自校准**。CubeMX **不生成**该步骤,且本仓库裁剪版
|
||||
HAL **没有** `HAL_ADCEx_Calibration_Start` → 由 `Src/adc.c` 的 `adc1_calibrate()` 实现
|
||||
(`USER CODE` 区 = L2,审计项 `adc1-calibration-call` / `adc1-calibration-impl` 兜底)。
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
> **模块**:ADC Input Layer(L1 采集与预处理,`App/algo/adc_input.c`)
|
||||
> **测试类型**:**主机侧纯逻辑单元测试**(L0.5 层)——在 PC 上离线验证语义通道映射、
|
||||
> STM32 ADC1 顺序扫描、TPAFE 快照消费与取数守卫,零硬件、零烧录、毫秒级、不占 MCU 的 RAM。
|
||||
> **状态**:✅ 已落地(70 用例,`/W4` 0 告警,已挂 pre-commit 钩子)
|
||||
> **状态**:✅ 已落地(79 用例,`/W4` 0 告警,已挂 pre-commit 钩子)
|
||||
> **日期**:2026-10-10
|
||||
> **关联文档**:
|
||||
> [`../../AGENTS.md`](../../AGENTS.md)(平台约束 · CubeMX 生成区铁律)、
|
||||
@@ -171,6 +171,16 @@ test/host/adc_input/adc_input_test.c
|
||||
| **TC-AI-039** | 超时后**立即退出**(只轮询 5 次,不继续扫完) | 不长时间阻塞 1 ms 保护节拍 |
|
||||
| **TC-AI-039b** | 超时后 STM32 侧无有效数据 | 不产出半帧 |
|
||||
| **TC-AI-03A** | 再失败一次 → 计数 = 2 | 跨次单调累计 |
|
||||
| **TC-AI-070..078** | ⭐ **失败拍不得沿用上一拍旧值**:先成功一拍让 STM32 侧进入有效态(`UB`=108),再注入超时拍 / `Start` 失败拍 → `adc_input_raw()` 必须返 **-2**;恢复拍重新可取数 | **陈旧帧**(2026-10-10 真机实测发现的缺陷,见下注) |
|
||||
|
||||
> ⚠ **为什么 TC-AI-034 / 039b 照不出这条缺陷**:两者都是"`adc_input_init()` 后**立刻**失败",
|
||||
> 此时 `s_stm32_valid` 本来就是 0,断言 -2 恒成立。真正的缺陷路径是**成功过之后再失败**:
|
||||
> 本层只有一份 `s_stm32_raw[]`,超时拍里"超时前几个 Rank 已写本拍新值、后面几个还是上一拍
|
||||
> 旧值",而有效标志此前**只在成功时置 1、失败时从不清零** → 消费侧照样取数、照样压入采样环
|
||||
> (`adc_input_poll()` 返回值只由 TPAFE 决定,`s_frames_skip` 抓不到),直接污染 20 点 DFT 窗。
|
||||
> 修复 = 两条失败支路都清 `s_stm32_valid`(`App/algo/adc_input.c`)。
|
||||
> **反向验证已做**:把超时支路改回旧行为,TC-AI-039b / 073 / 074 三条立即 FAIL(76/79)→
|
||||
> 证明这组用例真的咬得住。
|
||||
|
||||
### 2.5 取数与参数守卫(`adc_input_raw`)
|
||||
|
||||
@@ -263,3 +273,4 @@ V0.1 上 8 路电气量分属两片转换器且**无共同采样触发**(N-18
|
||||
| 日期 | 变更 |
|
||||
|---|---|
|
||||
| 2026-10-10 | 创建;70 用例全绿(`/W4` 0 告警);覆盖 `adc_input.c` 全部 8 个函数(6 公开 + 2 静态);挂 pre-commit 钩子 `adc-input-unit-test` |
|
||||
| 2026-10-10 | **新增 TC-AI-070..078(70 → 79 用例)**:钉死"失败拍不得沿用上一拍旧值"。配套修复 `App/algo/adc_input.c` 两条失败支路(清 `s_stm32_valid`)。已做**反向验证**:改回旧行为 → TC-AI-039b/073/074 立即 FAIL(76/79),证明用例有效 |
|
||||
|
||||
@@ -230,11 +230,15 @@ TIM6 ISR 提供。**"超拍永久落后 → 空转"这一类缺陷在结构上
|
||||
- 但 `adcTask` 现已在 **`AboveNormal`(32)**,一旦某拍真走到 2 ms 忙等,它挤的是
|
||||
netTask / ftpTask / defaultTask / timeSyncTask(改前是同级竞争)→ **这是 P1-5 换来的新暴露,
|
||||
记在账上**;
|
||||
- ⚠ **超时的第二个代价是正确性,不只是耗时**:超时拍 `s_stm32_valid` 仍是上次成功留下的 1,
|
||||
而 `adc_input_poll()` 的返回值只由 TPAFE 决定 → 该拍照样 `r == 1`、照样压环,STM32 侧几路
|
||||
(UB/UC/IB/IC/IN/温度)**静默沿用上一拍的值**(`adc_input.c:209-212`);`s_frames_skip`
|
||||
只统计"从未成功过",抓不到它。→ 硬化第一步应是**超时即置 `s_stm32_valid = 0`**(走既有
|
||||
`skip` 整帧跳过路径,一行改动),而不是先去消忙等。
|
||||
- ✅ **超时的第二个代价是正确性,不只是耗时(2026-10-10 已修)**:超时拍 `s_stm32_valid` 此前
|
||||
仍是上次成功留下的 1,而 `adc_input_poll()` 的返回值只由 TPAFE 决定 → 该拍照样 `r == 1`、
|
||||
照样压环,STM32 侧几路(UB/UC/IB/IC/IN/温度)**静默沿用上一拍的值**(且超时前几个 Rank 已写
|
||||
本拍新值 → 半新半旧的混合帧);`s_frames_skip` 只统计"从未成功过",抓不到它。
|
||||
**修复** = `stm32_scan()` 两条失败支路(`HAL_ADC_Start` 失败 / 轮询超时)**都清
|
||||
`s_stm32_valid`** → 本拍 `adc_input_raw()` 对 STM32 语义通道返 -2 → `adc_task` 走既有
|
||||
`skip` 整帧跳过路径,下一拍成功自动恢复。主机侧新增 **TC-AI-070..078** 钉死(旧用例
|
||||
TC-AI-034/039b 都是"init 后立刻失败",`valid` 本就是 0,照不出这条路径),并做**反向验证**:
|
||||
把支路改回旧行为 → 3 条立即 FAIL(76/79)。**剩余未做 = 消掉 2 ms 忙等本身(DMA)**。
|
||||
|
||||
---
|
||||
|
||||
@@ -544,3 +548,4 @@ A(门控 + 兜底)→ @build + @test + e2e → 看 [PROF]:uis 掉到 µs
|
||||
| 2026-10-10 | **P1-5 已实施(方案 a:事件驱动消费)**:`Src/main.c` TIM6 分支在 `tpafe5160_start_conv_irq()` 后调 `adc_tick_irq_notify()`;`App/task/adc_task.c` 循环尾 `osDelayUntil` → `osThreadFlagsWait(ADC_WAKE_FLAG, osFlagsWaitAny, osWaitForever)` 并删 `ADC_TASK_BEAT_MS`/`next`;`Src/freertos.c` 的 `adcTask` 优先级 `Normal` → **`AboveNormal`**;`App/task/adc_task.h` 新增 `ADC_WAKE_FLAG` 与 ISR 入口。实施时补一条规划未预见的**就绪门** `s_consumer_ready`——TIM6 在 `app_main_init()` 就启动,比建任务与 `osKernelStart()` 早 ≈100 ms,ISR 不能对尚不存在的消费者动手(同本仓 TIM7 分支 2026-10-02 的实测坑)。验证:`@build` 0 错 0 警(Code 256368→**256728**)、**`@test` 6/6 PASS**、**`idle` 由 1~649 降到 0**、`frames` 严格 1000.0 Hz、`seq−frames = 7` 正是就绪窗口、UI 侧 `uip/uiw/uis` 一字不差、0 `task stall`。本文同步:§4.1 补优先级变化与可接受性论证、§4.3 补"再现状"(P0-2 那段已删)、§4.4 改为"忙等未改但敏感度上升"、§8 的 P1-5 行转 ✅ 并拆出仍未办的"单拍硬上界"、健康剖面按 6 连跑复测值更新 |
|
||||
| 2026-10-10 | **§4.4 复核修正(无代码改动)**:① 原文"9 通道全超时最坏 = **18 ms** 连占"**有误**——`stm32_scan()` 在首个超时处即 `HAL_ADC_Stop()`+`return`(`adc_input.c:79-84`),单拍上界 = **一次**超时 ≈2~3 ms;② 6 次上板对照证明 **2 ms 只在超时拍出现**:超时 1 次那轮 `adcp = 2209 µs`,超时 0 次的 5 轮 `adcp = 122 µs`;③ `adcm ≈ 2.1 ms` 的归因由"ADC 采集"**改判**为每 10 s 一次的心跳日志(`idle = 0`/20000 反证不存在"每拍 2 ms");④ 新增超时的**正确性**代价——超时拍 `s_stm32_valid` 仍为 1 → STM32 侧静默沿用上一拍值、`skip` 抓不到 → 硬化第一步应是"超时即置 `s_stm32_valid = 0`"。同步修正 `App/task/adc_task.c` 两处过期注释与 `ADC采样节拍设计说明.md` §10.1/§10.3/§10.4 |
|
||||
| 2026-10-10 | **补 F4 ADC 上电自校准(§7.2 候选根因之一)**:核查发现本项目**从未**做过 ADC 自校准——CubeMX 不生成该步骤,且仓库裁剪版 HAL 无 `HAL_ADCEx_Calibration_Start`、裁剪版 CMSIS 头无 `ADC_CR2_CAL`。已在 `Src/adc.c` 的 `USER CODE` 区实现 `adc1_calibrate()`(清 ADON→等生效→置 ADON→置 CAL→等 CAL 由硬件清零)并在 `MX_ADC1_Init()` 末尾调用;新增 `cubemx-survival` 审计项 `adc1-calibration-call` / `adc1-calibration-impl`。验证:`@build` 0 错 0 警(Code 256728→**256868**)、`@test` **PASS**(`adcp` 仍 122 µs、`adc1_err=0`、`idle=0`);**校准前后同一拍 STM32 侧 6 路同向偏移** `UB 898→961`/`UC 978→1038`/`IB 906→922`/`IC 1061→1071`/`IN 1017→1055`/`T 790→846`(+10~+63 LSB),TPAFE 侧 `UA/UN/IA` 一字不变 → 变化确来自 ADC1 校准。§4.4 的 2 ms 忙等与"超时沿用旧值"**仍未处理** |
|
||||
| 2026-10-10 | **修 §4.4 的"超时拍静默沿用旧值"(正确性缺陷)**:`stm32_scan()` 两条失败支路(`HAL_ADC_Start` 失败 / 轮询超时)现都清 `s_stm32_valid` → 失败拍 `adc_input_raw()` 返 -2 → `adc_task` 走既有 `skip` 整帧跳过,不再把上一拍的值当新数据压入 20 点 DFT 窗;下一拍成功自动恢复。主机侧新增 **TC-AI-070..078**(70 → 79 用例)钉死——旧用例 TC-AI-034/039b 都是"init 后立刻失败"(`valid` 本就是 0),照不出"成功过之后再失败"这条路径。**反向验证已做**:把支路改回旧行为 → TC-AI-039b/073/074 立即 FAIL(76/79)。验证:`@build` 0 错 0 警(Code 256868→**256872**)、主机单测 **79/79**、`@test` **PASS**(本轮无超时:`adc1_err=0`、`skip=0`、`idle=0`)。**剩余未做 = 消掉 2 ms 忙等本身(DMA)** |
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@
|
||||
| `cfg_file/stub/` | 桩头文件(`dbg_log.h`),覆盖 MCU 版以在 PC 上编译 |
|
||||
| `phasor/run_unit_test.py` | **相量层(L2)单测**:离线编译真实 `App/algo/phasor.c` + `sample_ring.c`(**无需任何桩**,纯算法),57 用例覆盖傅氏系数表 / 幅值·相位 / 谐波抑制与提取 / 频偏 / 采样环 / 双区原子交换 |
|
||||
| `phasor/phasor_test.c` | 测试单元本体(`#include` 两个生产源码 + 合成任意波形) |
|
||||
| `adc_input/run_unit_test.py` | **ADC 输入层(L1)单测**:离线编译真实 `App/algo/adc_input.c`,70 用例给每个 Rank 注入互异码值**逐通道钉死映射**,并覆盖 HAL 启动失败 / 转换超时 / 参数错等真机难复现分支 |
|
||||
| `adc_input/run_unit_test.py` | **ADC 输入层(L1)单测**:离线编译真实 `App/algo/adc_input.c`,79 用例给每个 Rank 注入互异码值**逐通道钉死映射**,并覆盖 HAL 启动失败 / 转换超时 / **失败拍陈旧帧** / 参数错等真机难复现分支 |
|
||||
| `adc_input/adc_input_test.c` | 测试单元本体(`#include` 生产源码 + HAL / TPAFE 可注入桩) |
|
||||
| `adc_input/stub/` | 桩头文件(`adc.h` / `tpafe5160.h`) |
|
||||
| `vol_lock/run_unit_test.py` | **卷锁单测**:离线编译真实 `App/fs/vol_lock.c`,33 断言点覆盖创建 / 获取 / 释放契约(**fail-open 不变量**:创建失败仍返回 1 且零阻塞)/ 幂等 / 超时透传 |
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
* TC-AI-019/01A 越界(ADC_SEM_COUNT)与负数语义 -> NULL
|
||||
* —— map_check(TC-AI-020)——
|
||||
* TC-AI-020 真实映射表自洽(反向路径结构上不可达,见文件末说明)
|
||||
* —— stm32_scan(TC-AI-021..03A)——
|
||||
* —— 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)
|
||||
@@ -37,6 +37,11 @@
|
||||
* 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(两来源分别)
|
||||
@@ -318,6 +323,40 @@ static void tc_scan_timeout(void) {
|
||||
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) {
|
||||
@@ -448,6 +487,8 @@ int main(void) {
|
||||
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");
|
||||
|
||||
Reference in New Issue
Block a user