浅浅的撅一下这个TIMER(一):定时器基本操作
说到MCU的定时器,可谓是一个超级多面手:51里用它发生串口波特率时钟,精确延时靠定时器,各种小众协议可以靠定时器做出精准的时序,甚至可以用它控制电机……
如此强大的定时器,你真的不来学一下?
本文以GD32E230所配的TIMER13(通用L2)为例。
(因为它配置最寒碜(逃))
定时器基本结构
看到这张图慌了吧?云里雾里的。
没关系,今天我们只搞点基本操作,因此这个图可以砍成这样:
图中的TIMERx_CHxCV
被划掉了,是由于在今天的介绍中,在这个位置的并不是它,而应该是“自动重载计数器的值”。
定时器具有一个内部时钟输入CK_TIMER
(来自RCU(复位与时钟单元),一般与系统主时钟一致),其可以直接连接到TIMER_CK
作为定时器的主时钟。TIMER_CK
可以被进行预分频(即将输入频率进行除以n的操作产生一个更低的时钟信号,对于这里所用的通用L2定时器来说n的取值范围是1≤n≤65536,但是对应的预分频寄存器PSC
的值范围是0≤PSC
≤65535)得到PSC_CLK
(PSC指的是“Prescaler”,预分频)。
在自动重载计数器中有一个寄存器称为TIMERx_CAR
(AR指的是“Auto Reload”,自动重载),对于通用L2定时器,该计数器值的上限为65535(0xFFFF)。
定时器的计数模式运行
在计数模式下,定时器启动后PSC_CLK
信号被直接送入计数器,在最常用的向上计数模式中,每次时钟脉冲会令计数器的值增加1。计数器的值初始状态下为0,对于通用L2定时器,该计数器值的上限为65535(0xFFFF)。
在向上计数模式中,TIMERx_CAR
的值会被与计数器的值进行比较,一旦计数器的值达到TIMERx_CAR
的值,定时器就会将计数器的值重置为0,并产生一个溢出事件(这并不是说计数值越过了值的上限,而是指“相比TIMERx_CAR
的值,计数器的值溢出了”)。这个事件可以被设置为产生更新(Update)中断,送往系统。
而此时若没有关闭定时器,定时器将会重复这样的“计数-溢出-归零、发事件”的过程,从而产生周期性的中断。
定时器的计数模式通俗解释
CK_TIMER
是一个恒定频率响起的声音信号【输入时钟】;- 你手里拿着一支笔一个草稿本,每听到(
PSC
+1)声信号后【预分频】会在纸上划一笔【计数】; - 画完后你会检查一下草稿本上的笔画数是否等于(
TIMERx_CAR
+1)画;- 如果不够(
TIMERx_CAR
+1)画就继续准备听下一个信号。 - 如果笔画数等于(
TIMERx_CAR
+1)画【溢出发生】,就撕掉草稿纸的当前页,新的一页上一画也没有【计数器置0】。然后你吼了一声你旁边的人【触发更新中断】。
- 如果不够(
我看别的资料里提到一个东西叫“影子寄存器”?那是啥?
如果你学过数电,你会发现这就是一个锁存器。
以自动重载值寄存器TIMERx_CAR
为例,其运行原理如下:
即相当于“TIMERx_CAR[影子寄存器]
”的值被锁存,直到发生溢出事件后才更新。
对于通用L2定时器,预分频寄存器PSC
的影子寄存器是被启用的,而自动重载值寄存器TIMERx_CAR
的影子寄存器可以根据需要是否启用。
该功能常用于多路PWM等需要保证实际用于比较的值被同时刷新的情况。
示例代码
以下代码整理自“GD32E23x_Firmware_Library_V1.1.4/Examples/TIMER/TIMER2_timebase”。
TODO处的代码会以1Hz的频率被执行。
1 | // TIMER13中断回调函数 |
浅浅的撅一下这个TIMER(一):定时器基本操作