STM32 HAL库使用笔记(五)ADC单通道/双通道DMA传输详解

实现目的:利用ADC采集光敏传感器/烟雾传感器的值,并利用串口打印

实验平台:正点原子精英版

一、简介

1.DMA的介绍

参考:STM32 hal库使用笔记(四)DMA—内存到内存/内存到外设_乱码小伙的博客-CSDN博客

2.ADC简介

     ADC(Analog-Digital Converter)模拟-数字转换器 ADC可以将引脚上连续变化的模拟电压转换为内存中存储的数字变量,建立模拟电路到数字电路的桥梁;

    12位逐次逼近型ADC,1us转换时间;

    输入电压范围:0~3.3V,转换结果范围:0~4095;

    18个输入通道,可测量16个外部和2个内部信号源;

    规则组和注入组两个转换单元,可利用模拟看门狗自动监测输入电压范围。

3.一些概念

    ADC数据寄存器是32位,但是只用到16位,所以一般采用右对齐方式,方便计算。

    如果多通道转换ADC,次数频繁,间隔时间短(循环模式),会导致数据寄存器的数值被覆盖,所以要利用DMA转运。

    注入组限制较多,一般采用规则组进行转换。

    TCONV = 采样时间 + 12.5个ADC周期,采样时间=n个ADC周期,ADC采用频率最大是14MHZ,由于ADC挂载总线的频率是72MHZ所以进行6分频,采用周期是12MHZ。

    ADC有一个内置自校准模式。校准可大幅减小因内部电容器组的变化而造成的准精度误差。校准期间,在每个电容器上都会计算出一个误差修正码(数字值),这个码用于消除在随后的转换中每个电容器上产生的误差。HAL_ADCEx_Calibration_Start(),进行校准。

二、HAL库配置

1.时钟树的设置

按照下图配置即可:

 2.ADC的配置

关于串口的配置参考:STM32 hal库使用笔记(二)中断—串口中断_乱码小伙的博客-CSDN博客

    本实验不使用串口中断,中断部分的配置不用操作

2.1 单通道(代码对应3.1)

1)关闭扫描模式,由于只有一个通道;

2)关闭连续转换模式,每次需要ADC转换时打开ADC转换即可;

3)采用规则组;

4)软件触发模式;

5)28.5个ADC周期,所以整个采采样周期是30个ADC周期。

配置完成后生成代码即可。

2.2 DMA双通道(代码对应3.2)

1)开启双通道

2)扫描模式打开

3)转换组设置为2

4)打开通道1

5)周期28.5个

6)打开通道2

7)周期28.5个

注意每个ADC的每个通道只能对应一个GPIO。

ADC数据是16位的,所以采用半字字宽。

配置完成后,生成代码即可。

三、代码编写

3.1 

以下代码在adc.c中编写

void MX_ADC1_Init(void)
{
  ADC_ChannelConfTypeDef sConfig = {0};

  /** Common config 
  */
  hadc1.Instance = ADC1;
  hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
  hadc1.Init.ContinuousConvMode = DISABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 1;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel 
  */
  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_28CYCLES_5;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)//配置ADC时钟,通道、序列 
  {
    Error_Handler();
  }
  HAL_ADCEx_Calibration_Start(&hadc1);//用户添加,ADC校准,据了解最新版HAL库已经删除 
}

用户编码区:

uint32_t adc_get_result(void)
{
    HAL_ADC_Start(&hadc1);//单次转换模式,每次转换完成后ADC转换会自动停止
    HAL_ADC_PollForConversion(&hadc1, 10);//ADC转换,转换参数ms
    return (uint16_t)HAL_ADC_GetValue(&hadc1);
}

以下在main.c

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */
  

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_ADC1_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
     
    /* USER CODE BEGIN 3 */
    HAL_Delay(2000);
      unsigned char arrary[4];
        arrary[0] = ((adc_get_result()/1000))+0x30;      
		arrary[1] = (((adc_get_result()%100)/10))+0x30;
		arrary[2] = ((adc_get_result()%10)/10)+0x30;
        arrary[3] = ((adc_get_result()%1)/10)+0x30;
    //unsigned char MyArray[1]={adc_get_result()};
      //HAL_UART_Transmit(&huart1, MyArray,1, 10000);
    HAL_UART_Transmit(&huart1, arrary,4,1000);
  }
  /* USER CODE END 3 */
}

实验现象:用手盖住和放开有明显变化

3.2

添加接收数据数组:uint16_t g_adc_dma_buf[2];

以下代码均在adc.c中编写:

void MX_ADC1_Init(void)
{
  ADC_ChannelConfTypeDef sConfig = {0};

  /** Common config 
  */
  hadc1.Instance = ADC1;
  hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
  hadc1.Init.ContinuousConvMode = DISABLE;
  hadc1.Init.DiscontinuousConvMode = DISABLE;
  hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
  hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
  hadc1.Init.NbrOfConversion = 2;
  if (HAL_ADC_Init(&hadc1) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel 
  */
  sConfig.Channel = ADC_CHANNEL_1;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_28CYCLES_5;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Regular Channel 
  */
  sConfig.Channel = ADC_CHANNEL_2;
  sConfig.Rank = ADC_REGULAR_RANK_2;
  if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
  {
    Error_Handler();
  }
   HAL_ADC_Start_DMA(&hadc1,(uint32_t *)(g_adc_dma_buf) ,0);//触发ADC转换,DMA传输数据
}

 HAL_ADC_Start_DMA(&hadc1,(uint32_t *)(g_adc_dma_buf) ,0);这个可以省略,因为DMA不是循环模式,ADC也不是连续模式,所以每次采集ADC都需要重新开启。

用户编码区:

void adc_dma_enable(uint32_t cndtr)
{
    
    __HAL_ADC_DISABLE(&hadc1);
    
    __HAL_DMA_DISABLE(&hdma_adc1);
    
    while (__HAL_DMA_GET_FLAG(&hdma_adc1, DMA_FLAG_TC1))
    {
    __HAL_DMA_CLEAR_FLAG(&hdma_adc1, DMA_FLAG_TC1);  //清除标志位
    }//实测,删去也能正常使用,因为测量间隔时间长,DMA肯定关闭了
    
    DMA1_Channel1->CNDTR = cndtr;

    __HAL_DMA_ENABLE(&hdma_adc1);
    
    __HAL_ADC_ENABLE(&hadc1);
    HAL_ADC_Start(&hadc1); //开启ADC转换,必须定时开启,因为ADC不是连续扫描模式 
}

以下均在main.c

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */
  

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
         HAL_Delay(2000);
       adc_dma_enable(2);//必须添加
      unsigned char arrary[4];
      unsigned char arrary2[4];
        arrary[0] = ((g_adc_dma_buf[0]/1000))+0x30;      
		arrary[1] = (((g_adc_dma_buf[0]%100)/10))+0x30;
		arrary[2] = ((g_adc_dma_buf[0]%10)/10)+0x30;
        arrary[3] = ((g_adc_dma_buf[0]%1)/10)+0x30;
      printf("ADC1:");
      HAL_UART_Transmit(&huart1, arrary,4,1000);
      printf("\r\n");
        arrary2[0] = ((g_adc_dma_buf[1]/1000))+0x30;      
		arrary2[1] = (((g_adc_dma_buf[1]%100)/10))+0x30;
		arrary2[2] = ((g_adc_dma_buf[1]%10)/10)+0x30;
        arrary2[3] = ((g_adc_dma_buf[1]%1)/10)+0x30;
      printf("ADC2:");
      HAL_UART_Transmit(&huart1, arrary2,4,1000);
      printf("\r\n");
  }
  /* USER CODE END 3 */
}

实验现象:没有打火机,烟雾传感器值一直为0,ADC通道1连接光敏,有明显变化(连接到通道2上也有明显变化)。实测成功。

 欢迎大家交流和指正!!!

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 HAL库使用笔记(五)ADC单通道/双通道DMA传输详解

发表评论