如果说 GPIO 是单片机的“手脚”,中断是“神经反射”,那么定时器(Timer)就是单片机的“脉搏”。在 51 单片机里,配置定时器需要去算初值、写寄存器。而在 STM32 中,虽然寄存器更多,但有了 CubeIDE,我们只需要掌握一个核心公式就能实现极其精准的定时。
1. 形象比喻:定时器到底是怎么工作的
你可以把定时器想象成一个“自动数数的小学生”**:
- 时钟源(Clock Source):老师敲黑板的速度(比如每秒敲 72,000,000 下)。
- 预分频器(PSC, Prescaler):小学生觉得老师敲得太快了,数不过来,于是规定“老师敲 7200 下,我才记 1 个数”。
- 计数器(Counter):小学生手里的小本子,每记满一个数就加 1。
- 自动重装载寄存器(ARR, Auto-Reload Register):老师规定的“目标数”。比如数到 10000,就举手报告(触发中断),然后擦掉本子从 0 重新数。
核心公式(必须死记硬背,它是定时器的灵魂)
你想定多长时间,全靠这个公式:
$$定时时间 (秒) = \frac{(PSC + 1) \times (ARR + 1)}{定时器输入频率 (Hz)}$$
对于 STM32F103C8T6,如果你的时钟树配置为 72MHz:
- 如果你想要 1 秒(1Hz)触发一次中断:
- 我们将 预分频器 PSC 设为 7199(即 $7200 - 1$)。
- 我们将 自动重装载寄存器 ARR 设为 9999(即 $10000 - 1$)。
- 计算:$\frac{7200 \times 10000}{72,000,000} = 1\text{ 秒}$。
为什么都要 -1? 因为计数器是从 0 开始数的。数 10 个数,实际上是 0 到 9。
3. STM32CubeIDE 配置步骤
我们以 TIM2(通用定时器)为例:
- 开启定时器:
- 在
.ioc文件左侧找到 Timers -> TIM2。 - Combined Channels 不用管,将 Clock Source 设为 Internal Clock(内部时钟)。
- 在
- 设置参数:
- Prescaler (PSC): 输入
7199。 - Counter Period (ARR): 输入
9999。 - 其他默认即可(向上计数模式)。
- Prescaler (PSC): 输入
- 开启中断 (NVIC):
- 点击上方的 NVIC Settings 选项卡。
- 勾选 TIM2 global interrupt 后面的 Enabled。(不勾这个,定时器只会数数,不会通知 CPU)
- 生成代码:按
Ctrl + S。
4. 编写代码(开启与回调)
定时器配置完后,默认是关闭的,你需要手动在代码里给它“上发条”。
第一步:启动定时器中断
在 main.c 的 MX_GPIO_Init() 之后,while(1) 之前,加入这行代码:
|
|
第二步:编写回调函数
在 main.c 下方的 USER CODE BEGIN 4 区域,写下我们要执行的动作:
|
|
5. 定时中断 vs HAL_Delay()
| 特性 | HAL_Delay() | 定时器中断 |
|---|---|---|
| 原理 | 阻塞式(CPU 在死等,啥也干不了) | 非阻塞式(CPU 平时干别的,时间到才去处理) |
| 精度 | 较低(会被其他任务干扰) | 极高(硬件自动计数,不受软件干扰) |
| 用途 | 简单的、不重要的等待 | 精准采样、控制电机速度、多任务并行 |
6. 进阶:如果你想要 1 毫秒定一次时
- 输入频率 = 72,000,000 Hz
- 目标时间 = 0.001 s
- 设置:PSC = 71 ($72-1$),ARR = 999 ($1000-1$)。
- 计算:$\frac{72 \times 1000}{72,000,000} = 0.001\text{ 秒}$。
常见避坑指南
- 忘记加
_IT:如果是调用HAL_TIM_Base_Start(),定时器会跑,但不会进中断函数。必须用HAL_TIM_Base_Start_IT()。 - 公式没减 1:虽然不减 1 也能跑,但时间会差那么一点点。在追求极致精准的工业控制中,这 1 个周期的误差很重要。
- 中断里代码太长:如果你的定时器是 1ms 触发一次,但你在中断函数里执行了需要 2ms 才能跑完的代码,系统就会彻底乱套。