Files
STM32F4-Base/Src/adc.c
T
wandering1 1166ab1c2e 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
两项漂移均属本次有意改动,锚点已逐条复核在位)。
2026-10-27 20:42:06 +08:00

351 lines
12 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file adc.c
* @brief This file provides code for the configuration
* of the ADC instances.
******************************************************************************
* @attention
*
* Copyright (c) 2026 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "adc.h"
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
/* ADC1 init function */
void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = ENABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
hadc1.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T3_TRGO;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 9;
hadc1.Init.DMAContinuousRequests = ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_144CYCLES;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_1;
sConfig.Rank = 2;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_2;
sConfig.Rank = 3;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_3;
sConfig.Rank = 4;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_4;
sConfig.Rank = 5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_5;
sConfig.Rank = 6;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_6;
sConfig.Rank = 7;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_7;
sConfig.Rank = 8;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure for the selected ADC regular channel its corresponding rank in the sequencer and its sample time.
*/
sConfig.Channel = ADC_CHANNEL_14;
sConfig.Rank = 9;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* STM32F4 ADC 上电**自校准**:RM0090 §13.3.6 / AN4976 要求的必需步骤,CubeMX 不生成,
* 且本仓库的 HAL 是裁剪子集、**没有** HAL_ADCEx_Calibration_Start()(已核实
* stm32f4xx_hal_adc_ex.c/.h 均无该函数)→ 用本文件 USER CODE BEGIN 1 里的等价实现。
* 必须在首次转换之前完成,否则 ADC 存在失调误差(未校准的偏移可达数十 LSB)。
* 放 USER CODE 区 = L2,重生成不丢;由 cubemx-survival 审计项 adc1-calibration 兜底。 */
if (adc1_calibrate() != 0)
{
Error_Handler();
}
/* USER CODE END ADC1_Init 2 */
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(adcHandle->Instance==ADC1)
{
/* USER CODE BEGIN ADC1_MspInit 0 */
/* USER CODE END ADC1_MspInit 0 */
/* ADC1 clock enable */
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
/**ADC1 GPIO Configuration
PA0-WKUP ------> ADC1_IN0
PA1 ------> ADC1_IN1
PA2 ------> ADC1_IN2
PA3 ------> ADC1_IN3
PA4 ------> ADC1_IN4
PA5 ------> ADC1_IN5
PA6 ------> ADC1_IN6
PA7 ------> ADC1_IN7
PC4 ------> ADC1_IN14
*/
GPIO_InitStruct.Pin = ADC9_RES_Pin|ADC10_IC_Pin|ADC11_IN_Pin|ADC12_UC_Pin
|ADC13_RES_Pin|ADC14_TEMP_Pin|ADC15_IB_Pin|ADC16_RES_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ADC17_UB_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(ADC17_UB_GPIO_Port, &GPIO_InitStruct);
/* ADC1 DMA Init */
/* ADC1 Init */
hdma_adc1.Instance = DMA2_Stream4;
hdma_adc1.Init.Channel = DMA_CHANNEL_0;
hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_adc1.Init.Mode = DMA_CIRCULAR;
hdma_adc1.Init.Priority = DMA_PRIORITY_LOW;
hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc1);
/* USER CODE BEGIN ADC1_MspInit 1 */
/* USER CODE END ADC1_MspInit 1 */
}
}
void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
{
if(adcHandle->Instance==ADC1)
{
/* USER CODE BEGIN ADC1_MspDeInit 0 */
/* USER CODE END ADC1_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_ADC1_CLK_DISABLE();
/**ADC1 GPIO Configuration
PA0-WKUP ------> ADC1_IN0
PA1 ------> ADC1_IN1
PA2 ------> ADC1_IN2
PA3 ------> ADC1_IN3
PA4 ------> ADC1_IN4
PA5 ------> ADC1_IN5
PA6 ------> ADC1_IN6
PA7 ------> ADC1_IN7
PC4 ------> ADC1_IN14
*/
HAL_GPIO_DeInit(GPIOA, ADC9_RES_Pin|ADC10_IC_Pin|ADC11_IN_Pin|ADC12_UC_Pin
|ADC13_RES_Pin|ADC14_TEMP_Pin|ADC15_IB_Pin|ADC16_RES_Pin);
HAL_GPIO_DeInit(ADC17_UB_GPIO_Port, ADC17_UB_Pin);
/* ADC1 DMA DeInit */
HAL_DMA_DeInit(adcHandle->DMA_Handle);
/* USER CODE BEGIN ADC1_MspDeInit 1 */
/* USER CODE END ADC1_MspDeInit 1 */
}
}
/* USER CODE BEGIN 1 */
/* CR2.CAL(bit 3)。⚠ 本仓库的 CMSIS 设备头 stm32f407xx.h 是裁剪版,**未定义**
* ADC_CR2_CAL(已核实:CR2 只有 ADON/CONT/DMA/DDS/EOCS/ALIGN/JEXTSEL/JEXTEN/
* JSWSTART/EXTSEL/EXTEN/SWSTART,没有 CAL)
* → 按 RM0090 在此补齐,命名避开 CMSIS 前缀以免将来重定义冲突。 */
#define ADC1_CAL_BIT (0x1UL << 3U)
/* 断电/校准等待上限(ms)。CAL 实测在数十 µs 内清零,10 ms 是 100 倍以上余量 */
#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 无此函数)
* 入口参数:无(操作 ADC1 寄存器)
* 返 回 值:0 - 校准完成;-1 - 等待超时(ADON 未清零 / CAL 未清零)
* 限定条件:MX_ADC1_Init() 之后、**首次转换之前**;内核启动前可调用
* (只用 HAL_GetTick 与寄存器,不触碰任何 CMSIS/FreeRTOS 对象)
* 函数说明:RM0090 §13.3.6 时序——① ADC 必须处于关闭态(清 ADON 并等其生效);
* ② 置 ADON 上电,等 Tstab;③ 置 CAL 启动自校准;④ 完成后**硬件自动**清 CAL,
* 等其清零即完成。校准值存在 ADC 内部偏置寄存器,此后所有转换都带上校正。
* 缺这一步的后果不是"读不到"而是"读得有偏"——正是 §7.2 那类
* adc1_err/异常值的候选根因之一,见 docs/硬件平台/ADC通道映射说明.md。
*/
int adc1_calibrate(void)
{
uint32_t tickstart;
/* 1) 前置条件:ADC 必须关闭 */
CLEAR_BIT(ADC1->CR2, ADC_CR2_ADON);
tickstart = HAL_GetTick();
while ((ADC1->CR2 & ADC_CR2_ADON) != 0U)
{
if ((HAL_GetTick() - tickstart) > ADC1_CAL_TIMEOUT_MS)
{
return -1;
}
}
/* 2) 上电(ADON 0→1),等待稳定后再启动校准 */
SET_BIT(ADC1->CR2, ADC_CR2_ADON);
{
__IO uint32_t wait_loop_index = (ADC1_STAB_DELAY_US * (SystemCoreClock / 1000000U));
while (wait_loop_index != 0U)
{
wait_loop_index--;
}
}
/* 3) 启动自校准,等硬件把 CAL 清零 */
SET_BIT(ADC1->CR2, ADC1_CAL_BIT);
tickstart = HAL_GetTick();
while ((ADC1->CR2 & ADC1_CAL_BIT) != 0U)
{
if ((HAL_GetTick() - tickstart) > ADC1_CAL_TIMEOUT_MS)
{
return -1;
}
}
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 */