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顺序
  • 建议使用宏定义通道数和采样次数,方便修改

这样就可以方便地获取任意通道的单次值或多次采样的平均值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值