MCU DMA控制ADC采样和串口传送详解

目录

一、建立工程

1.相同的配置

2.配置ADC

3.配置DMA

二、代码修改

1.定义存储ADC采样结果的数组

2.启动ADC与定时器

3.编写主程序代码

4.重定义回调函数

5.查看结果

三、修改DMA模式

1. 修改DMA模式为Circular

2.查看结果


        采用DMA(Direct Memory Access,直接存储器访问)控制器实现A/D采样。采用这种方式时,一旦配置好ADC参数及所使用的DMA通道,DMA控制器就会自动将A/D转换结果送至指定的存储器空间中(数组)。在使用A/D转换数据时,只需要在主程序中读取相应的数组变量就可以了,无需再调用HAL_ADC_GetValue()等函数来获取A/D转换结果。

        采用DMA的方式可以不占用CPU的资源,直接由DMA控制器来实现外设(或存储器与存储器之间的数据交互。所以,这种方式在实际中是比较实用的,并且可以极大地提高CPU的工作效率。

一、建立工程

       本文项目以来的硬件工程及配置参考本文作者的下述文章,工程配置基本一致。本文只描述不一样的地方。细说MCU用定时器控制ADC采样频率的实现方法并通过Simulink查看串口输出波形-CSDN博客  https://wenchm.blog.csdn.net/article/details/140523545https://wenchm.blog.csdn.net/article/details/140523545

1.相同的配置

  • 配置串口;
  • 配置TIM3,TIM4;
  • 选择时钟源和Debug模式,配置系统时钟和ADC时钟;
  • 配置GPIO,LED;
  • 2.配置ADC

            在硬件配置界面中打开Analog→ADC1,在其模式Mode区中,通道1(IN1)选择IN1 Single-ended;在下面的配置(Configuration)区中,需要对几个参数进行调整:

            首先,在ADC设置(ADC_Settings)参数栏,依然可以不对ADC的时钟进行分频,还将预分频参数(Clock Prescaler)选择为Asynchronous clock mode divided by 1。本例中用定时器实现对采样频率的控制。

            随后,依然将ADC设置(ADC_Settings)参数栏中连续转换模式(Continuous Conversion Mode)设置为Disabled,由于要用DMA,所以需要使能DMA连续请求(DAM Continuous Requests)参数。

    3.配置DMA

            打开DMA设置(DMA Settings)选项卡,先添加一个ADC1的DMA请求。DMA有多个可选通道,这里随便选择一个即可(共有两个DMA,每个都有8个通道)。此外,优先级有四级,从低(Low)到很高(Very High),可以先保持默认值Low。

            DMA请求设置(DMA Request Settings)栏,可以设置DMA的模式;模式有两种:常规(Normal)和循环(Circular)。如果是Normal模式,仅会执行一次DMA,若要继续执行,则要重新启动。在Circular模式下,可以连续执行DMA。此例中,先将DMA模式设置为Normal。此外,在增量地址(Increment Address)中,勾选上存储器(Memory),这样就可以将数据顺次存储到一个数组中。因为A/D的转换结果需要一个16位的数,所以将数据宽度(Data Width)设置为半字(Half Word),一个字为32位。

            ADC1的DMA请求设置完毕后,设置DMA连续请求(DAM Continuous Requests)参数为Enabled。

            在ADC规则转换模式(ADC_Regular_ConversionMode)栏,还是将外部触发转换源(External Trigger Conversion Source)选择为Timer 3 Trigger Out event。在ADC规则转换模式参数栏中,将Rank下的采样时间选择为2.5个周期。

            由于是使用DMA来实现将A/D采样结果传递到存储器(数组)的,所以无需配置ADC的中断。不过,因为配置了ADC的DMA功能,所以会用到DMA的中断。由于上面配置的是DMA1的通道1,所以会自动开启DMA1的通道1中断。打开ADC配置界面中的NVIC设置(NVIC Settings),可以看到DMA1 channel 1 global interrupt已经自动被使能了,并且不能取消。另外一个ADC1的中断(ADC1 and AD2 global interrupt)由于用不到,所以无需开启。

    二、代码修改

    1.定义存储ADC采样结果的数组

            首先定义存储ADC采样结果的数组,本例中用数组变量ADC1ConvertedData。将存储ADC采样结果的数组定义为全局变量,同时定义一个后面会用到的变量ADCDMAFlag,将它们一并放到主程序中的注释对中:

    /* USER CODE BEGIN PV */
    uint16_t ADC1ConvertedData[ADC_CONVERTED_DATA_BUFFER_SIZE] = {0};
    uint8_t ADCDMAFlag = 0;
    /* USER CODE END PV */

            其中,数组长度ADC_CONVERTED_DATA_BUFFER_SIZE可以定义到main.h中:

    /* USER CODE BEGIN Private defines */
    #define ADC_CONVERTED_DATA_BUFFER_SIZE (uint16_t) 60
    /* USER CODE END Private defines */
    

    2.启动ADC与定时器

            本例中无需开启ADC1的中断。不过,要在主函数的初始化代码中调用ADC校验函数HAL_ADCEx_Calibration_Start,启动DMA方式的ADC转换(通过调用HAL_ADC_Start_DMA函数),并开启TIM3(通过调用HAL_TIM_Base_Start)。将上述三个函数的调用放到while(1)之前、MX_ADC1_Init()之后的注释对中:

     /* USER CODE BEGIN 2 */
      HAL_ADCEx_Calibration_Start(&hadc1,ADC_SINGLE_ENDED);
      HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC1ConvertedData,ADC_CONVERTED_DATA_BUFFER_SIZE);
      HAL_TIM_Base_Start(&htim3);
      HAL_TIM_Base_Start_IT(&htim4);
      HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_1);
     /* USER CODE END 2 */

            ADC的采样是由TIM3控制的,采样值存入存储器(数组)的过程是通过DMA完成的,即ADC采样值在DMA控制器的控制下直接传送到数组ADC1ConvertedData中。虽然没有开启ADC1的中断,但在DMA完成设定长度的ADC采样数据传递后,也会调用一次回调函数HAL_ADC_ConvCpltCallback()。这里所谓的“设定长度”,就是函数HAL_ADC_Start_DMA()中的第三个参数。该参数在前面的代码中被设定为60。

            TIM4用来产生信号源。

    3.编写主程序代码

            如果要通过串口送出采样值数据,可以在本次DMA传送完毕后进行。如果DMA还在更新时就进行串口数据发送,可能会出现数据不连续的情况。所以,可以在回调函数HAL_ADC_ConvCpltCallback()中将一个标志变量置位(可使用前面定义的变量ADCDMAFlag),置位就表示DMA传送完毕。然后,在while(1)循环中,以此标志位为条件,实现一段完整的采样值数据发送。串口数据发送,可以通过在主程序中调用串口发送函数来实现。

      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
    	  if(ADCDMAFlag == 1)
    	  {
    		  ADCDMAFlag = 0;
    		  HAL_ADC_Stop_DMA(&hadc1);
    		  HAL_UART_Transmit(&huart2,(uint8_t*)&ADC1ConvertedData,ADC_CONVERTED_DATA_BUFFER_SIZE*2,0xFFFF);
    		  HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC1ConvertedData,ADC_CONVERTED_DATA_BUFFER_SIZE);
    		  HAL_Delay(1000);
    	  }
      }
      /* USER CODE END 3 */

            上面这段代码中有两个是控制DMA的函数,有一个是串口发送数据的函数。第一个函数是让DMA停止工作,暂停数据搬运,然后用函数HAL_UART_Transmit发送A/D采样数据。注意,在HAL_UART_Transmit的参数中,设置发送数据的长度为ADC采样数据的2倍,这是因为串口每次只能发送1个字节的数据,而一个A/D采样值会占用2个字节。数据发送完毕后,再重新启动ADC的DMA传输。

    4.重定义回调函数

            此外,在main.c中重新定义回调函数HAL_ADC_ConvCpltCallback():

    /* USER CODE BEGIN 4 */
    void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
    {
    	ADCDMAFlag =1;
    }
    
    //信号源
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    {
    	HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
    }
    /* USER CODE END 4 */

    5.查看结果

            施加信号源(可以是PA5)到ADC输入端PA0上,打开串口接收的Simulink模型,即可看到通过串口送来的信号波形。

    三、修改DMA模式

    1. 修改DMA模式为Circular

            上面例子中,主程序每间隔1000 ms发送一组数据;每次发送前要关闭DMA,发送后再重启。这种方式送来的两组数据其实并非连续的数据。那么,如何让串口实时向外连续发送A/D采样的数据呢?

            在前面配置ADC1的DMA、设置ADC1的DMA请求的模式时,选择的是Normal。如果选择Circular,DMA就会持续传送ADC采样数据到数组中,不过会循环覆盖;如果能够在下次DMA数据传递完成前将数据发送出去,就不会有影响。假如还是设置ADC采集缓冲区长度为60,则DMA一次会传送60个采样值数据;因为采样频率为1 kHz,所以完成这些数据的采样需要60 ms的时间。加上DMA的处理时间,DMA完成这些数据的传递至少需要60 ms。这60个ADC采样值,占120个字节。串口发送1个字节的数,至少要发送10个二进制位(8个数据位、1个停止位和1个起始位),所以发送120个字节的数据,对应的二进制位数为1200,而设置的串口波特率为115200 bit/s发送1200位需要的时间为(1200/115200)s,约为10.4 ms。这个时间小于DMA搬运一次数据所需的60 ms。所以完全可以实现通过串口的数据实时发送。

            修改while(1)循环中的代码如下:

      /* Infinite loop */
      /* USER CODE BEGIN WHILE */
      while (1)
      {
        /* USER CODE END WHILE */
    
        /* USER CODE BEGIN 3 */
    	  //DMA标准模式
    	  /*if(ADCDMAFlag == 1)
    	  {
    		  ADCDMAFlag = 0;
    		  HAL_ADC_Stop_DMA(&hadc1);
    		  HAL_UART_Transmit(&huart2,(uint8_t*)&ADC1ConvertedData,ADC_CONVERTED_DATA_BUFFER_SIZE*2,0xFFFF);
    		  HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&ADC1ConvertedData,ADC_CONVERTED_DATA_BUFFER_SIZE);
    		  HAL_Delay(1000);
    	  } */
    	  //DMA循环模式
    	  if(ADCDMAFlag ==1)
    	  {
    		  ADCDMAFlag =0;
    		  HAL_UART_Transmit(&huart2,(uint8_t *)&ADC1ConvertedData,ADC_CONVERTED_DATA_BUFFER_SIZE*2,0xFFFF);
    	  }
      }
      /* USER CODE END 3 */

    2.查看结果

     

             DMA模式修改为循环后,串口的工作效率更高,不丢数据,达到了实时传递数据的效果。

    作者:wenchm

    物联沃分享整理
    物联沃-IOTWORD物联网 » MCU DMA控制ADC采样和串口传送详解

    发表回复