【嵌入式】STM32F4的ADC采样——多通道、DMA、定时器触发

文章目录

      • 一、硬件原理简介
        • 1.1 ADC
        • 1.2 定时器
        • 1.3 DMA
      • 二、软件配置
        • 2.1 ADC配置
        • 2.2 TIM配置
        • 2.3 DMA配置
      • 最后.上代码

项目中需要对三个通道的电压进行一定频率的AD采样,由于采样过程贯穿整个任务,为了使采样过程尽可能不占用CPU资源,采用定时器触发的多通道ADC扫描采样,且采样数据由DMA传到RAM中的缓存。
这样做有以下几个好处:1、由定时器触发ADC采样,这样采样的频率可控,且定时器触发不会占用任何CPU资源;2、DMA进一步降低了任务对CPU的占有率。

一、硬件原理简介

1.1 ADC

ADC的规则通道扫描采样不再赘述,配置好规则通道后,可以采用软件触发的方式开启AD转换,也可通过外部触发,如下图所示。可以通过定时器以及外部中断方式触发。【嵌入式】STM32F4的ADC采样——多通道、DMA、定时器触发_第1张图片
使用定时器触发时,最好实现的为TIM3_TRGO事件,这个事件将在下面介绍。也就是说当ADC转换配置为不连续模式时,每发生一次TIM3_TRGO事件,就会触发ADC进行一次规则通道的转换。

1.2 定时器

前面提到TIM3_TRGO事件,那什么是TIM3_TRGO呢。看下图,可以把它理解为一个定时器内部输出的信号,当满足一定条件时他就输出一个信号到其他外设,从而触发其他外设的某些操作。运用在ADC中即是触发ADC的一次规则通道转换。【嵌入式】STM32F4的ADC采样——多通道、DMA、定时器触发_第2张图片

1.3 DMA

其实这个没什么好说的,就是配置好,根据你所用的ADC来选择DMA设备,且配置相应的数据流和通道就好了。具体如何选择数据流和通道,看下图。
【嵌入式】STM32F4的ADC采样——多通道、DMA、定时器触发_第3张图片
【嵌入式】STM32F4的ADC采样——多通道、DMA、定时器触发_第4张图片

二、软件配置

提到外设的配置,怎么能少的了STM32Cube这个神器呢,了解了以上硬件原理后,我们可以使用STM32Cube轻松配置需要使用的外设,无非就三个外设——ADC、DMA、TIM。

2.1 ADC配置

这里我选用了ADC1的0、1、2三个通道作为采样通道【嵌入式】STM32F4的ADC采样——多通道、DMA、定时器触发_第5张图片
到外设配置里,如下图配置,打箭头的位置需要注意:1、由于实用的是定时器触发的AD转换,故 连续模式要disable,这样才能定时器触发一次就转换一次选中的3个规则通道;2、由于是多通道,所以要开启扫描模式;3、使用了DMA;4、外部触发方式选择TIM2的Trigger Out event,就是一直在说的TRGO。其他的诸如分频、左右对齐、AD转换位数、转换周期等都不是重点。
【嵌入式】STM32F4的ADC采样——多通道、DMA、定时器触发_第6张图片
同时,由于使用了DMA,故在上图的DMA Setting选项卡中做如下设置:1、根据DMA硬件原理中的DMA映射选择DMA2的数据流0,通道在这里没有体现,应该是通道0,当STM32Cube生成代码时可以看到已经配置好了;2、开启循环模式,否则一次DMA转换完成后就停止了;3、由于有三个通道,一轮ADC转换完成后会有三个采样值,这三个采样值将依次触发DMA请求,所以需要设置DMA内存地址递增,否则1号通道的值就会覆盖0号的值,2号的值又会覆盖1号的值;4、由于STM32的ADC最大就是12位,所以配置为半字(16位)足够。【嵌入式】STM32F4的ADC采样——多通道、DMA、定时器触发_第7张图片

2.2 TIM配置

这里使用了TIM2,配置如下。实验中经过时钟树分频后,到TIM2的时钟为60MHz,此时配置TIM2预分频为35、重载值为375的向上计数模式,计数器每溢出一次就会产生一个更新事件。而按图中配置,更新事件将会触发TRGO信号。那么此时的采样频率为60MHz/35/375 = 3200Hz,也就是1秒钟触发3200次3通道的转换。【嵌入式】STM32F4的ADC采样——多通道、DMA、定时器触发_第8张图片

2.3 DMA配置

DMA没啥配置。。。因为在ADC配置中已经都配置好了,需要注意三点:1、就是要记得开启DMA中断,并在中断服务函数中及时的对AD采样值处理;2、开始DMA的ADC转换:HAL_ADC_Start_DMA(&hadc1,buffer,3),buffer即为DMA接收缓存,3表示DMA传输的数据大小,即传输3个半字后就产生传输完成中断;3、不用DMA的传输完成一半中断的话记得关掉,以免DMA中断服务被没必要的调用。

最后.上代码

当然,这些代码都是STM32Cube生成的,你需要做的仅仅就是写一个好用的DMA中断服务函数,具体流程就是判断来了传输完成中断->请标志位->将DMA缓存中的数据拷贝到数据处理的缓存,然后做什么就有你而定了。

void MX_GPIO_Init(void)
{

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOH_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();

}

ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;

/* ADC1 init function */
void MX_ADC1_Init(void)
{
  ADC_ChannelConfTypeDef sConfig;

    /**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_DIV2;
  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_T2_TRGO;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 3;
  hadc1.Init.DMAContinuousRequests = ENABLE;
  hadc1.Init.EOCSelection = ADC_EOC_SEQ_CONV;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

    /**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_3CYCLES;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }
	
    /**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(__FILE__, __LINE__);
  }

    /**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(__FILE__, __LINE__);
  }

}

void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct;
  if(adcHandle->Instance==ADC1)
  {
  /* USER CODE BEGIN ADC1_MspInit 0 */

  /* USER CODE END ADC1_MspInit 0 */
    /* ADC1 clock enable */
    __HAL_RCC_ADC1_CLK_ENABLE();
  
    /**ADC1 GPIO Configuration    
    PA0-WKUP     ------> ADC1_IN0
    PA1     ------> ADC1_IN1
    PA2     ------> ADC1_IN2 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2;
    GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /* ADC1 DMA Init */
    /* ADC1 Init */
    hdma_adc1.Instance = DMA2_Stream0;
    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_HIGH;
    hdma_adc1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
    if (HAL_DMA_Init(&hdma_adc1) != HAL_OK)
    {
      _Error_Handler(__FILE__, __LINE__);
    }

    __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 
    */
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2);

    /* ADC1 DMA DeInit */
    HAL_DMA_DeInit(adcHandle->DMA_Handle);
  /* USER CODE BEGIN ADC1_MspDeInit 1 */

  /* USER CODE END ADC1_MspDeInit 1 */
  }
} 
void MX_DMA_Init(void) 
{
  /* DMA controller clock enable */
  __HAL_RCC_DMA2_CLK_ENABLE();

}
TIM_HandleTypeDef htim2;

/* TIM2 init function */
void MX_TIM2_Init(void)
{
  TIM_ClockConfigTypeDef sClockSourceConfig;
  TIM_MasterConfigTypeDef sMasterConfig;

  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 35 - 1;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 375 - 1 ;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    _Error_Handler(__FILE__, __LINE__);
  }

}

void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{

  if(tim_baseHandle->Instance==TIM2)
  {
  /* USER CODE BEGIN TIM2_MspInit 0 */

  /* USER CODE END TIM2_MspInit 0 */
    /* TIM2 clock enable */
    __HAL_RCC_TIM2_CLK_ENABLE();
  /* USER CODE BEGIN TIM2_MspInit 1 */

  /* USER CODE END TIM2_MspInit 1 */
  }
}

void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* tim_baseHandle)
{

  if(tim_baseHandle->Instance==TIM2)
  {
  /* USER CODE BEGIN TIM2_MspDeInit 0 */

  /* USER CODE END TIM2_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_TIM2_CLK_DISABLE();
  /* USER CODE BEGIN TIM2_MspDeInit 1 */

  /* USER CODE END TIM2_MspDeInit 1 */
  }
} 

你可能感兴趣的:(嵌入式)