STM32 HAL库:ADC+DMA应用(连续采样、触发采样)

一、为什么要用DMA

STM32的ADC是一个非常强大且灵活的外设,它不仅有着大量的通道,同时具备较好的精度。在笔者的实际测试中,在模拟参考电源较为稳定的情况下,使用STM32G4系列12位分辨率模式采样,数值波动范围可以小到正负3以内。
STM32系列的大多数产品ADC属于SAR型 (逐次逼近型),每次转换需要一定的时间,尤其是需要多个通道同事转换的场合,不同的程序设计方式对系统效率有着极大的影响。举例来说,假设每次adc采样需要1us,采样率8k,每次采样需要采4个通道,使用阻塞的方式进行AD转换,如果这4个通道共用一个ADC,那么AD转换将会占用系统约3%的资源,若这4个通道每个通道用一个ADC,那么4个通道的转换可以并行进行,系统资源占用降到了1%以下,如果4个通道采样使用DMA实现,系统资源占用可以忽略。随着通道数量的增加,DMA的优势会更加明显。

二、使用DMA的连续AD转换

以下以STM32G4 100脚单片机为例,对具体配置方法加以说明。
此处STM32CubeMX版本为5.2.0
首先使能需要采集的若干个通道,并将其配置为单端输入模式(除了单端输入模式外,G4系列芯片还支持差分输入)。
在这里插入图片描述
然后具体配置ADC参数如下图。
其中:
ADC模式->独立模式

分辨率->12位模式
数据对其方式->右对齐
扫描模式->使能
结束标志产生条件->整个序列转换完
连续转换模式->使能
不连续转换模式->关闭
DMA连续请求->使能

序列转换->开启
序列过采样->关闭
转换序列数目->7
外部触发源->软件触发

然后在rank中配置通道号,rank为每次转换的序号
注入模式关闭
看门狗不使能

在这里插入图片描述

然后对DMA进行配置,DMA请求模式设置为循环模式,DMA数据传递方向为外设到内存,外设地址不增加,内存地址自动增加,每次数据传递宽度为半字,对应uint16_t型
在这里插入图片描述
配置完成后生成代码,只需要对代码略作修改ADC即可开始工作。

  1. 定义长度和转换序列长度相等的数组用来储存数据,如
    adc_buff[7]
  2. 在adc初始化代码后添加dma转换开始代码
    HAL_ADC_Start_DMA(&hadc2,(uint32_t *)adc_buff,7);

最终adc配置代码如下:

static void MX_ADC2_Init(void)
{

  /* USER CODE BEGIN ADC2_Init 0 */

  /* USER CODE END ADC2_Init 0 */

  ADC_AnalogWDGConfTypeDef AnalogWDGConfig = {0};
  ADC_ChannelConfTypeDef sConfig = {0};

  /* USER CODE BEGIN ADC2_Init 1 */

  /* USER CODE END ADC2_Init 1 */
  /** Common config 
  */
  hadc2.Instance = ADC2;
  hadc2.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
  hadc2.Init.Resolution = ADC_RESOLUTION_12B;
  hadc2.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc2.Init.GainCompensation = 0;
  hadc2.Init.ScanConvMode = ADC_SCAN_ENABLE;
  hadc2.Init.EOCSelection = ADC_EOC_SEQ_CONV;
  hadc2.Init.LowPowerAutoWait = DISABLE;
  hadc2.Init.ContinuousConvMode = ENABLE;
  hadc2.Init.NbrOfConversion = 7;
  hadc2.Init.DiscontinuousConvMode = DISABLE;
  hadc2.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc2.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
  hadc2.Init.DMAContinuousRequests = ENABLE;
  hadc2.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  hadc2.Init.OversamplingMode = DISABLE;
  if (HAL_ADC_Init(&hadc2) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Analog WatchDog 1 
  */
  AnalogWDGConfig.Channel = ADC_CHANNEL_1;
  if (HAL_ADC_AnalogWDGConfig(&hadc2, &AnalogWDGConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel 
  */
  sConfig.Channel = ADC_CHANNEL_4;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_2CYCLES_5;
  sConfig.SingleDiff = ADC_SINGLE_ENDED;
  sConfig.OffsetNumber = ADC_OFFSET_NONE;
  sConfig.Offset = 0;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel 
  */
  sConfig.Channel = ADC_CHANNEL_12;
  sConfig.Rank = ADC_REGULAR_RANK_2;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel 
  */
  sConfig.Channel = ADC_CHANNEL_6;
  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();
  }
  /** Configure Regular Channel 
  */
  sConfig.Channel = ADC_CHANNEL_8;
  sConfig.Rank = ADC_REGULAR_RANK_5;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel 
  */
  sConfig.Channel = ADC_CHANNEL_9;
  sConfig.Rank = ADC_REGULAR_RANK_6;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel 
  */
  sConfig.Channel = ADC_CHANNEL_11;
  sConfig.Rank = ADC_REGULAR_RANK_7;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC2_Init 2 */
	HAL_ADC_Start_DMA(&hadc2,(uint32_t *)adc,7);
  /* USER CODE END ADC2_Init 2 */

}

三、使用定时器触发的AD转换

在大多数应用中,并不需要连续的对数据进行采样,更多的场合是在需要进行ADC采样时触发一次采样。接下来,对上述配置进行修改,实现通过定时器触发AD转换。

想要实现通过定时器触发AD转换,只需要在外部触发源里面挑选一个定时器通道,此处选择timer2 compare2,即定时器2的2通道,想要让触发信号生效,需要在定时器2的2通道产生一个上升沿,此处把定时器2的2通道配置为pwm模式以实现电平变化。
此时不在需要ADC进行转换,把连续转换模式设为关闭
在这里插入图片描述
tim2配置如下,使能通道2,并配置为pwm模式,根据想要的采样频率配置分频系数psc以及计数周期arr。
在这里插入图片描述
其他配置保持不变。

根据上述配置,cube生成的ADC初始化代码如下

static void MX_ADC2_Init(void)
{

  /* USER CODE BEGIN ADC2_Init 0 */

  /* USER CODE END ADC2_Init 0 */

  ADC_AnalogWDGConfTypeDef AnalogWDGConfig = {0};
  ADC_ChannelConfTypeDef sConfig = {0};

  /* USER CODE BEGIN ADC2_Init 1 */

  /* USER CODE END ADC2_Init 1 */
  /** Common config 
  */
  hadc2.Instance = ADC2;
  hadc2.Init.ClockPrescaler = ADC_CLOCK_ASYNC_DIV1;
  hadc2.Init.Resolution = ADC_RESOLUTION_12B;
  hadc2.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc2.Init.GainCompensation = 0;
  hadc2.Init.ScanConvMode = ADC_SCAN_ENABLE;
  hadc2.Init.EOCSelection = ADC_EOC_SEQ_CONV;
  hadc2.Init.LowPowerAutoWait = DISABLE;
  hadc2.Init.ContinuousConvMode = DISABLE;
  hadc2.Init.NbrOfConversion = 7;
  hadc2.Init.DiscontinuousConvMode = DISABLE;
  hadc2.Init.ExternalTrigConv = ADC_EXTERNALTRIG_T2_CC2;
  hadc2.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
  hadc2.Init.DMAContinuousRequests = ENABLE;
  hadc2.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  hadc2.Init.OversamplingMode = DISABLE;
  if (HAL_ADC_Init(&hadc2) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Analog WatchDog 1 
  */
  AnalogWDGConfig.Channel = ADC_CHANNEL_1;
  if (HAL_ADC_AnalogWDGConfig(&hadc2, &AnalogWDGConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel 
  */
  sConfig.Channel = ADC_CHANNEL_4;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_2CYCLES_5;
  sConfig.SingleDiff = ADC_SINGLE_ENDED;
  sConfig.OffsetNumber = ADC_OFFSET_NONE;
  sConfig.Offset = 0;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel 
  */
  sConfig.Channel = ADC_CHANNEL_12;
  sConfig.Rank = ADC_REGULAR_RANK_2;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel 
  */
  sConfig.Channel = ADC_CHANNEL_6;
  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();
  }
  /** Configure Regular Channel 
  */
  sConfig.Channel = ADC_CHANNEL_8;
  sConfig.Rank = ADC_REGULAR_RANK_5;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel 
  */
  sConfig.Channel = ADC_CHANNEL_9;
  sConfig.Rank = ADC_REGULAR_RANK_6;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel 
  */
  sConfig.Channel = ADC_CHANNEL_11;
  sConfig.Rank = ADC_REGULAR_RANK_7;
  if (HAL_ADC_ConfigChannel(&hadc2, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN ADC2_Init 2 */
	HAL_ADC_Start_DMA(&hadc2,(uint32_t *)adc,7);
  /* USER CODE END ADC2_Init 2 */

}

需要注意的是,cube生成的定时器初始化代码中不包含使能定时器以及使能pwm输出代码,需要添加使能配置。代码如下:

	HAL_TIM_Base_Start_IT(&htim2);
	HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_2);
  • 40
    点赞
  • 257
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论
以下是基于HAL库stm32f407定时器触发adc dma多通道采样代码: 首先,需要初始化定时器和ADC: ``` TIM_HandleTypeDef htim; ADC_HandleTypeDef hadc; void MX_TIM_Init(void) { TIM_MasterConfigTypeDef sMasterConfig = {0}; htim.Instance = TIMx; htim.Init.Prescaler = TIMx_PRESCALER; htim.Init.CounterMode = TIM_COUNTERMODE_UP; htim.Init.Period = TIMx_PERIOD; htim.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim.Init.RepetitionCounter = 0; HAL_TIM_Base_Init(&htim); sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(&htim, &sMasterConfig); } void MX_ADC_Init(void) { ADC_ChannelConfTypeDef sConfig = {0}; hadc.Instance = ADCx; hadc.Init.ClockPrescaler = ADC_CLOCKPRESCALER_PCLK_DIV2; hadc.Init.Resolution = ADC_RESOLUTION_12B; hadc.Init.ScanConvMode = ENABLE; hadc.Init.ContinuousConvMode = DISABLE; hadc.Init.DiscontinuousConvMode = DISABLE; hadc.Init.NbrOfDiscConversion = 0; hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING; hadc.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_Tx_TRGO; hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT; hadc.Init.NbrOfConversion = ADC_CHANNEL_NUM; hadc.Init.DMAContinuousRequests = ENABLE; hadc.Init.EOCSelection = ADC_EOC_SEQ_CONV; HAL_ADC_Init(&hadc); for (int i = 0; i < ADC_CHANNEL_NUM; ++i) { sConfig.Channel = ADC_CHANNEL_x[i]; sConfig.Rank = i + 1; sConfig.SamplingTime = ADC_SAMPLETIME_15CYCLES; HAL_ADC_ConfigChannel(&hadc, &sConfig); } } ``` 然后,需要初始化DMA和中断: ``` DMA_HandleTypeDef hdma_adc; void MX_DMA_Init(void) { hdma_adc.Instance = DMAx_STREAMx; hdma_adc.Init.Channel = DMA_CHANNELx; hdma_adc.Init.Direction = DMA_PERIPH_TO_MEMORY; hdma_adc.Init.PeriphInc = DMA_PINC_DISABLE; hdma_adc.Init.MemInc = DMA_MINC_ENABLE; hdma_adc.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; hdma_adc.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; hdma_adc.Init.Mode = DMA_CIRCULAR; hdma_adc.Init.Priority = DMA_PRIORITY_HIGH; hdma_adc.Init.FIFOMode = DMA_FIFOMODE_DISABLE; HAL_DMA_Init(&hdma_adc); __HAL_LINKDMA(&hadc, DMA_Handle, hdma_adc); } void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc) { if (hadc->Instance == ADCx) { HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); } } ``` 最后,在main函数中启动定时器和DMA: ``` int main(void) { HAL_Init(); MX_TIM_Init(); MX_ADC_Init(); MX_DMA_Init(); HAL_TIM_Base_Start(&htim); HAL_ADC_Start_DMA(&hadc, (uint32_t*)adc_buf, ADC_CHANNEL_NUM); while (1) { HAL_Delay(1000); } } ```
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深入浅出说电机

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值