STM32CubeMX教程14 ADC - 多通道DMA转换
读者可访问 GitHub - lc-guo/STM32CubeMX-Series-Tutorial 获取原始工程代码
1、准备材料
STM32CubeMX软件(Version 6.10.0)
keil µVision5 IDE(MDK-Arm)
3个滑动变阻器
2、实验目标
使用STM32CubeMX软件配置STM32F407开发板的ADC实现ADC多通道DMA采集,具体为使用ADC_IN5/6/7三个通道进行DMA连续ADC转换
3、实验流程
3.0、前提知识
“STM32CubeMX教程13 ADC - 单通道转换”实验中提到过,规则通道只有一个16位的数据寄存器,因此规则通道同时只能转换一个ADC通道,而且每次转化完一个ADC通道就需要及时从数据寄存器中取出转化的数据,否则会被后面转化完毕的通道数据覆盖
这个时间非常短,一般不采用像单通道转化中使用中断提取处理每个单通道数据的方法,而是采用DMA连续转化的方法,将多通道转化完毕之后,在DMA的数据存储中将采集到的所有通道的数据一起处理
ADC是利用片上的模数转换器将外部的模拟量转化为数字量存储到内存中,数据传输方向应该只有从外设到内存这一种方向,因此可知ADC的DMA方向也只有外设到内存一种
从“STM32CubeMX教程12 DMA 直接内存读取”实验中可知ADC1的DMA通道有DMA2_Stream0 CH0 和 DMA2_Stream4 CH0 两个通道
ADC的DMA请求模式一般选择循环模式,在多通道ADC采集时,配合使能扫描转化模式,这样就可以连续转化多通道而不停止
由于ADC采集后的数据一般需要存储在内存中,因此在选择地址递增时,ADC外设地址不增加,内存地址选择递增
使用HAL_ADC_Start_DMA()以DMA方式启动ADC采集时需要指定存储的内存首地址,从函数的定义可知其为uint32_t*类型,因此在DMA配置时我们需要选择的数据宽度为字Word
3.1、CubeMX相关配置
3.1.0、工程基本配置
打开STM32CubeMX软件,单击ACCESS TO MCU SELECTOR选择开发板MCU(选择你使用开发板的主控MCU型号),选中MCU型号后单击页面右上角Start Project开始工程,具体如下图所示
开始工程之后在配置主页面System Core/RCC中配置HSE/LSE晶振,在System Core/SYS中配置Debug模式,具体如下图所示
详细工程建立内容读者可以阅读“STM32CubeMX教程1 工程建立”
3.1.1、时钟树配置
系统时钟使用8MHz外部高速时钟HSE,HCLK、PCLK1和PCLK2均设置为STM32F407能达到的最高时钟频率,具体如下图所示
3.1.2、外设参数配置
本实验需要需要初始化USART1作为输出信息渠道,具体配置步骤请阅读“STM32CubeMX教程9 USART/UART 异步通信”
设置TIM3通用定时器溢出时间100ms,外部触发事件选择更新事件,参数详解请阅读“STM32CubeMX教程6 TIM 通用定时器 - 生成PWM波”实验,具体配置如下图所示
在Pinout & Configuration页面左边功能分类栏目Analog中单击其中ADC1,勾选IN5/6/7三个通道,在下方的参数设置中以ADC - 单通道转换实验为模板修改部分参数
Scan Conversion Mode :使能扫描转换模式,因此现在需要转换5/6/7三个通道,因此使能该模式之后,在规则通道转换为其中一个通道后就会接收转换下一个通道
DMA Continuous Requests :使能DMA连续转换请求,该参数的使能需要在配置完DMA请求之后才可选,配合参数 Scan Conversion Mode 可以实现连续不间断的对三个通道数据进行采集
End Of Conversion Selection :选择EOC flag at the end of all conversions,该参数表示当转换完毕一组ADC中的所有通道之后再产生EOC标志,进入中断
Number Of Conversion :规则通道转换数量现在为3,对应三个不同的通道,通道转换顺序及每个通道的采样时间由Rank及其下参数决定
具体参数配置如下图所示
单击Configuration中的DMA Settings选项卡对ADC1的DMA请求进行设置,单击ADD按键增加DMA请求,这里可选的只有一个ADC1
选择想要使用的DMA Stream,并设置优先级,将DMA请求模式设置为循环模式,外设地址不增加,内存地址递增,数据宽度选择字Word
为何如此配置?
请阅读本实验“3.0、前提知识”
如下图所示为ADC1的DMA请求具体设置
3.1.3、外设中断配置
在Pinout & Configuration页面左边System Core/NVIC中勾选DMA2 Stream0 全局中断,然后选择合适的中断优先级即可
注意这里没有勾选ADC1/2/3的全局中断,因为外设DMA中断使用的回调函数和外设本身中断的回调函数一般是同一个回调函数(为什么?请阅读本实验3.2.2小节),如果同时开始两者中断可能会导致重复进入中断函数
但是有些外设使用DMA时必须开启自身的中断,不同外设情况不一样
建议在外设使用DMA时,尽量不开启外设全局中断,必须开启的可以禁用外设主要事件源产生的硬件中断 (注释1)
上述步骤如下图所示
3.2、生成代码
3.2.0、配置Project Manager页面
单击进入Project Manager页面,在左边Project分栏中修改工程名称、工程目录和工具链,然后在Code Generator中勾选“Gnerate peripheral initialization as a pair of 'c/h' files per peripheral”,最后单击页面右上角GENERATE CODE生成工程,具体如下图所示
详细Project Manager配置内容读者可以阅读“STM32CubeMX教程1 工程建立”实验3.4.3小节
3.2.1、外设初始化调用流程
首先在生成的工程主函数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参数配置并进行了初始化,最后调用了__HAL_LINKDMA(adcHandle,DMA_Handle,hdma_adc1)将adc1外设与DMA流对象关联
具体的ADC DMA初始化调用流程如下图所示
3.2.2、外设中断调用流程
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中断回调函数指针
3.2.3、添加其他必要代码
在主函数中以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");
}
4、常用函数
/*以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)
5、烧录验证
烧录程序,单片机上电之后,串口不断的输出三个通道的ADC采集值,笔者将三个滑动变阻器按照通道5、通道6和通道7的顺序,分别从一端缓慢拧到另一端,串口数据如下图所示,通道5/6/7三个通道采集到的ADC数据从最大4095慢慢变到最小值0
6、注释详解
注释1:详细内容请阅读STM32Cube高效开发教程(基础篇)14.5.1小节内容