fix(adc): 挂 DMA 前关闭 ADC,消除乒乓缓冲 +2 格相位旋转
问题:ADC1 在 MX_ADC1_Init() 里使能,而 DMA 在 app_main_init() 步骤 4b 才挂载,中间隔着 GD5F 全片 BBT 扫描(数秒)。台架同一次开机内用 无 DMA 单通道逐路取真值、与 DMA 路径对照,得 9/9 完美置换:乒乓缓冲 从 rank 3 开始搬,槽 i 装的是 rank ((i+1) mod 9)+1,整体错开 2 格。 表象是"接地 AIN2、UB 却读到别的通道的值",极易误判为映射表写错或 注入点标签不对。 修法:新增 adc1_disable()(Src/adc.c 的 USER CODE BEGIN 1),在 app_main_init() 步骤 4b-pre 调用,把 ADC 置为关闭态,使能动作交给 HAL_ADC_Start_DMA() 内部完成(它自己 __HAL_ADC_ENABLE + 等 Tstab 约 20 us,紧接着开 CR2.DMA 并启流),使能与搬运之间不留可观测间隔。 顺序不可调换:本调用必须严格早于 adc_input_init()。 台架验证:在 MX_ADC1_Init() 末尾关闭 4/4 次、在 4b-pre 调本 API 3/3 次,均 UB=0;同开机对照差值 <=4 码(修前 7/7 次 UB=783~786)。 文档:ADC采样节拍设计说明.md 新增 4.5 节(9 行证据表、判据陷阱、 根因与修法)与设计铁律第 8 条。 台账:Flash 261,764 -> 261,928 B(+164,新增函数),RAM 无变化; CubeMX 存活审计基线重建(adc1-calibration-impl / adc1-dma-link 两项漂移均属本次有意改动,锚点已逐条复核在位)。
This commit is contained in:
@@ -23,11 +23,11 @@
|
||||
"ioc-task-stacks": "58ee94dd2a093177",
|
||||
"adc1-hal-enabled": "1a8ed6cff34f7ba6",
|
||||
"adc1-calibration-call": "8d5f4b40fb218913",
|
||||
"adc1-calibration-impl": "d33bc4323e243248",
|
||||
"adc1-calibration-impl": "25723dd0449ac701",
|
||||
"adc-tick-tim3-branch": "39c3548c37f6c4ae",
|
||||
"ioc-adc-trig-t3trgo": "a9939de260ee46f8",
|
||||
"ioc-adc-dma-continuous": "189126670d85d80a",
|
||||
"adc1-dma-link": "397fce92dca3b728",
|
||||
"adc1-dma-link": "8a57c70611db1a02",
|
||||
"adc1-dma-irq-priority": "beff08e91333229b"
|
||||
},
|
||||
"_note": "CubeMX 存活审计基线;改动用 --write 重建"
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "stm32f4xx_it.h" /* fault_handler_enable() */
|
||||
#include "usart.h" /* huart5 */
|
||||
#include "tim.h" /* htim3:1 kHz 采样节拍(TIM3 更新中断触发 CONVST + TRGO 触发 ADC1) */
|
||||
#include "adc.h" /* adc1_disable():挂 DMA 前把 ADC 关掉,让 Start_DMA 自己使能 */
|
||||
#include "rs485.h"
|
||||
#include "sd2506.h"
|
||||
#include "boot_stats.h" /* 开机账本:上电次数(RTC 用户 SRAM)+ 本次复位原因快照(道G task-12) */
|
||||
@@ -220,6 +221,24 @@ int app_main_init(void) {
|
||||
syslog_post(SYSLOG_SELFTEST, "ADC fault");
|
||||
}
|
||||
|
||||
/* 4b-pre) 挂 DMA 之前**先把 ADC 关掉**,让使能动作由 HAL_ADC_Start_DMA 内部完成
|
||||
* (它自己 __HAL_ADC_ENABLE + 等 Tstab ~20 µs,然后紧接着开 CR2.DMA 并启流)。
|
||||
*
|
||||
* 为什么必须这样做:MX_ADC1_Init() 在 main() 里就调用了,而挂 DMA 在这里,
|
||||
* 中间隔着 GD5F 全片 BBT 扫描(数秒)。台架实测(同一次开机内用无 DMA 单通道
|
||||
* 探针取真值对照,9/9 完美置换):**ADC 使能后闲置数秒才挂 DMA,DMA 乒乓缓冲
|
||||
* 会整体 +2 格旋转** —— 槽 i 装的是 rank ((i+1) mod 9)+1,9 路通道按 rank 序
|
||||
* 整体错开 2 位。表象是"接地 AIN2、UB 却读到别的通道的值",极易被误判成
|
||||
* 映射表写错或夹子位置不对。
|
||||
* 把使能与挂 DMA 压到同一时刻后旋转消失(台架 4/4 次,同开机对照差值 ≤4 码)。
|
||||
* 证据与复现方法见 docs/子系统设计/ADC采样节拍设计说明.md §4.5。
|
||||
*
|
||||
* ⚠ 顺序不可调换:本调用必须严格早于下面的 adc_input_init()。
|
||||
* 若 ADC 在本步之后仍处于使能态,旋转会重新出现。 */
|
||||
if (adc1_disable() != 0) {
|
||||
DBG_ERROR("ADC1 disable before DMA arm: TIMEOUT");
|
||||
}
|
||||
|
||||
/* 4b) ADC 输入层(L1)初始化:清原始码值缓冲与统计 + **语义映射表自洽性自检**
|
||||
* (映射指向了不会被扫描的通道 → 返回 -1,避免"读到恒为陈旧值"的静默错)
|
||||
* + 挂载一次 STM32 ADC1 的 DMA(失败返回 -2)。
|
||||
|
||||
@@ -46,6 +46,12 @@ void MX_ADC1_Init(void);
|
||||
* 实现见 Src/adc.c 的 USER CODE BEGIN 1)。返回 0 = 完成,-1 = 等待超时 */
|
||||
int adc1_calibrate(void);
|
||||
|
||||
/* ADC1 关闭(清 ADON 并等其生效)。实现见 Src/adc.c 的 USER CODE BEGIN 1。
|
||||
* **必须在挂 DMA 之前调用**:把"使能 ADC"推迟到 HAL_ADC_Start_DMA 内部完成,
|
||||
* 使能与搬运之间不留可观测间隔。返回 0 = 已关闭,-1 = 等待超时。
|
||||
* 动机与台架证据见 docs/子系统设计/ADC采样节拍设计说明.md §4.5(DMA 缓冲 +2 格旋转)。 */
|
||||
int adc1_disable(void);
|
||||
|
||||
/* USER CODE END Prototypes */
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -261,6 +261,8 @@ void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
|
||||
#define ADC1_CAL_TIMEOUT_MS (10U)
|
||||
/* ADON 0→1 后的稳定等待(µs),同 HAL 的 ADC_STAB_DELAY_US 口径 */
|
||||
#define ADC1_STAB_DELAY_US (20U)
|
||||
/* 关闭等待上限(ms)。ADON 由硬件清零,实测在数十 ns 内完成;10 ms 是十万倍余量 */
|
||||
#define ADC1_OFF_TIMEOUT_MS (10U)
|
||||
|
||||
/*
|
||||
* 函数功能:ADC1 上电自校准(等价于 HAL_ADCEx_Calibration_Start,本仓库 HAL 无此函数)
|
||||
@@ -311,5 +313,38 @@ int adc1_calibrate(void)
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:关闭 ADC1(清 CR2.ADON 并等硬件清零生效)
|
||||
* 入口参数:无
|
||||
* 返 回 值:0 - 已关闭;-1 - 等待超时(ADON 未清零)
|
||||
* 限定条件:任何上下文均可(只碰 ADC1 寄存器与 HAL_GetTick);须在挂 DMA 之前调用
|
||||
* 函数说明:**本函数的存在只为把"使能 ADC"推迟到挂 DMA 的同一刻。**
|
||||
* MX_ADC1_Init() 在 main() 里调用,而挂 DMA 在 app_main_init 步骤 4b,
|
||||
* 中间隔着 GD5F 全片 BBT 扫描(数秒)。台架实测(同一次开机内用无 DMA
|
||||
* 单通道探针取真值对照,9/9 完美置换):ADC 使能后闲置数秒才挂 DMA,
|
||||
* DMA 乒乓缓冲会整体 +2 格旋转 —— 槽 i 装的是 rank ((i+1) mod 9)+1,
|
||||
* 即 9 路通道按 rank 序整体错开 2 位,表现为"接地 AIN2、UB 却读到别的
|
||||
* 通道的值"这种看似映射表写错的现象。
|
||||
* 调用本函数后 ADC 处于关闭态,HAL_ADC_Start_DMA 内部会自己
|
||||
* __HAL_ADC_ENABLE + 等 Tstab(~20 µs),然后紧接着开 CR2.DMA 并启流,
|
||||
* 使能与搬运之间不再有可观测的间隔 → 旋转消失(台架 4/4 次,差值 ≤4 码)。
|
||||
* 证据、复现方法与机制分析见 docs/子系统设计/ADC采样节拍设计说明.md §4.5。
|
||||
*/
|
||||
int adc1_disable(void)
|
||||
{
|
||||
uint32_t tickstart;
|
||||
|
||||
CLEAR_BIT(ADC1->CR2, ADC_CR2_ADON);
|
||||
tickstart = HAL_GetTick();
|
||||
while ((ADC1->CR2 & ADC_CR2_ADON) != 0U)
|
||||
{
|
||||
if ((HAL_GetTick() - tickstart) > ADC1_OFF_TIMEOUT_MS)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
/* USER CODE END 1 */
|
||||
|
||||
|
||||
@@ -47,10 +47,10 @@ dhara 失败类 +84;RO 另 +208,全是该失败类名表)→ `ER_IROM1`
|
||||
<!-- MEM_SNAPSHOT:BEGIN generated by scripts/check_mem_numbers.py -->
|
||||
| 区域 | 用量 | 容量 | 占比 |
|
||||
|---|---|---|---|
|
||||
| ER_IROM1 | 261,764 B (255.63 KB) | 1,048,576 B (1024 KB) | 25.0% |
|
||||
| ER_IROM1 | 261,928 B (255.79 KB) | 1,048,576 B (1024 KB) | 25.0% |
|
||||
| RW_IRAM1 | 81,072 B (79.17 KB) | 131,072 B (128 KB) | 61.9% |
|
||||
| RW_CCM | 57,376 B (56.03 KB) | 65,280 B (64 KB) | 87.9% |
|
||||
| **Flash (ROM 合计)** | **263,336 B (257.2 KB)** | 1,048,576 B (1024 KB) | **25.1%** |
|
||||
| **Flash (ROM 合计)** | **263,500 B (257.3 KB)** | 1,048,576 B (1024 KB) | **25.1%** |
|
||||
<!-- MEM_SNAPSHOT:END -->
|
||||
|
||||
各区的容量与常量来源:
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<!-- MEM_BUDGET:BEGIN generated by scripts/ram_baseline.py -->
|
||||
| 区域 | 用量 | 容量 | 占比 | 门禁(≤92%) |
|
||||
|---|---|---|---|---|
|
||||
| ER_IROM1 (Flash) | 261,764 B | 1,048,576 B | 25.0% | 参考 |
|
||||
| ER_IROM1 (Flash) | 261,928 B | 1,048,576 B | 25.0% | 参考 |
|
||||
| RW_CCM | 57,376 B | 65,280 B | 87.9% | PASS |
|
||||
| UNINIT_CCM | 48 B | 256 B | 18.8% | 参考 |
|
||||
| RW_IRAM1 | 81,072 B | 131,072 B | 61.9% | PASS |
|
||||
|
||||
@@ -200,6 +200,50 @@ const uint16_t *src = &s_stm32_dma[half * ADC_INPUT_STM32_CH_NUM];
|
||||
- 取帧状态码:`ADC_INPUT_RC_OK` = 0 / `ADC_INPUT_RC_START` = -1 挂载失败 /
|
||||
`ADC_INPUT_RC_STALL` = -2 本拍无新帧 / `ADC_INPUT_RC_NOT_RUN` = -3 尚未取过帧。
|
||||
|
||||
### 4.5 使能时机与 DMA 缓冲相位(+2 格旋转,2026-10-27 实测)
|
||||
|
||||
**这条是本节最反直觉的一条:DMA 缓冲会整体错开 2 格,而寄存器配置全对、拷贝循环也全对。**
|
||||
|
||||
**现象。** 台架把某个端子接地,读数却出现在**另一个通道**上——例如接地 `AIN2`(`UB` / PC4 / CH14),
|
||||
`UB` 读出 784 码(浮空值),而 `IB` 读出 0。表象与"映射表写错"完全一致,但
|
||||
`SQR1/SQR2/SQR3` 实测为 CH0..CH7 + CH14、`L=8`,`k_stm32_rank_ch[]` 与拷贝循环也逐条核过,全对。
|
||||
|
||||
**证据。** 同一次开机内,先用无 DMA 单通道方式(逐路关扫描、软件触发、直接读 `DR`)取真值,
|
||||
再与 DMA 路径的同一批输入逐路对照,得到 9/9 完美置换:
|
||||
|
||||
| 槽(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 码以内(两种读法的噪声),不是随机抖动。
|
||||
|
||||
**判据上的陷阱。** `s_stm32_raw[k_stm32_rank_ch[i]] = src[i]` 的恒等性**不能**用来证明映射正确:
|
||||
DMA 从序列中途开始搬时,缓冲整体旋转,拷贝照样是恒等的。判映射只能靠
|
||||
**按通道号索引的独立真值**(无 DMA 单通道读),或按 §4.5 的修法消除旋转后再比对。
|
||||
|
||||
**根因。** 触发条件是「ADC 被使能之后**闲置了数秒**才挂 DMA」。本工程 `MX_ADC1_Init()` 在 `main()`
|
||||
里调用,而挂 DMA 在 `app_main_init()` 步骤 4b,中间隔着 GD5F 全片 BBT 扫描。台架对照:
|
||||
"使能后闲置数秒才挂" → 7/7 次都是 +2 旋转;"使能与挂 DMA 在同一时刻" → 6/6 次旋转为 0
|
||||
(探针固件 5/5 + 同一次开机内重新挂 DMA 1/1)。
|
||||
**微架构层面的确切原因(为何恰好丢 2 拍)尚未证出**,但工程结论是确定的:使能与搬运不该有可观测间隔。
|
||||
|
||||
**修法。** `app_main_init()` 步骤 4b-pre 调 `adc1_disable()`(实现见 `Src/adc.c` 的 `USER CODE` 区)
|
||||
先把 ADC 关掉,使能动作交给 `HAL_ADC_Start_DMA()` 内部完成——它自己 `__HAL_ADC_ENABLE` +
|
||||
等 Tstab(约 20 µs),然后紧接着开 `CR2.DMA` 并启流。台架验证:在 `MX_ADC1_Init()` 末尾直接关闭
|
||||
4/4 次、在步骤 4b-pre 调本 API 3/3 次,均 `UB=0`,同开机对照差值 ≤4 码。
|
||||
⚠ **顺序不可调换**:`adc1_disable()` 必须严格早于 `adc_input_init()`;若 ADC 在挂 DMA 时仍处于使能态,
|
||||
旋转会重新出现。改动被删时会由审计项 `adc1-calibration-impl` 的基线漂移拦下
|
||||
(它整段守护 `Src/adc.c` 的 `USER CODE BEGIN 1`)。
|
||||
|
||||
---
|
||||
## 5. TPAFE 侧:CONVST 脉冲与 BUSY 读帧
|
||||
|
||||
@@ -342,3 +386,6 @@ t = 1000 µs 下一个更新事件
|
||||
**违反时不报任何错**,只在数据上出事(静默丢通道、搬不回来、优先级被重置为 0)。
|
||||
6. **节拍源停摆必须导致复位**(§6.3):不得给等待加超时来"绕过"看门狗。
|
||||
7. **动栈或加深调用链前后各跑一次 `scripts/stack_report.py`**,新增缓冲前跑 `scripts/ram_report.py`(§9)。
|
||||
8. **挂 DMA 之前必须让 ADC 处于关闭态**(§4.5):`app_main_init()` 步骤 4b-pre 的 `adc1_disable()`
|
||||
不可删除、不可后移。违反时**不报任何错**,只是 9 路通道按 rank 序整体错开 2 格——
|
||||
表象与"映射表写错"或"注入点标签不对"完全一样,历史上曾据此误判过三轮。
|
||||
|
||||
Reference in New Issue
Block a user