我们知道在STM32 MCU片内的常规定时器【基本定时器、通用定时器、高级定时器】里面,有个ARR寄存器,它负责配置定时器的计数时钟个数,进而决定每个计数周期的时长。【下图是计数器三种计数方式及溢出点的示意图,绿色圆圈表示溢出位置。】
对于基本定时器和通用定时器,每次溢出时都可以产生更新事件,该事件可以用来触发中断、触发DMA请求、触发其它外设【ADC/DAC等】或与其它定时器级联等。显然,这个更新事件发生频率跟ARR值直接相关,对于单向【边沿对齐】计数模式,更新事件发生频率跟计数频率一致,对于双向【中心对齐】计数模式,更新事件发生的频率是计数频率的2倍。如果希望更新事件发生的频率快点或慢点,只需相应调整计数时钟及分频值和ARR值即可。然而在高级定时器里,又引入了另外一个寄存器RCR,它针对计数器的溢出动作进行计数,每发生RCR+1次溢出就可以产生更新事件。它不再要求更新事件于每个计数周期都发生,让更新事件发生点更具灵活性。如果单从影响更新事件发生频率这个角度来看,它的引入似乎多余。我们调整ARR及时钟分频不照样可以达到目的吗?为何还要来个RCR呢?
的确,单从计数定时这个角度来看,引入这个对溢出次数进行统计的寄存器意义并不大,正因为如此,也就不难理解基本定时器和通用定时器就没有这个RCR寄存器及相关部件。
不过,STM32的通用定时器和高级定时器功能非常强大,尤其高级定时器,它并不总是只做些计数定时。很多时候需要定时器既要输出驱动脉冲,同时还要实现对外、对内的定时触发,驱动脉冲的频率和定时触发的频率往往可能并不一致。比方像下面应用情形,应用上需要每3次溢出才产生一次更新事件去触发别的外设。【这里计数模式为向上单向计数】
此时引入RCR寄存器及相关机制,就能兼顾驱动脉冲频率和定时触发频率的双重要求。当然,或许会有人说借助中断也可以完成。的确很多场合可以,但不能保证所有的场合都适用,毕竟利用RCR及相关硬件更加高效方便,尤其是有些对时序、实时性要求高的场合。顺便多说几句,ST后期推出的STM32系列在定时器输出比较功能方面增加很多新功能,比如非对称输出、组合输出等,单从功能实现上讲,这些功能使用之前的基本PWM、OC输出功能结合DMA以及中断大概率也能实现,但是,先不说实现过程的复杂性及难度,最终功能的实时性以及可靠性方面也未必能很好满足应用需求。另外,从开发角度讲,基于硬件支撑的新功能也大大降低了开发难度及工作量。
前面说过,定时器每经过RCR+1次溢出就可以产生一次更新事件。对于单向计数模式,拟定RCR后,更新事件点很好确认。但对于中心对齐双向计数模式,更新事件点的确认就稍显复杂些。关于中心对齐计数模式下,结合RCR如何确定更新事件点的话题,这里顺便多聊几句。
先说说对RCR写奇数值的情况。当给RCR赋奇数值时,其更新事件点固定发生在上溢点或下溢点,具体取决于你写进RCR影子寄存器的值的时间点相对于计数器启动时刻的先后顺序。
当写奇数值进RCR影子寄存器的时刻早于计数器启动时,更新事件固定在下溢点发生;当写奇数值到RCR影子寄存器的时刻晚于计数器启动时,更新事件固定在上溢点发生。【注:RCR寄存器是带预装功能的,其预装值到影子寄存器的拷贝必须借助更新事件。】
对于上面这段话,可能有人会有些许疑惑。【顺便提醒下,关于RCR赋值描述那部分,有些版本的STM32参考手册在这个地方应该存在笔误。】
我们不妨结合下图一起看看。定时器采用中心对齐计数模式,黄色箭头所指为计数器启动时刻。
对于写数据到RCR影子寄存器早于计数器启动,比较好理解。反正启动前我们将数据写进了RCR影子寄存器,计数器启动后按照给定的RCR值来产生更新事件。如果说写值到RCR影子寄存器晚于计数器的启动,那多晚算晚?或者说可以晚到什么时候?
说实在的,这里简单说“晚于”还是有些模糊、不严谨。
参考手册在描述这个地方时,应该是基于一个假定,或者说这里还有潜台词。当用户没有对RCR影子寄存器写进数据时,RCR影子寄存器总得有个数据,这里默认使用芯片复位后的初始值0。既然使用该默认值0,计数器启动后的第一次更新事件就发生在上图A1位置【即计数到ARR-1时刻】。另外,还有个默认条件就是计数器初始值也是0.
那么,作为用户写RCR预装寄存器最合理的时间段就是在启动到计数到ARR-1之前的这一段。然后借助在A1处发生的更新事件,RCR影子寄存器的值得到更新,即预装值拷贝进影子寄存器。之后,定时器使用新的RCR值。当然,你在别的合适的地方写RCR预装值也行,这要结合你要写的值和期望发生更新事件点的位置综合考虑。
聊到这里或许有人还会问,你怎么知道手册是基于那个默认假定呢?如果不是基于上面所说的假定,我们若在启动计数器后的任一时刻来写相同奇数值到RCR预装寄存器,根本没办法保证更新事件点固定在上溢点或下溢点发生。甚至还会有人说,我们是中途调整RCR,之前RCR影子寄存器的值并非默认值0,而是1或2什么的。这样的话,问题就更复杂了,客观地讲,这些情形也是存在的。但只要吃透了原理,做具体问题具体分析,终归还是可以将更新事件点确认下来。【个人理解手册上在描述给RCR赋奇数值时明显没有包含此类情形的。实际应用中如非必要,也不要把问题搞得太复杂。】
上面所说的,都是针对给RCR写奇数值而言。只有给RCR写奇数值才会让定时器在固定位置点产生更新事件,该位置点除了与具体值有关,还跟写进影子RCR相对于计数器启动的时间点有关。
上图就是RCR为奇数值发生更新事件的情况。有两排箭头分别代表两种互斥的情形。
参照前面的默认条件之说法,当RCR值在启动计数器前已经生效,更新事件点乃红色箭头所指;当RCR值在启动计数器后的前半个周期内被赋值,然后基于默认RCR=0在第一次上溢点发生更新事件让新RCR值生效,更新事件点乃非红色箭头所指位置。
如果说给RCR写偶数值的话就没有什么好探究的了,它的更新事件点是摇摆的,一次上溢点、一次下溢点,这样循环交替。下面截图是RCR=0和RCR=2的情形。竖直箭头所指为更新事件发生点。
基于RCR针对计数器溢出次数计数,更新事件点自由可调的特性,当把它跟定时器的单脉冲输出模式结合起来输出指定个数的脉冲时就非常方便,这里就不展开了,前面也有类似话题专门介绍过。
本来只打算简单聊聊为什么有ARR还要弄个RCR的,没想到围绕RCR延伸了不少文字。平常看手册时,这个地方泛泛看看或许很快飘过,真要细究起来似乎东西还不少。以上分享,愿对你有所帮助或启示。
*****************************************
往期话题阅读链接:
1、使用定时器输出指定个数脉冲的几种方式
2、单次事件触发DMA读取多字节SPI数据
3、STM32CubeIDE几个调试工具使用演示
4、基于STM32G4芯片的DAC应用示例
5、STM32 DMAMUX DMA同步事件的应用示例