这是一个能让你解放双手的ADC采样配置。因为你只需要在程序开始的时候配置好定时器、DMA、ADC,剩下的,就等着采集完成后在中断中处理AD相关数据就行了。 前面也写过STM32的基于DMA的AD转换功能,但不够完善。
STM32的ADC采样功能一直很强大,它又引入了DMA,同时定时器还能做为ADC开始转换的触发源,不得不说,真的好使!当然了,其它家的cotex-m核的MCU也有类似功能,这里只是以ST的芯片为例,做为演示。
本文实现的AD功能为:通过定时器每隔一段时间,自动触发ADC转换,并通过DMA自动把转换后的值存入某变量或数组,存储完成后触发中断。接下来说一下具体的配置和代码,以及调试时的注意事项。
一、创建工程
使用STM32CubeMX创建工程,芯片选择STM32G030F6Px。创建后,使能下载调试接口:SW。
二、时钟树配置
STM32G030系列,在使用内部RC做为时钟源的情况下,最高可以运行在64MHZ。这里,我们选择内部RC做为时钟源,默认16MHZ,然后使用PLL倍频使其达到64MHZ。
三、配置定时器3
如图所示,这里对定时器的时钟源(我们要用64MHZ)做了640分频,分频后频率为100KHZ。计数周期2000,在100KHZ频率下对应20ms。下面的:
Triger Event Selection TRGO :Update Event
表示,每次TIMER3计数溢出更新时,做为一个触发事件。
注意:这里虽然用到了定时器中断,但是TIM3中的NVIC Settings中的中断可以使能,也可以不使能。个人测试,都能实现效果。
四、ADC采样配置
4.1 DMA配置
这里使用ADC1的通道0来采集数据,使能通道0后,先把DMA功能打开。模式为循环,数据宽度为半字:Half Word。其它的,没什么要改的。如下图所示:
4.2 ADC_Settings
接着ADC_Settings相关的参数,如下图:
Clock Prescaler:时钟分频,时钟频率越高,采样速度越快,越费电,精准度也会略微低一点;
Resolution:分辨率,支持6位、8位、10位、12位可选,这个功能不错,不过我默认习惯用12位。
Data Alignment:数据对其,正常人习惯都是右对齐,即Right alignment。
Sequencer:有两个选项,Sequencer set to fully configurable 和Sequencer set to not fully configurable。单通道的时候不用管,多通道的时候我再说。
Scan Convertion Mode:多通道采样的时候会用到,单通道的时候默认即可。
Continuous Convertion Mode:连续转换模式,开启后,AD转换功能一直运行。这不是我们需要的,我们需要它在特定的时刻转换,且每次只转换一次。
Discontinuouse Convertion Mode:非连续转换模式,又叫做间断模式。单通道时不用管,多通道时再考虑。
DMA Continuous Requests:这个参数是用来配置ADC与DMA之间的数据传输行为,这里设置为使能。第四步一开始先打开DMA,就是因为DMA使能的情况下,这个参数才能设置为使能。
这个参数使能后,DMA会不停地请求ADC转换数据。
后面的参数用不到,暂时不用管。
4.3 ADC_Regular_ConversionMode
然后是ADC_Regular_ConversionMode相关参数的设定。
SamplingTime Common 1和SamplingTime Common 2:这里是为不同的组别设置相应的采样时间,我们只有一个通道,所以只关注SamplingTime Common 1即可。
时间这里选择79.5 Cycles,即一次AD转换需要的周期。周期越大,时间越久,但是会越准确;反之,会更快,但不精准。
Number Of Conversion:转换通道的数量,这里是单通道,所以写1即可。
External Trigger Conversion Source:选择外部触发源。即,你想让什么事件来启动AD转换。这里选择Timer3 Trigger Out event,即步骤3中设置的Updata Event。
Exteranl Trigger Conversion Edge和Trigger Frequency,暂时不用管。
Rank:如果是多通道,这里会有多个rank。但是这里是单通道,所以只有一个。下面选择通道和希望的转换时间。
五、配置GPIO
设置PC15和PA3为输出模式,依次命名为LED和PA3。
这两个管脚做什么用? 后面就知道了。
六、生成工程
基本配置都完成了,进入Project Manger页面。
输入工程名称,选择保存路径,IDE工具,版本等,然后点击代码生成按钮。
七、完善代码
7.1 变量定义
添加部分变量定义或声明:
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
extern TIM_HandleTypeDef htim3;
extern ADC_HandleTypeDef hadc1;
uint16_t dat_buf[3] = {0};
uint8_t con_cplt_flag = 0;
/* USER CODE END PM */
7.2 AD转换校准与启动
依次进行校准、定时器启动、AD转换启动,代码如下:
代码:
/* USER CODE BEGIN 2 */
HAL_ADCEx_Calibration_Start(&hadc1);
HAL_TIM_Base_Start_IT(&htim3);
HAL_ADC_Start_DMA(&hadc1,(uint32_t *)dat_buf,3);
/* USER CODE END 2 */
7.3 AD转换完成中断回调函数
AD转换完成,并通过DMA存入数组之后,会触发一个中断,在这个中断中,我们取反一个GPIO,通过示波器观测IO翻转的周期,可以确认AD转换是按照定时器定的时间去触发并转换的。
否则,我们无法明确确认这个转换间隔到底是多久。
代码如下:
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
con_cplt_flag = 1;
HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
}
7.4 定时器中断功能验证(可选)
前面提到过,TIM3做为AD转换的触发源后,自身的中断使能可以打开,也可以不打开。无论哪种,AD定时触发的功能都是可以正常运行的。
不过有个问题,定时器还能用过普通定时器产生定时中断吗?
答案是可以,但是ST-LINK仿真你是仿不出来的。
经测试,我同时编写了TIM3定时中断回调函数和AD转换完成中断回调函数。使用ST-LINK仿真观测,两个中断回调函数中打断点,TIM3定时中断回调函数仅在复位后执行一次或两次。之后就再也进不去了。
去掉AD转换完成中断回调函数中的断点,只保留TIM3的断点,就又能进去了。所以,情况有点诡异~~~
因此,这种情况下,我不再依靠断点。而是通过在TIM3定时中断回调函数中取反GPIO的方式来判断它是否进入中断,代码如下:
代码:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
HAL_GPIO_TogglePin(PA3_GPIO_Port,PA3_Pin);
}
八、程序下载,观测波形
程序完善后,保存、编译、下载。先看数组中是否采到数据,如下:
有数据,OK。
然后是AD转换完成中断回调函数,是多久执行一次,通过管脚LED的翻转周期,可以判断,如下:
翻转周期60ms。
咦?不应该是20ms吗?
是20ms,但那个是单次的时间间隔。AD转换时,我们定义了一个3元素的数组,所以要转换3次,也就是60ms了。
HAL_ADC_Start_DMA(&hadc1,(uint32_t *)dat_buf,3);
接下来是TIM3定时中断,观测PA3管脚的波形,如下:
可以看到翻转周期是20ms,和TIM3中的配置是一致的。
至此,单通道ADC转换定时器自动触发+DMA传输,实现方式记录完成。
有需要源码的,关注公众号:单片机爱好者,回复关键词:026,即可获取下载链接。
打完收工!