STM32CubeMX能够极大减小STM32外设配置的工作量,因此作者也借助空闲时间对STM32CubeMX相关配置进行了学习,本文介绍如何利用STM32CubeMX配置ADC采样,记录了作者学习过程中遇到的问题及解决办法,使大家少走弯路,并方便以后复习
目录
- 1、单通道轮询
- 2、单通道中断
- 3、单通道DMA
- 4、多通道轮询
- 5、多通道中断
- 6、多通道DMA
1、单通道轮询
先选择所使用的MCU,这里我使用的是STM32F407ZGT系列
修改一下DEBUG功能,否则后续无法调试
修改时钟,采用外部晶振
配置一串口,用于打印采集的ADC值
这里我采用ADC1的通道0,并开启连续采样模式,否则每次开启ADC采样后只进行一次采样
开启ADC全局中断(如果只采用轮询采样就不用开启,这里开启是为了方便后面演示中断采样)
修改一下ADC全局中断的优先级
在HCLK处输入其时钟最高频率168,按回车,其余时钟会自动配置好,一般情况下无需改动
勾选上后,每个外设会单独保存至一个.c文件,便于查看
生成并打开文件
在main.h中包含头文件stdio.h
在usart.c文件中加入fputc函数,加入后才能使用printf函数进行打印数据
在while循环中添加以上代码,由于开启了连续转换模式,即hadc1.Init.ContinuousConvMode = ENABLE;因此每次转换完成后都要调用HAL_ADC_Stop来关闭ADC;如果不想每次都调用该函数来关闭ADC,可以关闭连续转换模式,即hadc1.Init.ContinuousConvMode = DISABLE,这样每次开启ADC转换以后只会进行一次采样,这样就不需要每次都关闭ADC了
实验现象如下图:
2、单通道中断
单通道中断采样配置过程与单通道轮询采样相同,利用cubemx配置完成后有两种开始ADC采样的方法
方法一:
在主函数中加入上述代码,__HAL_ADC_ENABLE_IT(&hadc1,ADC_IT_EOC);用于打开ADC转换完成中断,HAL_ADC_Start(&hadc1);用于开启ADC采样,这两个函数一定要放到外设初始化函数的后面,否则串口函数初始化未完成,在ADC中断中使用printf函数可能会卡死
在adc.c文件中加入上面代码,HAL_ADC_ConvCpltCallback是ADC中断的回调函数,其原型是个弱函数,重新定义后原来的函数就失去作用了
最后将adc.c文件中的通道转换结束标准修改为ADC_EOC_SEQ_CONV,否则只会进入一次中断
ADC_EOC_SEQ_CONV:在所有通道转换完成后进入中断
ADC_EOC_SINGLE_CONV:单个通道转换完成后进入中断
多个通道时,两种均可采用,单通道采样时需使用ADC_EOC_SEQ_CONV才能连续进入中断(经测试)
最后将程序下载到单片机中,现象如下:
方法二:
我们利用HAL_ADC_Start_IT开启adc中断,该函数比较特殊,调用一次该函数就打开了ADC转换完成中断和开启ADC采样
在主函数中直接调用HAL_ADC_Start_IT即可
3、单通道DMA
dma有两种模式,分别为circular和normal
circular模式:dma的circular模式只需要调用一次dma开启函数,dma就会持续的搬运数据,提高了数据的刷新速度,但是在circular模式下,不管adc新的一轮数据采集是否完成,有可能直接将旧数据搬运走
normal模式:该模式下,dma启动函数调用一次,dma通道只会搬运一次数据,这样每调一次dma启动函数,dma只会搬运一次数据,等待数据传输完成后再次开启dma启动函数,这样更能保证adc数据采集的可靠性
circular模式:
cubemux配置步骤如下(时钟配置等已略去):
这里我们同样用ADC1的通道IN0来测试,开启adc连续采集模式
修改ADC采样时间,通常采样时间越长,adc采样精度越高
选用dma的circular模式
返回开启dma连续请求(若不开启,只能进入一次dma采集完成中断函数)
uint16_t AD_value=0;
float f_AD_value;
打开工程文件,在文件中添加上面代码,AD_value用于保存测量的adc原值,f_AD_value保存计算得到的电压值
HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&AD_value,sizeof(AD_value));
在主函数中调用HAL_ADC_Start_DMA开启adc采样,同时开启dma传输数据(调用一次即可)
最后在dma中断函数中添加上述代码,将adc采集完成的数据打印出来
normal模式:
normal模式下,我们的想法是调用一次HAL_ADC_Start_DMA函数,adc进行一次数据采集,dma搬运一次数据,我们在上面配置完成的代码中做简单修改就能实现
关闭adc连续采集模式,这样开启一次adc,只会进行一次数据采集
将dma模式修改为normal
修改dma中断函数
HAL_ADC_Start_DMA(&hadc1,(uint32_t*)&AD_value,sizeof(AD_value));
最后在主函数中调用HAL_ADC_Start_DMA开启adc采样,同时开启dma传输数据(每调用一次只会采集一次数据)
4、多通道轮询
这里用ADC1的通道IN0、IN1、IN2、IN3四个通道作演示,多个通道时必须开启间断模式,并且每个间断组中只有一个通道,否则每次只能读取到每组最后一个通道的值
设置通道转换顺序
uint16_t AD_value[4]={0};
打开工程文件,创建一数组用于存储四个通道的ADC值
for(i=0;i<4;i++){
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1,10);
AD_value[i]=HAL_ADC_GetValue(&hadc1);
printf("PA%d:%d\r\n",i,AD_value[i]);
printf("PA%d:%.3f v\r\n",i,AD_value[i]*3.3/4096);
}
HAL_Delay(500);
在主循环中添加上述代码,HAL_ADC_Start必须放在for循环中,否则只能采集第一个通道的ADC值;HAL_ADC_PollForConversion用于轮询转换,是一个阻塞函数,等待转换完成,参数10是阻塞时间
5、多通道中断
多通道情况下使用中断来读取数据理论上是可行的,但是读取的数据会混淆,即无法确定读取的数据是属于哪一个通道的,因此我们不使用
6、多通道DMA
开启DMA并采用circular模式
uint16_t AD_value[4]={0};
定义一数组用于保存ADC采样值
在外设初始化函数下面调用 HAL_ADC_Start_DMA(&hadc1,(uint32_t*)AD_value,sizeof(AD_value));启动ADC转换和DMA数据传输
最后在DMA接收完成中断中将采集到的ADC数据打印出来
经测试:DMA接收中断在数据接收缓冲区满了后触发,这里的缓冲区是数据AD_value[4]
注意:
通常为了方便使用一些滤波算法,我们可以将缓冲区数据创建的更大一些(为通道数的整数倍),这里我们采用了4个通道,我们可以将缓冲数组创建为AD_value[40]
则AD_value[0]、AD_value[4]、AD_value[8]…AD_value[36]均为通道IN0的值
AD_value[1]、AD_value[5]、AD_value[9]…AD_value[37]均为通道IN1的值,
AD_value[2]、AD_value[6]、AD_value[10]…AD_value[38]均为通道IN2的值,
AD_value[3]、AD_value[7]、AD_value[11]…AD_value[39]均为通道IN3的值,
我们同样在dma接收完成中断中将数据打印出来
实验现象如上,只打印了一次数据,表示只进入了一次DMA中断中,并且主循环中的程序也不再继续执行,程序卡死了
可以在进入DMA中断函数时调用HAL_ADC_Stop_DMA来关闭ADC采集,当需要采集ADC的时候再调用 HAL_ADC_Start_DMA即可,这里为了演示在中断函数结束时又重新开启了ADC
这样就能连续采集打印数据了