Files
STM32F4-Base/docs/问题记录/ADC_Trap_Records.md
T
wandering1 20617a33f7 docs(编号规范): 编号一律停发——§4.1 五族也不再发新号,只读存量
用户裁定(2026-10-29):「啥号都不发」= 任何族都不再新增编号,
含 §4.1 那五个原本"有脚本按字面量匹配"的存量族;既有号只读引用,
族里新增内容一律写语义行名。

规范本体:
- §1 由「先问三句」改成「默认答案:不发号」,三问表换成场景对照表;
- §4.1 由「允许用号的族」改成「存量族」,第四列改「既有号被谁读 /
  新写怎么办」,逐行写明新写怎么做(TRAP 写语义行名、FR 用 §5 行名、
  CODE-RULE / MENU-STATE 先问用户、P-n 只读存量页号);
- 末框四条对照 + 唯一例外入口(确需机器按字面量匹配 → 先问用户);
- 新增族 = 一律不许;批准后顺序:本文 §4.1 → 属主文档 → 才动 registry。

跨文件同步(12 份):门禁说明 id-check-new / id-check / host-unit-tests
(把"校验 5/3 拦新号"这类现状叙述历史化)、两份 TRAP 记录的编号口径、
开机账本用例号说明、AGENTS 两处、id-family 技能与其 steps-alpha 模板、
归档 README 与两份归档脚本 docstring。

AGENTS 主题表那行原写「允许用号的 7 个族」,与规范的 5 行本就对不上,
旧计数一并消掉。

未做:pre-commit 的 default_stages 一行——用户对该项未置可否。

验证:py_compile 与 check_ids_new.py --self-test 均 rc=0;逐篇
check_doc_links.py --sections-strict rc=0(编号规范 70 处引用 /
68 可判定 / 无法判定 0);13 份改动文件表列数与字节体检相对基线零新增。
2026-10-29 21:31:42 +08:00

8.3 KiB
Raw Blame History

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

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

编号口径:本域既有条目按 TRAP-ADC-<nn> 自 01 起连续编号,只读引用;2026-10-29 起不再新增 TRAP 号(docs/编号规范.md §4.1 一律停发),新踩坑在本文写语义行名。编号不复用、不重排。 状态图例:✅ 已修复定案 · ⏳ 待修。 设计侧口径(采样节拍、寄存器配置、铁律)不在本文,见 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。