使用STM32L071进行高效的TIM DMA ADC数据采集

 简介:使用TIM+DMA+ADC可实现高频、大量的数据采集,节省CPU资源,本文将讲解如何结合三者进行高频、大量的ADC采集。

概述:本文大致介绍使用定时器触发ADC采集转换,转换数据通过DMA通道传输存入内存空间。可在DMA中断中处理相关数据。

优点:ADC采用频率可调范围广,可一次性大量采集,DMA传输完成后及时处理,提升数据的实时性。

一、硬件配置

        所有硬件配置均通过STM32cubeMX自动生成,再根据自己需求对各寄存器参数进行微调,故本文不再对各硬件功能、性能参数做介绍,相关知识可查看数据手册。

(一)、TIM配置

  本列中,定时器用于事件更新触发ADC转换。此处使用TIM3,在TIM3中断函数中更新事件,达到触发ADC转换的目的(下文ADC配置中,外部触发源需要对应选择TIM3触发)。

       使用STM32cubeMX生成代码如下:(通过调整定时器更新中断频率调整ADC采集周期)

tiemr3_init()
{ 
  TIM_SlaveConfigTypeDef sSlaveConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};

  /* USER CODE BEGIN TIM3_Init 1 */

  /* USER CODE END TIM3_Init 1 */
  timer3.Instance = TIM3;
  timer3.Init.Prescaler = 8; //预分频值
  timer3.Init.CounterMode = TIM_COUNTERMODE_UP;//向上计数
  timer3.Init.Period = 100;//自动重装载值
  timer3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;//时钟分频系数
  timer3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;//自动重装载使能
  if (HAL_TIM_Base_Init(&timer3) != HAL_OK)
  {
    Error_Handler();
  }
  sSlaveConfig.SlaveMode = TIM_SLAVEMODE_TRIGGER;//触发模式
  sSlaveConfig.InputTrigger = TIM_TS_ITR0;//内部触发器0为触发源
  if (HAL_TIM_SlaveConfigSynchro(&timer3, &sSlaveConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE;//更新事件作为触发输出
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&timer3, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  
  __HAL_RCC_TIM3_CLK_ENABLE();
  /* TIM3 interrupt Init */
  HAL_NVIC_SetPriority(TIM3_IRQn, 3, 0);//中断配置
  HAL_NVIC_EnableIRQ(TIM3_IRQn);
}

(二)、DMA配置

  DMA工作原理不做过多讲解,简单说来就是可以将一个地址的数据内容搬运至另一个地址内存中,而在这个过程中不需要CPU的介入,两个地址可以分别是:外设寄存器地址与存储器地址、存储器地址与存储器地址、外设寄存器地址与外设寄存器地址。

  这里推荐一篇文章:https://blog.csdn.net/as480133937/article/details/104927922 作者详细讲解了DMA的工作原理,内容通俗易懂。

  DMA配置代码如下:

 

dma_init()
{
    hdma_adc.Instance = DMA1_Channel1;
    hdma_adc.Init.Request = DMA_REQUEST_0;//指定通道选择请求
    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;//数据宽度——半字-16位
    hdma_adc.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_adc.Init.Mode = DMA_CIRCULAR;//DMA_NORMAL;
    hdma_adc.Init.Priority = DMA_PRIORITY_HIGH;
    if (HAL_DMA_Init(&hdma_adc) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(hadc,DMA_Handle,hdma_adc);//连接DMA与ADC句柄

    /* DMA controller clock enable */
      __HAL_RCC_DMA1_CLK_ENABLE();
    
    /* DMA interrupt init */
    /* DMA1_Channel1_IRQn interrupt configuration */
    HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 3, 0);
    HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);

}

(三)、ADC配置

  ADC配置与使用软件触发时配置相同,仅需注意外部触发源需要选择定时器3的触发器0作为触发源,其余参数根据自己需求修改。

  ADC代码配置如下:

adc_init()
{
  ADC_ChannelConfTypeDef sConfig = {0};

  /** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
  */
  hadc.Instance = ADC1;
  hadc.Init.OversamplingMode = DISABLE;
  hadc.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV1;
  hadc.Init.Resolution = ADC_RESOLUTION_12B;//12位
  hadc.Init.SamplingTime = ADC_SAMPLETIME_39CYCLES_5;//采样时间
  hadc.Init.ScanConvMode = ADC_SCAN_DIRECTION_FORWARD;
  hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc.Init.ContinuousConvMode = ENABLE;//DISABLE;
  hadc.Init.DiscontinuousConvMode = DISABLE;
  hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_RISING;
  hadc.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T3_TRGO;
  hadc.Init.DMAContinuousRequests = DISABLE;//DMA转换为连续模式
  hadc.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
  hadc.Init.Overrun = ADC_OVR_DATA_PRESERVED;
  hadc.Init.LowPowerAutoWait = DISABLE; 
  hadc.Init.LowPowerFrequencyMode = DISABLE; 
  hadc.Init.LowPowerAutoPowerOff = DISABLE; 
  if (HAL_ADC_Init(&hadc) != HAL_OK)
  {
    Error_Handler();
  }

  /** Configure for the selected ADC regular channel to be converted.
  */
  sConfig.Channel = ADC_CHANNEL_0;
  sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
  if (HAL_ADC_ConfigChannel(&hadc, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }

}

二、软件结合

(一)、中断函数

1、定时器中断函数:

     在中断中给出更新事件,触发ADC转换。


void TIM3_IRQHandler()
{
  HAL_TIM_GenerateEvent(&timer3,TIM_EVENTSOURCE_UPDATE);
  HAL_TIM_IRQHandler(&timer3);
}

此处生成软件事件直接放在中断函数里面,在cubeMX自动生成的文件中,有中断回调函数,可在回调函数里生成软件事件。个人不习惯,故放在外面。

2、DMA传输结束中断函数:

  DMA在每次传输完成后会进入DMA中断,可在该中断中处理数据或根据需求进行相应动作。

void DMA1_Channel1_IRQHandler(void)
{
  /* USER CODE BEGIN DMA1_Channel1_IRQn 0 */

  /* USER CODE END DMA1_Channel1_IRQn 0 */
  HAL_DMA_IRQHandler(&hdma_adc);
  /* USER CODE BEGIN DMA1_Channel1_IRQn 1 */

  /* USER CODE END DMA1_Channel1_IRQn 1 */
}

 (二)、数据转换

数据转换与传输不是一次触发就永远执行的,定时器生成的触发事件仅触发ADC进行数据转换,但并没有将数据传输至存储器中,需要使用下面函数启动转换传输数据。在一组数据传输完成后需要重新启动方可转换。(*pData为缓存空间,length为数据个数,可动态调整通道传输个数)

HAL_ADC_Start_DMA(ADC_HandleTypeDef *hadc, uint32_t *pData, uint32_t Length)

三、总结

  第一次使用定时器+DMA进行ADC采集,单个模块的使用很简单,但是要将三个联系在一起,且不习惯使用cubeMX的情况下,还是有许多疑问。在此记录一下,文中不尽之处欢迎指正,欢迎探讨。

  最后,DMA真的很实用,且方便。

作者:狡猾的鹰

物联沃分享整理
物联沃-IOTWORD物联网 » 使用STM32L071进行高效的TIM DMA ADC数据采集

发表评论