1.3 TIM 定时器中断

定时器的具象化理解

定时器(Timer, TIM)

你正在写作业, 并且设定了25min 的番茄钟. 那么番茄钟🍅就会每 25min 叫你一下, 番茄钟就是一个定时器

这种定时完成任务的方式, 就利用了定时器(Timer, TIM)

但是在单片机中, 定时器的功能远比定期执行任务更多, 这里只以定时器中断这一功能作为切入点, 带你了解定时器以及它的相关参数, 如果想知道它的其他功能, 比如 PWM 波生成, 可以课外了解或者以后会讲


SysTick 和 TIM的区别

你可能记得, 我们在之前点灯的时候用到过了 HAL_Delay() 这个函数用来延时, 它的逻辑是进入这个函数之后让整个 CPU 死等, 直到结束, 这就是我们说的阻塞式等待, 它也会用到定时器, 叫做 SysTick

在这里我们必须分清楚系统定时器 SysTick外设定时器 TIM 有什么区别了Pasted image 20260622223312

最明显的区别是, 他们的位置不一样: SysTick 是内核定时器, 它处在内核中, TIM 是外设定时器, 他处在独立的外部设备中, 所以我们需要一个 GPIO 口来利用它


TIM

我们现在知道了中断是什么, 它可以打断当前运行的程序去处理中断程序, 处理完成之后会到原程序继续执行. 那我们现在把这个运用到定时器上面

HAL_Delay() 的不足

我们现在来说说HAL_Delay() 的问题

1
2
3
4
while (1) {
HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
HAL_Delay(500); // 等 500ms
}

他能做到等待 500ms , 但是他是阻塞的, 如果在等待 500 ms 的同时去干别的事, 我们就需要利用到定时器

也就是说, 我们需要一种办法: 让某个硬件自己去数时间,时间到了就”通知”CPU,平时不打扰 CPU。 这个硬件,就是定时器(Timer,简称 TIM)。而”时间到了通知 CPU”这件事,正是你刚学过的 – 中断

希望你能好好看看上面的这句话, 怎么把定时器和中断结合的? 定时器用来计数, 而中断是一个事件, 开启定时器是一个中断事件, 开启完成之后马上结束这个中断让外设自己数数, 数到对应时间之后, TIM 发一个中断告诉你到时间了, 然后你再处理这个中断


三种不同的定时器

类型 能力
基本定时器 最基础:预分频、自动装载、计数、产生溢出中断、触发 DMA
通用定时器 基本定时器的全部功能 + 4 个独立通道,可做输入捕获、输出比较、PWM、编码器接口
高级定时器 通用定时器的全部功能 + 互补/反相 PWM、刹车输入等(常用于电机控制)

这里我们讲的是结合中断的定时器的应用, 而这个做定时中断这件事是任何上述一类定时器都可以做到的基本功能. 所以本篇文章并不是注重讲定时器(即后文不会将不同类型的定时器具体是怎么工作的), 而是注重讲定时中断(即以一个基本定时器为基础, 讲解定时器的配置)

这里提到三种定时器只做了解作用


定时器的核心

时钟来源

定时器是一个外设, 他需要工作的话必须需要一个时钟才行, 也就是 TIM 的”心跳”(在1.1 中有提到)

这个时钟源就来自芯片时钟树上的 APB 总线 Pasted image 20260623001855

预分频(PSC - Prescaler)

现在时钟树上的 APB1 Timer clocks 将时钟信号传递给 TIM 外设了, 但 72MHz 太快了,定时器往往不需要数这么快。于是有了预分频器 PSC:它把输入时钟分频(除以一个数),得到一个慢一些的”计数时钟”。这个经过预分频后的时钟信号才是被真正利用的计数时钟信号

计数时钟 = 定时器输入时钟 ÷ (PSC + 1)

注意那个 +1:寄存器从 0 开始算,所以 PSC = 999 表示除以 1000

这个设置项在这个界面Pasted image 20260623003751

计数器(Counter,CNT)+ 计数模式

定时器里有个计数器 CNT,每来一个计数时钟节拍,它就变化 1:

  • 向上计数(Up):每个节拍 +1;
  • 向下计数(Down):每个节拍 −1;
  • 中央对齐(双向):先加到顶,再减回来

自动装载值(Auto Reload Register,ARR)

CNT 不会无限增长,它的”上限”由 ARR 决定。以最常用的向上计数为例:

CNT 从 0 一直加到 ARR,溢出,归零,再开始新一轮。

每次溢出,定时器就产生一个更新事件(Update Event) – 可以拿来触发中断

把三者连起来,就是这么一张锯齿图: CNT 像锯齿一样,从 0 斜着升到 ARR,啪地归零,重来;每个尖峰(溢出点)就是一次中断。斜率(数得多快)由 PSC 定, 峰值(数到多少)由 ARR 定, 倾斜方向(是向上倾向还是向下倾斜)由Counter mode 定

Pasted image 20260623004249

理解过程

现在我们来理一下整个过程:

TIM 从零开始数数, 既然是数数, 那么肯定有数的速度, 这个速度就由 PSC 决定, 原本 72MHz(72,000,000) 传进来, 经过 PSC, 比如我要除以7200 那么 PSC 就填 7199, 得到 10,000 Hz > PSC = 7199 → 72,000,000 ÷ 7200 = 10,000 Hz(每秒数 1 万个)

现在我以一秒数一百万个的速度前进着, 我说你要每一秒过后触发一次中断, 那么我就设置一个终点(ARR), 将 ARR 填成 9999 表示你数到一万 的时候就触发一次中断, 然后接着从 0 开始数. 而从 0 数到一万的时间, 根据我们的速度, 他就用了一秒钟 > ARR = 9999 → 数 10,000 个,正好 1 秒

公式

到了激动人心的公式环节

这个是频率, 知道了频率我们就知道时间:

套用上面的例子:

也就是说我们的定时器中断一秒一个!

CubeMX 配置

现在回头看 CubeMX 里 TIM 的参数(Parameter Settings),就全认识了:

配置项 含义 说明
Prescaler (PSC) 预分频值 就是公式里的 PSC。填 7199 = 输入时钟 ÷ 1000
Counter Mode 计数模式 Up = 向上计数
Counter Period (ARR) 自动装载值 就是公式里的 ARR。填 9999 = 数一万个后溢出
Internal Clock Division (CKD) 内部时钟分频 用于输入滤波等高级场景,初学保持 No Division 即可
auto-reload preload ARR 预装载 见下方说明,初学建议 Enable

单独说说 auto-reload preload(ARR 预装载,对应 ARPE 位):

它决定你在程序运行中修改 ARR时,新值何时生效!, 仔细看这句话

  • Enable(开启):新的 ARR 不会立刻生效,而是等到下一次溢出(更新事件)时才装入。好处是修改周期时不会把当前这一轮计数打乱
  • Disable(关闭):写入立即生效,中途改 ARR 可能让当前周期变得很怪

血与泪!!: 如果你已经置好了 CubeMX 的ARR 的话, 你开着这个点生成之后你的代码相应的htim3.Init.Period不会得到改变, 你需要把这个auto-reload preload设置为 disable 再去改