首页 关于
树枝想去撕裂天空 / 却只戳了几个微小的窟窿 / 它透出天外的光亮 / 人们把它叫做月亮和星星
目录

计时器之时基单元

在写单片机控制时,除了与各种各样的外设通信外,计时器应该是打交道最频繁的片上外设了。尤其是在控制系统中, 我们往往会有周期地执行某些特定函数完成一项任务的需求,而这种需求通常都是通过计时器来满足的。

时基单元是计时器工作的基础,它就是一个计数器,在每个计数周期到来时,都会加一或者减一(具体计数方向可以配置)。 当计数器的值达到了某个阈值时,就会发生计时溢出,从而产生计时中断。我们通过控制计数周期、计数方向、计数阈值就可以获得我们想要的控制周期, 进而达到定时控制系统的目的。

在本文中,我们介绍时基单元的结构、计数模式,最后利用时基单元的计数溢出中断,控制LED灯闪烁。

1. 时基单元的结构

如下图1所示,时基单元只有三个部分:计数器(Counter)、预分频器(Prescaler)、和自动重载寄存器(Auto-Reload Register), 我们可以在任何时间任何状态下对这三个部分进行读写操作。

其中预分频器和自动重载寄存器是存在缓存的,也就是说物理上它们有两个寄存器: 预装寄存器(preload register)和影子寄存器(shadow register)。我们对这两个部分的读写操作都是针对预装寄存器的访问, 而实际对时基单元起控制作用的是其背后的影子寄存器。我们可以在修改预装寄存器的时候就修改其影子寄存器,也可以在每个更新事件(Update Event, UEV) 到来时更新影子寄存器。控制寄存器CR1的ARPE位的配置决定了自动重载寄存器的影子寄存器的更新时刻,而预分频器的影子寄存器只能在更新事件时更新。

图1 时基单元结构框图

预分频器的工作就是对其输入时钟CK_PSC进行分频,产生CK_CNT时钟用于驱动计时器计数。计数器在CK_CNT的驱动下向上或者向下计数, 具体由控制寄存器CR1的DIR位决定。自动重载寄存器定义了计数器只能在0与该寄存器的值ARR之间计数。 每当计数器向上计数到达ARR,或者向下计数到达0时,就会产生一次UEV事件,如果开启了中断,将相应的产生中断信号。

2. 计数模式

STM32的计数器一共有三种计数模式:向上计数、向下计数和中间对齐(Center-Aligned)。

下图2(a)和(b)中分别描述的是向上计数和向下计数时的时序图。在这两个图中,自动重载寄存器中的值都是36。 从图2(a)中,我们可以看到向上计数时,当计数器的值达到了ARR,就会产生一个更新事件(UEV),置更新中断标识为'1'。与此同时,计数器将从0开始重新计数。 类似的,向下计数到0的时候,就会产生更新事件,置位中断标识,同时重新载入ARR的值重新计数。

图2(a) 向上计数时序图 图2(b) 向下计数时序图

下图3(a)中描述的是中间对齐计数模式的时序图。与向上和向下计数不同的是,中间对齐计数模式先从0开始向上计数,到ARR-1时触发一次更新事件; 然后从ARR开始向下计数,到达1时再次触发一次更新事件,接着从0开始重新计数。整个过程就像图3(b)中所示,计数阶梯图就是一个中间有一个峰值的等腰三角形。

实际上向上和向下计数又被称为边沿对齐(Edge-Aligned)计数模式,它们的计数阶梯图如下图3(c)(d)所示。计数对齐模式可以通过控制寄存器CR1的CMS位控制。 当CMS为0时,为边沿对齐模式,此时通过CR1的DIR位我们可以控制时基单元向上或者向下计数。当CMS位不为0时,为中间对齐模式,此时我们对DIR的修改将不再有效, 但DIR的值将反应当前的计数方向。

图3(a) 中间对齐时序图 图3(b) 中间对齐阶梯图
图3(c) 向上计数阶梯图 图3(d) 向下计数阶梯图

3. 预分频器和驱动时钟源

在图1中,我们提到预分频器对其输入时钟CK_PSC进行分频后产生的时钟信号CK_CNT用于驱动计数器计数。下图4中描述了一次修改PSC寄存器前后的时序图。

从图4中,我们可以看到预分频器实际上至少有三个寄存器。其中预装寄存器是我们唯一可以读写的寄存器,而影子寄存器和计数器则直接控制了时基单元。 我们通过TIMx_PSC修改了预装寄存器中的值,但该值并不会立即写入影子寄存器中,此时时基单元的计数频率并不会改变。 只有发生了计数溢出触发了UEV的时候,才会写入到影子寄存器,变更时基单元的计数频率。

图4 预分频器时序图

CK_PSC有四种时钟来源:

  1. 所在外设总线的APB时钟。计时器作为一种片上外设一定挂载APB1或者APB2总线上。当寄存器SMCR的SMS位为000b时,计时器就会用该时钟源驱动预分频器。 该时钟源能够满足输出特定频率的方波信号,或者用于触发周期任务等一般常见的功能需求。
  2. 时钟通道引脚。在STM32中的计时器一般都有4个通道,我们配置以某个通道上信号的上升沿或者下降沿触发计时器计数。SMS=111b时,将工作在这种模式下。 我们将在输入捕获一文中对该时钟源进行更详细介绍。
  3. 外部触发信号(ETR)。这种时钟源只对少数几个计时器有效。通过SMCR的ECE控制开启这种时钟源。由于很少用到这个功能,所以暂时先不讲了。
  4. 其它计时器的触发信号。STM32的计时器可以工作在主从模式下,在该模式下一个计时器将作为另一个计时器的预分频器。这样可以扩展计时器的位宽, 也就是说我们可以用两个16位的计时器得到一个32位计时器的效果。详细将在主从模式中介绍。

4. 通过计时中断控制LED灯

对时基单元的控制很简单,如下左边的代码中定义了一个函数tim3_init()用于初始化TIM3。在函数中我们只是定义了计数的方向、预分频器以及自动装载寄存器的值。 这两个寄存器的值可以通过参数pres和period进行配置,这样我们就可以控制中断产生的频率了。第8行到第10行的代码, 分别控制重置计数器、开启更新中断,使能计数。

右边的代码是TIM3的中断服务函数,在该函数中我们控制LED0不断的闪烁,最后写状态寄存器的UIF位清除计时更新信号,防止重复进入中断服务函数中。 完整的代码可以在这里找到。

        void tim3_init(uint16 pres, uint16 period) {
            RCC->APB1ENR.bits.tim3 = 1;

            TIM3->CR1.bits.DIR = TIM_COUNT_DIR_UP;
            TIM3->PSC = pres;
            TIM3->ARR = period;
        
            TIM3->EGR.bits.UG = 1;
            TIM3->DIER.bits.UIE = 1;
            TIM3->CR1.bits.CEN = 1;
        }
        void TIM3_IRQHandler(void) {
            if (1 == TIM3->SR.bits.UIF) {
                LED_0 = ~LED_0;
            }
            TIM3->SR.bits.UIF = 0;
        }

5. 总结

时基单元为计时器提供了基础的计数功能,它由预分频器、计数器和自动装载寄存器三个部分组成。修改分频系数和自动装载值可以调整计数溢出频率, 它们都有一个预装寄存器和一个影子寄存器,我们对他们的读写访问实际上是对预装寄存器和读写,而影子寄存器则是实际控制时基单元的, 在计时溢出触发更新事件时,就会把预装寄存器的值写到影子寄存器中。

时基单元有三种计时模式:向上计数、向下计数和中间对齐计数。此外还有四种时钟源,用于驱动预分频器进而驱动计数器计数。 一般使用外设总线上的时钟,就可以完成很多定时控制的工作。关于外部信号和计时器的主从工作模式,我们将在后续的章节中予以详细的介绍




Copyright @ 高乙超. All Rights Reserved. 京ICP备16033081号-1