STM32双定时器+ADC+DMA实战案例

科技   2024-06-30 17:03   湖北  


本文源于某STM32用户的问题咨询,稍加改动整理而成。

扫描关注一起学嵌入式,一起学习,一起成长

内容可能稍多,涉及STM32定时器同步应用、定时器触发事件与ADC的关联、基于ADC事件的中断处理、基于定时器事件的DMA传输,最终实现还需关注颇多细节以及基于细节的灵活处理。


涉及的外设及功能大致框图如下:



现有人使用STM32G474芯片从事产品开发。用到TIM1高级定时器,该定时器的4个通道CH1/CH2/CH3/CH4用作相关应用的PWM输出驱动


下图是STM32G4系列高级定时器功能框图的输出部分。



其中,CH1/CH2/CH3/CH4的输出可以经GPIO实现对外驱动,而CH5/CH6只是在内部产生了相应事件和驱动信号,但并不能对外做实际输出。


CH5/CH6的输出跟CH1/CH2/CH3/CH4相比,除了不能做实际输出外,有关定时器通道的输出特性是差不多的。


下面的示例就会用到CH5OC信号及特性。


用户设置TIM1工作在中心对齐计数模式。RCR=3,即每4次溢出【上溢或下溢】产生1次定时器更新事件【下面图中闪电标志表示更新事件发生点】。



现在有个需求就是要在计数器向上计数段的适当时间点触发ADC。该时间点会根据转换结果动态调整,并希望调整后的采样点在下一个计数周期的上坡段能够生效。


使用什么事件来确定ADC的采样点呢?因为CH1/CH2/CH3/CH4要用作其它PWM输出控制,不太方便抽取出来单独改来改去。


这时候我们想到TIM1CH5/CH6OC5/OC6信号及事件,他俩不做具体PWM输出驱动,用其中之一来做ADC触发事件是个不错的选择。


下面就以CH5OC5信号及事件为例来介绍。


现在要通过设置CCR5以不同的值来决定ADC的采样点,同时根据ADC的结果反过来调整CCR5的值进而调整ADC的采样点。现在面临一个CCR5的生效时间点的问题。


我们知道CCR寄存器都是可以开启预装功能的。如果开启CCR5的预装功能,因为RCR=3,就没法保证每次修改CCR5后都能在下一个计数周期的上坡段生效。


如果关闭CCR5的预装功能,即改即生效,也有隐患。有可能在同一个上坡段触发两次甚至多次ADC,显然不合适,会给ADC结果的处理及判断带来混乱。



比如结合上图,如果第一次触发ADC是在A点,ADC完成后调整CCR5的值,如果调整后的值比刚才的小倒还好,如果调整的值比刚才的大,就很可能在同一坡段上方又触发一次ADC


这样看来,感觉对CCR5寄存器不论开不开预装都不太好用。怎么办呢?


这时,我们可以考虑引入另外一个辅助的TIMER,让它与TIM1同频并让二者同步启动。


这里让它工作在向上计数模式,并基于某个通道做PWM输出【其实有无实际输出无所谓】,让它的比较事件发生点跟TIM1的中心对齐点总保持同步。见下图示意。



具体到这里,选择TIM3做辅助定时器,设置跟TIM1完全一样的计数周期。


选择其CH1做PWM输出,占空比固定50%,让TIM3每个周期的比较事件点与TIM1每个周期的中心对称点始终同步于同一位置。


如下图示意【蓝色PWM波形为TIM1-CH1基于中心对齐计数、PWM2模式、极性选择高有效时的输出,黄色PWM波形为TIM3基于向上计数模式、PWM1模式、极性选择高有效的输出。


红色三角波为TIM1计数器值的实时显示。】:



我们将CCR5寄存器的预装功能关闭,在ADC中断里需要调整CCR5时先写指定的内存缓冲,然后启动基于TIM3比较事件的DMA传输,让DMA将存放在内存缓冲的数据写到CCR5寄存器,保证在下一个计数周期可以生效。这样既能避免在同一个上坡段发生多次ADC触发、也能避免因RCR=3导致修改CCR5可能没法及时生效的问题。


谈到这里,基本的方向性、功能性问题解决了。下面就该看看具体实现了,说实话,还有一段路要走。


TIM1TIM3要做同步启动,建立主从关系。这点需结合STM32G4系列的参考手册,查看TIMER间信号的内联关系。见下图:



我让TIM3工作在Trigger mode 从模式,TIM1的计数器使能信号作为其触发输出,该输出跟TIM3ITR0相连,作为TIM3的计数器启动信号。


下面是使用CubeMx针对TIM1的关键配置:【注:下面有关TIMER的分频参数都很大,主要是为了调试、观察结果的需要。具有应用时请根据实际需求设置分频系数。】



下面是使用CubeMx针对TIM3的配置:



从上面TIM1/TIM3的时基参数配置不难看出,二者计数周期一样,都是2000个计数时钟单位【注:中心对齐计数模式,一个周期分为向上、向下计数两部分】。


前面说过了,CCR5寄存器的修改将基于TIM3的比较事件通过DMA来完成。所以我们还得做基于TIM3-CH1比较事件的DMA配置。



这里选择ADC1ch1作为验证测试通道。使用TIM1的OC5事件或信号作ADC的外部触发源。如何将OC5事件或信号跟ADC的触发关联起来呢?


因为ADC1可以选择TIM1TRGO 2做触发启动信号,我们可以选择OC5REF信号作为TIM1的第2路触发输出,用它来触发ADC


同时,ADC对源于OC5Ref的信号还可以做触发极性选择,这就涉及到TIM1-CH5PWM配置。对TIM1-CH5PWM输出配置是这样的【注意CCR5的预装功能关闭了】:



基于上面配置,那么OC5REF信号就应该是下面的样子:



因为我们要求在TIM1计数的上坡段触发ADC,此时我们就选择OC5Ref信号下降沿作为ADC外部触发沿。最后有关ADC的触发配置如下:



另外,我启用了ADC的EOC中断功能,在ADC中断里读取结果并修改CCR5的缓冲值,然后启动基于TIM3比较事件的DMA传输,以修改CCR5寄存器的值,实现ADC采样点的动态调整。


我在ADC中断里依次针对CCR5做了3个不同数据的轮流修改,以观察效果。这3个值分别是400,600,800


下面截图是ADC转换完成中断处理函数的部分参考代码。



下面截图是基于上面配置创建工程,添加用户代码,调试后的输出结果:



蓝色波形是TIM1-CHI1pwm输出,黄色波形是TIM3-CHI1pwm输出,其占空比固定为50%


绿色波形是我在每次ADC中断里通过GPIO输出的尖峰脉冲序列。


我们不难看出,绿色尖峰离TIM1中心对齐线的距离呈现有规律的周期性远近变化,这跟我在ADC中断里周期性地修改TIM1-CCR5的值是对应的,而且ADC的触发及转换都发生在TIM1计数的上坡段。


介绍到这里,STM32用户要实现的功能就算结束了,该实现的功能也基本可以实现。当然,上面的实现方法并非一成不变,还可以有其它灵活调整的方案。


最后,我还是想针对前面2个主从定时器同频同步启动做个重要提醒。在前面我设置TIM1TIM3ARR时间参数时二者的计数周期是一样的,以确保TIM3的比较事件点跟TIM1的中心对齐线总处于同一位置,即始终保持下面图示的样子。



否则,这两个点的距离会不停的漂移变动,这样的话,我们ADC操作的时间点也会乱套。导致这个问题的一个常见原因就是TIM1TIM3设置的计时周期并不完全一样。


具体到这里,TIM1采样中心对齐模式,如果ARR=1000,它的一个周期的计数长度就是2000个计数时钟。


TIM3要跟它设置相同的计数周期,因它采用的向上单向计数模式,此时它的ARR应设置为2000-1,如果设置成了2000就会出现漂移问题。


下面两幅图就是TIM1->ARR=1000,TIM3->ARR=2000,其它参数完全同前,TIM1-CH1TIM3-CH1在两个不同时刻的输出情形。



从上面波形不难看出,TIM1的中心对称线与TIM3的比较事件点的距离在不停变化,显然这个情况对于当前应用就很糟糕,理由不言而喻了


所以,我们在设置这两个同频定时器的参数时,ARR值不要搞错了,多一个少一个都不行,时间久了就能感觉到两个特定事件在时间上明显漂移了。


有人对这个地方似乎一下脑补不过来。打个比方,2个工人铺地砖,都以铺黄色砖为主。某时刻同时开始铺,平行前进,铺砖速度也一样。


但是A工人像下图示意一样,每4块砖放入1块绿色砖,B工人每5块砖放入1块红色砖。这样下来,我们会看到相邻红绿色两块砖的间距总在不停变化,即二者的间距不是固定的。


如果说A/B两工人铺法相同且固定,红绿砖的间距自然保持不变。



谈到这里,就再补充几句。


前面我是将两个TIMER设置相同的计数周期并同步启动。其实,如果TIM3的周期能刚好设置成TIM1的一半并做同步启动也是可以的。这时我们可以基于TIM3的更新事件来修改CCR5的值。


另外,上面我是把TIM3的比较事件点跟TIM1的中心计数点设置在同一位置,这也不是必须或说固定的。


我们可以根据实际情况,将辅助定时器TIM3的比较事件点适当往后挪些也是可能的,但比较事件点在每个周期内相对中心对称线的位置保持固定不变是必须的。


整体上,上面案例的实现需要添加的用户代码不多,但对相关外设工作特性及细节的把握要求比较高


比方引入合适的辅助定时器,合理选择TRGO信号,合理配置PWM信号以匹配ADC触发点及极性选择、巧用DMA功能等。



关注【一起学嵌入式】,回复加群进技术交流群。



觉得文章不错,点击“分享”、“”、“在看” 呗!

一起学嵌入式
结伴而行,RTOS、Linux编程、C/C++,以及经验分享、行业资讯、物联网等技术知识。一起学习,一起成长
 最新文章