Files
STM32F4-Base/docs/问题记录/ADC_Trap_Records.md
T
wandering1 03721aafc4 docs: 新增 ADC 采样陷阱台账(TRAP-ADC-01/02)
把 2026-10-27 排查出的两个 ADC 采样陷阱沉淀成正式台账,按
docs/编号规范.md 的 TRAP-ADC-<nn> 编号,属主文档
docs/问题记录/ADC_Trap_Records.md。

TRAP-ADC-01:ADC 使能后闲置数秒才挂 DMA,导致 DMA 乒乓缓冲整体
+2 格相位旋转(槽 i 装的是 rank ((i+1) mod 9)+1)。全程无任何
编译或运行错误,只是数据整体错位两格,表象与「映射表写错」完全
一致,历史上据此误判过三轮。

条目重点记了那条判读陷阱:adcr[CH_i] == adcb[i] 成立不能证明通道
映射正确——它只证明拷贝循环是恒等映射,缓冲整体旋转时拷贝照样
恒等。判映射只能靠按通道号索引的独立真值(无 DMA 单通道读)。
附 9 行同开机对照证据表、5 种固件形态的 A/B 对照、修复方式、
回归判据与未证部分(微架构为何恰好丢 2 拍仍未证)。

TRAP-ADC-02:RTOS 运行期校准被 TIM3 TRGO 打断,adc1_calibrate()
返回 0xFFFF 超时哨兵。记了指纹判读要点(truth=65535 且 min 大于
max 是哨兵,不是满量程)与修法(校准期间屏蔽 EXTEN)。

关联登记三处:
- docs/README.md 7.3 问题记录表新增一行
- 设计文档 4.5 节加反向链接到 TRAP-ADC-01(代码 -> 设计 -> 台账)
- scripts/id_registry.json 的 TRAP 族 cursor_note 补 ADC 域 01~02

纯文档改动,不涉构建。
2026-10-27 20:55:04 +08:00

8.1 KiB
Raw Blame History

ADC 采样陷阱记录(TRAP-ADC 系列)

ADC1 采样链路(TIM3 1 kHz 节拍 → ADC1 九通道扫描 → DMA2_Stream4 乒乓搬运 → adc_input 语义映射)的实证陷阱:现象、判读陷阱、定案根因、修复与回归判据。动 ADC 采样、通道映射或 诊断代码之前先读本文。

编号口径:按 docs/编号规范.md 采用 TRAP-ADC-<nn>,本域自 01 起连续。编号不复用、不重排。 状态图例:✅ 已修复定案 · ⏳ 待修。 设计侧口径(采样节拍、寄存器配置、铁律)不在本文,见 docs/子系统设计/ADC采样节拍设计说明.md;本文只记「踩过的坑」与「怎么判断踩到了」。

索引

编号 标题 状态 一句话结论
TRAP-ADC-01 ADC 使能后闲置数秒才挂 DMA ✅ 乒乓缓冲整体 +2 格旋转;挂 DMA 那一瞬 ADON 必须为 0
TRAP-ADC-02 RTOS 运行期校准被 TIM3 TRGO 打断 ✅ adc1_calibrate() 返回 0xFFFF 超时哨兵,诊断读数全 65535

TRAP-ADC-01 · ADC 使能后闲置数秒才挂 DMA → 乒乓缓冲 +2 格相位旋转

  • 状态:✅ 已修复定案(2026-10-27,提交 1166ab1c)
  • 发现时间:2026-10-27(台架,同开机对照实验)

现象:把 AIN2(UB / PC4 / CH14)的端子接地,UB 却读出 784 码(浮空量级), 而本该浮空的 IB 读出 0。九路读数整体"错开两位":接哪一路,0 就出现在往前数两路 的位置上。

表象与"映射表写错"或"注入点标签不对"完全一致,历史上据此误判过三轮——两次接地实验 (认为 AIN3→IC=0、AIN10→UB=0)都被读成"映射错了两个槽位",实际夹子位置也在动 (U5.B7 与 U5.A8 在连接器上斜向相邻、间距不到 3 mm),两个因素叠加,谁也说不清。

判读陷阱(这条比根因更贵):

adcr[CH_i] == adcb[i] 成立不能证明通道映射正确。

它只证明 stm32_take() 的拷贝循环是恒等映射(s_stm32_raw[k_stm32_rank_ch[i]] = src[i])。 DMA 从序列中途开始搬运时,整个缓冲一起旋转,拷贝照样"恒等"——旋转后的缓冲被原样复制, 看起来完美自洽。我当时正是拿这个恒等性(11:18 那次 6/6 精确相等)宣布"DMA 从来没有错过", 结论完全错误。

判通道映射只有两条路:

  1. 按通道号索引的独立真值——关掉 DMA 与扫描,逐路软件触发、直接读 DR;
  2. 先按本节修法消除旋转,再做任何比对。

从 adc: 的语义标签反推永远不算证据:标签一错,整条推断链跟着跑偏。

根因(定案):触发条件是「ADC 被使能之后闲置了数秒才挂 DMA」。

MX_ADC1_Init() 在 main() 里就调用(使能 ADC),而挂 DMA 在 app_main_init() 步骤 4b, 两者之间隔着 GD5F 全片 BBT 扫描(数秒)。ADC 在这段时间里处于 ADON=1 的空转态; 等 DMA 挂上去时,搬运起点已经不在序列的 rank 1 上。

台架进一步收窄为:挂 DMA 的那一瞬间 CR2.ADON 必须为 0——不需要整个闲置期都为 0, 只要使能与搬运之间不留可观测间隔,旋转就不出现。

证据(同一次开机内对照,9/9 完美置换):先用无 DMA 单通道方式逐路取真值,再与 DMA 路径 的同一批输入按通道号对照:

槽(rank) 通道 DMA 值 实际装的是 真值 差
1 CH0 850 CH2 850 0
2 CH1 932 CH3 931 1
3 CH2 853 CH4 852 1
4 CH3 819 CH5 817 2
5 CH4 946 CH6 945 1
6 CH5 954 CH7 953 1
7 CH6 0 CH14 0 0
8 CH7 690 CH0 691 1
9 CH14 790 CH1 785 5

即槽 i 装的是 rank ((i+1) mod 9)+1,整个缓冲从 rank 3 开始搬,固定错开 2 格。 差值全在 5 码以内(两种读法的噪声),不是随机抖动。

A/B 对照(硬件完全不动,只换固件):

固件形态 开机次数 UB 读数 结论
干净固件 7/7 783~786 旋转存在
挂 DMA 前重初始化 ADC 5/5 0 旋转消失
同一次开机内重新挂 DMA 1/1 0 旋转消失
修复后(MX_ADC1_Init() 末尾关闭) 4/4 0 旋转消失
修复后(步骤 4b-pre 调 adc1_disable()) 3/3 0 旋转消失

⚠ 跨开机对照无效:输入浮空时读数会随环境漂移,7 次和 5 次看着都"稳定",但两组之间 不可比。必须同一次开机内、硬件不动,才能下结论。

修复:app_main_init() 步骤 4b-pre 调 adc1_disable()(实现见 Src/adc.c 的 USER CODE BEGIN 1),把 ADC 关掉;使能动作交给 HAL_ADC_Start_DMA() 内部完成——它自己 __HAL_ADC_ENABLE + 等 Tstab(约 20 µs),然后紧接着开 CR2.DMA 并启流。

⚠ 顺序不可调换:adc1_disable() 必须严格早于 adc_input_init()。若 ADC 在挂 DMA 时 仍处于使能态,旋转会重新出现。该顺序已写进 docs/子系统设计/ADC采样节拍设计说明.md 的设计铁律第 8 条,并由 CubeMX 存活审计项 adc1-calibration-impl(整段守护 USER CODE BEGIN 1)的基线漂移兜底。

回归判据:

  1. 把 AIN2(UB)端子接地后开机,adc: 行 UB 必须为 0(不是 700~800);
  2. 九路按 rank 序无错位——接地哪一路,0 就出现在该路上,不往前偏两格;
  3. 同开机对照(DMA 值 vs 无 DMA 单通道真值)逐路差值 ≤5 码。

未证部分:微架构层面为什么恰好丢 2 拍仍未证出(ADON=1 空转态下内部状态机如何 预推进两格)。工程结论已足够:使能与搬运不该有可观测间隔。不要为了"解释清楚"去猜, 更不要据此改动 ADC 配置。

教训:

  1. 恒等性 ≠ 正确性:拷贝循环恒等只能证明"没抄错",证明不了"源头没偏"。
  2. 同开机对照是唯一有效方法论:跨开机比浮空读数会把漂移当成差异。
  3. 判映射只认按通道号索引的真值,不认语义标签反推。
  4. "不报错的错"最贵:这一条全程没有任何编译/运行错误,只是数据整体错位两格。

TRAP-ADC-02 · RTOS 运行期校准被 TIM3 TRGO 打断 → 返回 0xFFFF

  • 状态:✅ 已定性(探针代码已删,本条作为以后再写校准/诊断代码的证词保留)
  • 发现时间:2026-10-27(TRAP-ADC-01 的同开机对照探针)

现象:诊断探针在 RTOS 上下文里逐路取真值,从第二路起每次都是 truth=65535 min=65535 max=0——看起来像"这一路输入是满量程",实际是校准失败。

根因:adc1_calibrate() 置 CR2.CAL 后轮询等它由硬件清零,超时上限 10 ms。但 TIM3 的 1 kHz 节拍(TRGO 触发 ADC1 常规转换)此时已经在跑,一次 TRGO 就启动常规转换、 打断 CAL 序列,CAL 位永远等不到清零 → 函数返回 0xFFFF 超时哨兵,调用方把它当成了 转换结果。固定发生在第二个被探通道,是因为第一路探完时 TIM3 还没启动。

修复(若再写探针):校准前后屏蔽外部触发—— CLEAR_BIT(ADC1->CR2, ADC_CR2_EXTEN) → 校准 → 恢复 EXTEN。

判读要点:truth 恰好等于 0xFFFF(65535)且 min=65535 / max=0(最小值大于最大值) 是超时哨兵的指纹,不是满量程读数。看到这个组合先怀疑校准失败,不要怀疑输入。

教训:任何在 RTOS 运行期调用的 ADC 校准,都必须先断开外部触发;校准是独占操作, 不能与常规采样节拍并发。


相关文档

  • 设计侧口径、寄存器配置、9 行证据表的原始记录与复现方法: docs/子系统设计/ADC采样节拍设计说明.md §4.5(设计铁律第 8 条)。
  • 通道映射与引脚对应:docs/硬件平台/ADC通道映射说明.md。
  • 前端拓扑(③ 段变比 = 1:1,非分压):docs/硬件平台/V0.0_网表详细分析报告.md §4.7。
  • 零点/线性/映射的现场验证步骤与判据:docs/测试说明/ADC前端注入验证流程.md。