STM32使用DMA实现多通道ADC数据单次采集详解
网上有很多使用DMA实现多通道ADC循环采集的教程,但没有使用DMA实现单次触发采集多通道的实现描述,踩过的坑记录一下(以ADC2为例)。
除了使用单次DMA实现多通道数据采集的方式,还可使用“Discontinuous Conversion Mode”的方式用轮询的方式实现,代码如下所示,每次调用HAL_ADC_Start会执行一个通道数据转换,调用HAL_ADC_GetValue获取此通道数据,再调用HAL_ADC_Start启动下一个通道的转换,直到所有通道转换完毕,再次调用HAL_ADC_Start时开启新一轮的转换。
void ADC_Get_Value(uint32_t adc_value[])
{
int i = 0;
for(i = 0;i < NUM_ADC_CHANNEL;i++)
{
HAL_ADC_Start(&hadc1);//开启ADC1
HAL_ADC_PollForConversion(&hadc1,10);//等待转换完成
adc_value[i] = HAL_ADC_GetValue(&hadc1);//获取ADC的值
}
return;
}
stm32CubeMx的配置如下:
- 配置4个通道
- 配置ADC2的参数,因为是每次请求转换4个通道,因此使用“DMA One Shot Mode”模式,此处的坑就是“Continuous Conversion Mode”必须使能,否则不能正常采集ADC数据
- 配置DMA为正常模式,注意不是循环模式
- 使能ADC中断
- 生成代码即可,实现HAL_ADC_ConvCpltCallback和HAL_ADC_ConvHalfCpltCallback函数,并在其中置转换完成的标志,这样,每次调用HAL_ADC_Start_DMA函数即可执行一次多通道的ADC转换,并等待转换完成标志。需要再次获取数据时,可再次调用HAL_ADC_Start_DMA函数并等待转换完成标志。
- 生成的主要代码如下
/* Definition of ADC2 conversions data table size */
#define ADC2_CONVERTED_DATA_BUFFER_SIZE ((uint32_t) 32)
ALIGN_32BYTES(__IO uint16_t adc2ConvertedData[ADC2_CONVERTED_DATA_BUFFER_SIZE]);
__IO uint8_t adc2_conversion_completed = 0;
ADC_HandleTypeDef hadc2;
DMA_HandleTypeDef hdma_adc2;
void MX_ADC2_Init(void)
{
/* USER CODE BEGIN ADC2_Init 0 */
/* USER CODE END ADC2_Init 0 */
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC2_Init 1 */
hadc2.Instance = ADC2;
if (HAL_ADC_DeInit(&hadc2) != HAL_OK)
{
/* ADC de-initialization Error */
Error_Handler();
}
/* USER CODE END ADC2_Init 1 */
/** Common config
*/
hadc2.Instance = ADC2;
hadc2.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc2.Init.Resolution = ADC_RESOLUTION_10B;
hadc2.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc2.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc2.Init.LowPowerAutoWait = DISABLE;
hadc2.Init.ContinuousConvMode = ENABLE;
hadc2.Init.NbrOfConversion = 4;
hadc2.Init.DiscontinuousConvMode = DISABLE;
hadc2.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc2.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc2.Init.ConversionDataManagement = ADC_CONVERSIONDATA_DMA_ONESHOT;
hadc2.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc2.Init.LeftBitShift = ADC_LEFTBITSHIFT_NONE;
hadc2.Init.OversamplingMode = DISABLE;
if (HAL_ADC_Init(&hadc2) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_11;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
sConfig.OffsetSignedSaturation = DISABLE;
if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_14;
sConfig.Rank = ADC_REGULAR_RANK_2;
if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_19;
sConfig.Rank = ADC_REGULAR_RANK_3;
if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_7;
sConfig.Rank = ADC_REGULAR_RANK_4;
if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC2_Init 2 */
/* 校准ADC,calibration offset */
if (HAL_ADCEx_Calibration_Start(&hadc2, ADC_CALIB_OFFSET, ADC_SINGLE_ENDED) != HAL_OK)
{
Error_Handler();
}
/* USER CODE END ADC2_Init 2 */
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(adcHandle->Instance==ADC2)
{
/* USER CODE BEGIN ADC2_MspInit 0 */
/* USER CODE END ADC2_MspInit 0 */
/* ADC2 clock enable */
__HAL_RCC_ADC12_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**ADC2 GPIO Configuration
PC1 ------> ADC2_INP11
PA2 ------> ADC2_INP14
PA5 ------> ADC2_INP19
PA7 ------> ADC2_INP7
*/
GPIO_InitStruct.Pin = ADC2_FX_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(ADC2_FX_GPIO_Port, &GPIO_InitStruct);
GPIO_InitStruct.Pin = ADC2_FB_Pin|ADC2_VBUS_Pin|ADC2_TYPEC_VBUS_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* ADC2 DMA Init */
/* ADC2 Init */
hdma_adc2.Instance = DMA1_Stream0;
hdma_adc2.Init.Request = DMA_REQUEST_ADC2;
hdma_adc2.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc2.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc2.Init.MemInc = DMA_MINC_ENABLE;
hdma_adc2.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_adc2.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_adc2.Init.Mode = DMA_NORMAL;
hdma_adc2.Init.Priority = DMA_PRIORITY_MEDIUM;
hdma_adc2.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_adc2) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc2);
/* ADC2 interrupt Init */
HAL_NVIC_SetPriority(ADC_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(ADC_IRQn);
/* USER CODE BEGIN ADC2_MspInit 1 */
/* USER CODE END ADC2_MspInit 1 */
}
}
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
/* Invalidate Data Cache to get the updated content of the SRAM on the second half of the ADC converted data buffer: 32 bytes */
if(hadc->Instance == ADC2)
{
SCB_InvalidateDCache_by_Addr((uint32_t *) &adc2ConvertedData[ADC2_CONVERTED_DATA_BUFFER_SIZE/2], ADC2_CONVERTED_DATA_BUFFER_SIZE);
// 转换完成标志置位
adc2_conversion_completed = 1;
}
}
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef* hadc)
{
if(hadc->Instance == ADC2)
{
SCB_InvalidateDCache_by_Addr((uint32_t *) &adc2ConvertedData[0], ADC2_CONVERTED_DATA_BUFFER_SIZE);
}
}
uint8_t get_adc2Data(int argc, char *argv[])
{
uint8_t i = 0;
uint8_t adc2_DMANumber = sizeof(adc2ConvertedData) / sizeof(adc2ConvertedData[0]);
HAL_StatusTypeDef tmp_hal_status = HAL_ERROR;
tmp_hal_status = HAL_ADC_Start_DMA(&hadc2, (uint32_t*)adc2ConvertedData, adc2_DMANumber);
if(tmp_hal_status != HAL_OK)
{
printf("Err(%d): get_adcData start\r\n", tmp_hal_status);
return 1;
}
while(adc2_conversion_completed == 0)
{
}
adc2_conversion_completed = 0;
// print ADC data
for (i = 0; i < adc2_DMANumber; i++)
{
printf("adc2Data[%d]: %d\r\n", i + 1, adc2ConvertedData[i]);
}
return 0;
}
这样,每调用一次get_adc2Data函数,都会执行单次的4通道的adc转换。注意,因为我用的是stm32h7,需要在转换完成和半完成的回调中将保存adc转换数据的数组所占用的地址设置为no cache
作者:taoking123