浅浅的撅一下这个TIMER(一):定时器基本操作

浅浅的撅一下这个TIMER(一):定时器基本操作

说到MCU的定时器,可谓是一个超级多面手:51里用它发生串口波特率时钟,精确延时靠定时器,各种小众协议可以靠定时器做出精准的时序,甚至可以用它控制电机……
如此强大的定时器,你真的不来学一下?

本文以GD32E230所配的TIMER13(通用L2)为例。
(因为它配置最寒碜(逃))

定时器基本结构

GD32E23x的通用L2定时器结构 来自GD32E23x_User_Manual_Rev1.5_CN.pdf
看到这张图慌了吧?云里雾里的。
没关系,今天我们只搞点基本操作,因此这个图可以砍成这样:
被砍完了的定时器结构
图中的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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// TIMER13中断回调函数
void TIMER13_IRQHandler(void)
{
// 检查是否是向上计数溢出引起的中断
if(SET == timer_interrupt_flag_get(TIMER13, TIMER_INT_FLAG_UP)){
// 清除中断标志
timer_interrupt_flag_clear(TIMER13, TIMER_INT_FLAG_UP);
// TODO: 在这里添加需要定时运行的语句
;
}
}

int main(){
// 建立配置用结构体
timer_oc_parameter_struct timer_ocinitpara;
timer_parameter_struct timer_initpara;
// 开启TIMER13时钟
rcu_periph_clock_enable(RCU_TIMER13);
// 清除TIMER13配置
timer_deinit(TIMER13);
// 初始化配置用结构体
timer_struct_para_init(&timer_initpara);
// 开始配置
// 以下配置基于SYSCLK = 72MHz情况。
timer_initpara.prescaler = 7199; // 预分频7200,得到PSC_CLK=0.01MHz,即10kHz
timer_initpara.alignedmode = TIMER_COUNTER_EDGE; // 边缘计数模式,单纯定时常用此模式
timer_initpara.counterdirection = TIMER_COUNTER_UP; // 向上计数模式
timer_initpara.period = 9999; // 自动重载计数器设为9999,即每计数10000次产生一次向上溢出。10kHz计数10000次,即向上溢出事件发生的周期是1Hz
timer_initpara.clockdivision = TIMER_CKDIV_DIV1; // 时钟为1倍输入时钟
// 初始化TIMER13
timer_init(TIMER13, &timer_initpara);
// 提前清理向上计数溢出标志
timer_interrupt_flag_clear(TIMER13, TIMER_INT_FLAG_UP);
// 启用向上计数溢出中断
timer_interrupt_enable(TIMER13, TIMER_INT_UP);
// 启动TIMER13
timer_enable(TIMER13);
// 允许TIMER13发起中断,最高优先级(0级)
nvic_irq_enable(TIMER13_IRQn, 0);

// 什么都不再做,主循环中死等
while(1){;}
return 0;
}

浅浅的撅一下这个TIMER(一):定时器基本操作

https://wuxiproj.mzy7.cn/posts/334d6621.html

作者

MZY7

发布于

2023-02-02

更新于

2024-12-07

许可协议

CC BY-NC-SA 4.0

Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×