目录
STM32CubeMX软件(Version 6.10.0)
keil μVision5 IDE(MDK-Arm)
CH340G Windows系统驱动程序(CH341SER.EXE)
三个滑动变阻器
使用STM32CubeMX软件配置STM32F407开发板的ADC实现ADC多通道DMA采集,具体为使用ADC_IN5/6/7三个通道进行DMA连续ADC转换
“STM32CubeMX教程13 ADC - 单通道转换”实验中提到过,规则通道只有一个16位的数据寄存器,因此规则通道同时只能转换一个ADC通道,而且每次转化完一个ADC通道就需要及时从数据寄存器中取出转化的数据,否则会被后面转化完毕的通道数据覆盖
这个时间非常短,一般不采用像单通道转化中使用的中断中提取处理每个单通道的数据的方法,而是采用DMA连续转化的方法,将多通道转化完毕之后,在DMA的数据存储中将采集到的所有通道的数据一起处理
ADC是利用片上的模数转换器将外部的模拟量转化为数字量存储到内存中,因此数据方向应该为从外设到内存,而且只有这一种方向,因此可知ADC的DMA方向也只有外设到内存一种
从之前的“STM32CubeMX DMA 直接内存读取???????”实验中我们可知ADC1的DMA通道有DMA2_Stream0 CH0和DMA2_Stream4 CH0两个通道
ADC的DMA请求模式一般选择循环模式,在多通道ADC采集时,配合使能扫描转化模式,这样就可以连续转化多通道而不停止
由于ADC采集后的数据一般需要存储在内存中,因此在选择地址递增时,ADC外设地址不增加,内存地址选择递增
使用HAL_ADC_Start_DMA以DMA方式启动ADC采集需要指定存储的内存首地址,从函数的定义可知其为uint32_t*类型,因此在DMA配置时我们需要选择的数据宽度为字Word
请阅读“STM32CubeMX教程1 工程建立”实验3.4.1小节配置RCC和SYS
系统时钟树配置均设置为STM32F407总线能达到的最高时钟频率,具体如下图所示
本实验需要需要初始化USART1作为输出信息渠道,具体配置步骤请阅读“STM32CubeMX教程9 USART/UART 异步通信”
在Pinout & Configuration页面左边功能分类栏目Analog中单击其中ADC1,勾选IN5/6/7三个通道,在下方的参数设置中以上个实验为模板修改部分参数
①使能连续转换模式,因此现在需要转换5/6/7三个通道,因此使能该模式之后,在规则通道转换为其中一个通道后就会接收转换下一个通道
②使能DMA连续转换请求,该参数的使能需要在配置完DMA请求之后才可选,配合参数①可以实现连续不间断的对三个通道数据进行采集
③结束转换选择EOC flag at the end of all conversions,该参数表示当转换完毕一组ADC中的所有通道之后再产生EOC标志,进入中断
④规则通道转换数量现在为3,对应三个不同的通道,通道转换顺序及每个通道的采样时间由Rank及其下参数决定
具体参数配置如下图所示
单击Configuration中的DMA Settings选项卡对ADC1的DMA请求进行设置,单击ADD按键增加DMA请求,这里可选的只有一个ADC1
选择想要使用的DMA Stream,并设置优先级,将DMA请求模式设置为循环模式,外设地址不增加,内存地址递增,数据宽度选择字Word
为何如此配置? 请阅读本实验“3.0、前提知识”,如下图所示为ADC1的DMA请求具体设置
在Pinout & Configuration页面左边System Core/NVIC中勾选DMA2 Stream0 全局中断,然后选择合适的中断优先级即可
注意这里没有勾选ADC1/2/3的全局中断,因为外设DMA中断使用的回调函数和外设本身中断的回调函数一般是同一个回调函数(为什么?请阅读本实验3.2.2小节),如果同时开始两者中断可能会导致重复进入中断函数
但是有些外设使用DMA时必须开启自身的中断,不同外设情况不一样
建议在外设使用DMA时,尽量不开启外设全局中断,必须开启的可以禁用外设主要事件源产生的硬件中断(注释1)
上述步骤如下图所示
请阅读?“STM32CubeMX教程1 工程建立”实验3.4.3小节配置Project Manager
单击页面右上角GENERATE CODE生成工程
首先在生成的工程主函数main()中调用MX_DMA_Init()函数对ADC1用到的DMA时钟及其流的中断进行了配置
然后调用MX_ADC1_Init()函数对ADC1的基本参数、通道和通道参数进行了配置,并调用了HAL_ADC_Init()使用配置的参数初始化了ADC1
在初始化函数HAL_ADC_Init()中又调用了HAL_ADC_MspInit()函数,在该函数中使能了ADC1/GPIOA的时钟,对ADC1_IN5/6/7的输入引脚做了复用设置,然后对ADC1的DMA参数配置并进行了初始化
具体的ADC DMA初始化调用流程如下图所示
CubeMX中勾选DMA2_Stream0的全局中断后,会在stm32f4xx_it.c中增加DMA的中断服务函数DMA2_Stream0_IRQHandler()
在中断服务函数DMA2_Stream0_IRQHandler()中调用了HAL库的DMA全局中断处理函数,该函数中根据各种标志判断DMA传输完成/失败/一半完成等事件,然后根据不同的事件调用不同的回调函数,这里DMA传输完成之后调用了hdma->->XferCpltCal1back()
上述过程如下图所示
这个函数指针在以DMA方式启动ADC采集时被指向DMA传输完成回调ADC_DMAConvCplt()函数
在该DMA传输完成回调ADC_DMAConvCplt()函数中最终调用了ADC采集完成回调HAL_ADC_ConvCpltCallback()函数,该函数上一个实验我们重新实现过
上述过程如下图所示
之前所有的外设回调函数都是直接调用了HAL库提前准备好的虚函数,比如ADC的采集完成回调函数HAL_ADC_ConvCpltCallback(),用户直接实现该虚函数即可
但是DMA不是一个外设,而是数据传输手段,大多数外设都可以使用,因此DMA的各种事件回调函数不是一个真正的函数,而是一个函数指针
当我们以DMA传输的方式启动某个外设的时候,就会将该外设对应事件的中断服务函数地址赋值给对应事件DMA中断回调函数指针
在主函数中以DMA的方式启动ADC采集传输,然后启动ADC1的触发源TIM3定时器,具体代码如下图所示
在adc.c中重新实现DMA传输完成回调函数,在该函数中取出ADC转换完成的三通道采集值,然后处理并通过串口输出,具体代码如下图所示
一些定义及函数源代码如下
/*main.c中的全局变量定义*/
uint32_t DataBuffer[BATCH_DATA_LEN];
/*main.h中的变量外扩及宏定义*/
#define BATCH_DATA_LEN 3
extern uint32_t DataBuffer[BATCH_DATA_LEN];
/*DMA转换完成中断回调*/
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
/*定时器DMA启动多通道转换*/
uint32_t val=0,Volt=0;
for(uint8_t i=0;i<BATCH_DATA_LEN;i++)
{
val=DataBuffer[i];
Volt=(3300*val)>>12;
printf("ADC_IN%d, val:%d, Volt:%d\r\n",i+5,val,Volt);
}
printf("\r\n");
}
/*以DMA方式启动ADC采集*/
HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef *hadc, uint32_t *pData, uint32_t Length)
/*结束以DMA方式启动的ADC采集*/
HAL_StatusTypeDef HAL_ADC_Stop_DMA(ADC_HandleTypeDef *hadc)
“USART1配置 -> TIM3定时器及触发源配置 -> ADC1多通道及参数配置 -> ADC1的DMA传输参数配置 -> DMA2 Stream0全局中断配置 -> 重新实现DMA传输完成回调函数HAL_ADC_ConvCpltCallback() -> 主函数中启动触发源定时器及以DMA方式启动ADC采集”
烧录程序,单片机上电之后,串口不断的输出三个通道的ADC采集值,笔者将三个滑动变阻器按照通道5、通道6和通道7的顺序,分别从一端缓慢拧到另一端,可以从串口输出的数据看到,通道5/6/7三个通道采集到的ADC数据从最大4095慢慢变到最小值0
注释1:详细内容请阅读STM32Cube高效开发教程(基础篇)14.5.1小节内容