一、简介
最近在用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顺序
- 建议使用宏定义通道数和采样次数,方便修改
这样就可以方便地获取任意通道的单次值或多次采样的平均值。