STM32 HAL库ADC与DMA协同采集详解

一、简介

最近在用ADC采集电压时发现一个问题,就是一个adc如果开启多个通道,无法直接对指定通道利用HAL库函数对它进行采集。本文详细介绍STM32 HAL库下ADC多通道采集的几种实现方式,包括基础配置、DMA传输以及实际应用示例。

二、DMA工作模式对比

2.1 循环模式(Circular Mode)

  • 特点:
  • DMA传输完成后自动重新开始
  • 不需要软件干预,持续传输
  • 适合连续采样场景
  • 应用场景:
  • ADC连续采样
  • 串口接收数据
  • 传感器实时监测
  • 2.2 普通模式(Normal Mode)

  • 特点:
  • DMA传输完成后自动停止
  • 需要手动重启DMA继续传输
  • 适合单次传输场景
  • 应用场景:
  • 串口发送数据
  • 单次数据采集
  • 存储器间数据搬运
  • 三、ADC多通道采集

    问题:当使用多通道采集时发现采集ADC的函数只有一个就是,HAL_ADC_GetValue(&hadc2)但是这个无法对多个通道进行采集

    解决方法:

    这里先看cubemx配置

    这里要将转换通道数设置为2

    然后对对应的rank配置通道

    然后可以知道这里使用DMA传输时利用HAL_ADC_Start_DMA(&hadc2,adcval,2);数据会按rank顺序存在DMA缓冲区。这时我们只需要对DMA缓冲区读取数据即可。

    配置DMA

    此处DMA配置为Normal,稍后会将两种模式的区别。

    uint8_t adc_cov=1;//这里使用全局变量用于等待转换完成
    void ADC_Get(void)
    {
    	uint32_t adcval[2];
    
    	HAL_ADC_Start_DMA(&hadc2,adcval,2);
    	DATA.PA4=(float)adcval[0]*3.3f/4096;
    	DATA.PA5=(float)adcval[1]*3.3f/4096;
    	while(adc_cov);
    	adc_cov=1;//DMA转换完成后置位一在传输完成后的中断服务函数中置为0,表示结束,跳出while
    	
    	HAL_ADC_Stop_DMA(&hadc2);
    }
    void DMA1_Channel1_IRQHandler(void)
    {
      /* USER CODE BEGIN DMA1_Channel1_IRQn 0 */
    
      /* USER CODE END DMA1_Channel1_IRQn 0 */
      HAL_DMA_IRQHandler(&hdma_adc2);
      /* USER CODE BEGIN DMA1_Channel1_IRQn 1 */
    	adc_cov=0;
      /* USER CODE END DMA1_Channel1_IRQn 1 */
    }

    此处在DMA的完成中断服务函数后将标志位置为0;结束转换。

    四、normal模式和circular模式两种模式区别

    我之前还遇到一个问题就是使用循环模式去采样时,如果利用标志位去等待DMA传输结束会导致程序卡死,之前一直不知道为什么现在终于知道了。

    如果使用circula模式使用标志位的话

    
    void ADC_Get(void)
    {
    	uint32_t adcval[2];
    
    	HAL_ADC_Start_DMA(&hadc2,adcval,2);
    	DATA.PA4=(float)adcval[0]*3.3f/4096;
    	DATA.PA5=(float)adcval[1]*3.3f/4096;
    	while(adc_cov);
    	adc_cov=1;//DMA转换完成后置位一在传输完成后的中断服务函数中置为0,表示结束,跳出while
    	
    	HAL_ADC_Stop_DMA(&hadc2);
    }

    看这段代码,因为DMA是循环模式所以,DMA不会停止采集所以DMA不会触发传输结束的中断,所以,标志位adc_cov永远不会置位0;所以循环不会结束导致程序卡死在这里。

    这里只要cubemx中改为normal模式手动开启DMA即可解决。

    五、如果DMA多通道采集数据如何分配

    如果使用DMA采集多次,但只有两个通道,数据会按照通道的顺序循环存储。假设采集10次,每个通道的数据分布如下

    // 定义DMA缓冲区 - 假设2个通道,每个通道采集10次
    #define ADC_CHANNELS    2    // 通道数
    #define ADC_SAMPLES    10    // 每个通道采样次数
    uint16_t ADC_DMA_Buffer[ADC_CHANNELS * ADC_SAMPLES];  // 总长度 = 2 * 10 = 20
    
    /* 数据在缓冲区中的分布:
    ADC_DMA_Buffer[0]  = 通道1的第1次采样
    ADC_DMA_Buffer[1]  = 通道2的第1次采样
    ADC_DMA_Buffer[2]  = 通道1的第2次采样
    ADC_DMA_Buffer[3]  = 通道2的第2次采样
    ... 以此类推
    */
    
    // 获取指定通道的所有采样值
    void Get_Channel_Values(uint8_t channel, uint16_t* values)
    {
        for(uint8_t i = 0; i < ADC_SAMPLES; i++)
        {
            values[i] = ADC_DMA_Buffer[i * ADC_CHANNELS + channel];
        }
    }
    
    // 获取指定通道的平均值
    uint16_t Get_Channel_Average(uint8_t channel)
    {
        uint32_t sum = 0;
        for(uint8_t i = 0; i < ADC_SAMPLES; i++)
        {
            sum += ADC_DMA_Buffer[i * ADC_CHANNELS + channel];
        }
        return sum / ADC_SAMPLES;
    }
    
    // 使用示例
    void main(void)
    {
        // 启动DMA传输
        HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ADC_DMA_Buffer, ADC_CHANNELS * ADC_SAMPLES);
        
        while(1)
        {
            // 获取通道0的平均值
            uint16_t ch0_avg = Get_Channel_Average(0);
            
            // 获取通道1的平均值
            uint16_t ch1_avg = Get_Channel_Average(1);
            
            // 如果需要获取某个通道的所有采样值
            uint16_t ch0_values[ADC_SAMPLES];
            Get_Channel_Values(0, ch0_values);
            
            printf("CH0 avg: %d, CH1 avg: %d\r\n", ch0_avg, ch1_avg);
            HAL_Delay(500);
        }
    }

    关键点说明:

  • DMA缓冲区中数据按照 "通道1值, 通道2值, 通道1值, 通道2值…" 的顺序排列
  • 要获取某个通道的第N次采样值:ADC_DMA_Buffer[N * 通道数 + 通道序号]
  • 通道序号从0开始,对应ADC配置时的Rank顺序
  • 建议使用宏定义通道数和采样次数,方便修改
  • 这样就可以方便地获取任意通道的单次值或多次采样的平均值。

    作者:taptaptap.jic

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 HAL库ADC与DMA协同采集详解

    发表回复