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的配置如下:

  1. 配置4个通道
  2. 配置ADC2的参数,因为是每次请求转换4个通道,因此使用“DMA One Shot Mode”模式,此处的坑就是“Continuous Conversion Mode”必须使能,否则不能正常采集ADC数据
  3. 配置DMA为正常模式,注意不是循环模式
  4. 使能ADC中断
  5. 生成代码即可,实现HAL_ADC_ConvCpltCallback和HAL_ADC_ConvHalfCpltCallback函数,并在其中置转换完成的标志,这样,每次调用HAL_ADC_Start_DMA函数即可执行一次多通道的ADC转换,并等待转换完成标志。需要再次获取数据时,可再次调用HAL_ADC_Start_DMA函数并等待转换完成标志。
  6. 生成的主要代码如下
/* 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

物联沃分享整理
物联沃-IOTWORD物联网 » STM32使用DMA实现多通道ADC数据单次采集详解

发表回复