【STM32专题】深入理解定时器(TIM)原理与配置 (HAL库 + CubeMX)
在嵌入式开发中,定时器(Timer)的重要性仅次于GPIO。无论是简单的LED闪烁、精确的时间控制、PWM电机驱动,还是复杂的输入捕获,都离不开定时器。很多初学者(包括曾经的我)在配置定时器时,往往对PSCARR为什么要减1?时钟源到底是多少?本文将以STM32F1/F4系列为例,从硬件原理到CubeMX配置,再到代码实战,带你彻底搞懂STM32定时器。STM32的定时器配置看似参数多,其实核心就
前言
在嵌入式开发中,定时器(Timer)的重要性仅次于GPIO。无论是简单的LED闪烁、精确的时间控制、PWM电机驱动,还是复杂的输入捕获,都离不开定时器。
很多初学者(包括曾经的我)在配置定时器时,往往对 PSC、ARR 这些参数感到困惑:为什么要减1?时钟源到底是多少?
本文将以STM32F1/F4系列为例,从硬件原理到CubeMX配置,再到代码实战,带你彻底搞懂STM32定时器。
一、 定时器的分类与功能
STM32的定时器资源非常丰富,通常分为以下三类。理解分类有助于我们“杀鸡不用牛刀”。
| 定时器类型 | 常见编号 | 主要功能 | 适用场景 |
|---|---|---|---|
| 基本定时器 | TIM6, TIM7 | 只有计数功能,无输入输出通道 | 简单的延时、驱动DAC |
| 通用定时器 | TIM2~TIM5 | 计数、输入捕获、输出比较(PWM)、编码器接口 | 此时最常用,电机控制、测量脉宽 |
| 高级定时器 | TIM1, TIM8 | 包含通用所有功能 + 死区控制、刹车功能 | 三相无刷电机、复杂电源控制 |
二、 核心原理:定时器是怎么工作的?
定时器的本质就是一个“只会加法的计数器”。它的工作流程可以概括为:时钟源 -> 分频器 -> 计数器 -> 溢出中断。
为了方便理解,我们可以用“水桶接水”的模型来类比:
- 时钟源 (CK_INT):相当于水龙头,水流的速度就是频率(比如72MHz)。
- 预分频器 (PSC):相当于限流阀。如果PSC=71,表示每来72滴水,只放1滴过去。这样可以让计数更慢、时间更长。
- 自动重装载寄存器 (ARR):相当于水桶的深度。设定一个值(比如4999),当水装到这个高度时,水桶就会翻倒。
- 计数器 (CNT):相当于当前的水位。水滴一滴滴进来,CNT就从0开始一直加。
- 更新中断 (UEV):当水位 (CNT) 达到桶深 (ARR) 时,水桶倒水,并触发一次中断,CNT清零重新开始。

三、 关键公式(敲黑板!)
在配置定时器时,最重要的就是计算 溢出时间(Tout)。

- CK_PSC:定时器的输入时钟频率(注意:通常APB1总线的定时器时钟会倍频,STM32F1的TIM2-TIM7通常为72MHz)。
- PSC:预分频系数 (Prescaler)。为什么要+1? 因为计算机从0开始计数,写0代表1分频,写71代表72分频。
- ARR:自动重装载值 (AutoReload Register)。为什么要+1? 同理,计数器从0数到ARR,总共数了ARR+1个数。
举个栗子 🌰
假设系统时钟CK_PSC=72MHz,我们要实现 500ms (0.5s) 的定时中断:
- 确定分频系数 (PSC):为了好算,我们将72MHz分频成10kHz(即1秒数10000个数)。
- PSC=7200−1=7199PSC=7200−1=7199
- 此时计数器每秒增加 10000 次,即每个计数周期 0.1ms0.1ms。
- 确定重装载值 (ARR):我们需要0.5s,也就是500ms。
- ARR=500×10−1=4999ARR=500×10−1=4999
结论配置: PSC = 7199, ARR = 4999。
四、 实战:CubeMX 配置步骤
以STM32F103C8T6为例,配置TIM2实现每1秒翻转一次LED。
1. 时钟树配置
确保系统时钟配置正确(例如HSE外部晶振,倍频到72MHz)。
注意: 查看APB1 Timer Clock的值,这决定了公式中的 CK_PSC。
2. 定时器配置
- 打开 Timers -> TIM2。
- Clock Source 选择
Internal Clock。 - Parameter Settings(参数设置):
- Prescaler (PSC):
7199(72MHz / 7200 = 10kHz) - Counter Mode:
Up(向上计数) - Counter Period (ARR):
9999(10000次计数 = 1秒) - auto-reload preload:
Enable(使能自动重装载影子寄存器)
- Prescaler (PSC):
3. 中断配置
- 切换到 NVIC Settings 标签页。
- 勾选
TIM2 global interrupt后面的 Enabled。 - (可选) 设置中断优先级。
- 点击 GENERATE CODE 生成代码。
- 、

五、 代码编写 (HAL库)
生成的代码在 main.c 和 tim.c 中。我们需要做两件事:开启定时器 和 重写中断回调函数。
1. 开启定时器
在 main.c 的 main() 函数中,MX_TIM2_Init() 之后,while(1) 之前,添加:
/* USER CODE BEGIN 2 */
// 以中断方式开启定时器2
HAL_TIM_Base_Start_IT(&htim2);
/* USER CODE END 2 */
2. 重写中断回调函数
HAL库处理中断的逻辑是:TIM2_IRQHandler -> HAL_TIM_IRQHandler -> HAL_TIM_PeriodElapsedCallback。
我们需要在 main.c 的末尾(或者专门的一个中断处理文件中)重写这个虚函数:
/* USER CODE BEGIN 4 */
/**
* @brief 定时器周期溢出回调函数
* @param htim: 定时器句柄
*/
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
// 判断是否是定时器2触发的中断
if (htim->Instance == TIM2)
{
// 翻转LED (假设LED接在PC13)
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}
}
/* USER CODE END 4 */
六、 常见问题与避坑指南
- 忘了开启定时器: 初始化后一定要调用
HAL_TIM_Base_Start_IT,否则定时器不会跑,中断也不会来。 - PSC和ARR忘了减1: 如果你想要分频72,PSC必须写71。如果不减1,时间会有一点点误差,虽然在LED闪烁上看不出来,但在精确计时或通信中是致命的。
- 中断没响应: 检查CubeMX里的NVIC有没有勾选;检查
main.c里是否开启了全局中断__enable_irq()(HAL库默认开启)。 - ARR为0: 如果ARR设为0,定时器刚启动就溢出,可能会导致程序卡死或异常。
七、 总结
STM32的定时器配置看似参数多,其实核心就是**“分频”与“重装载”**两个概念。
- PSC 决定了定时器跳动的快慢。
- ARR 决定了定时器跳动多少次触发中断。
掌握了这两个参数和计算公式,你就掌握了定时器的基本命脉。接下来可以尝试进阶功能:PWM输出(呼吸灯) 和 输入捕获(测按键时长),原理都是基于此的延伸。
希望这篇文章能帮你彻底理解定时器!如果有疑问,欢迎在评论区留言讨论。
常用配置速查表 (假设时钟 72MHz)
| 目标效果 | 第一步:定频率 (PSC) | 每一跳时间 | 第二步:定次数 (ARR) | 计算逻辑 |
|---|---|---|---|---|
| 500ms中断 | 7199 (7200分频) | 0.1ms | 4999 | 0.1ms×5000=500ms0.1ms×5000=500ms |
| 1s中断 | 7199 (7200分频) | 0.1ms | 9999 | 0.1ms×10000=1s0.1ms×10000=1s |
| 1ms中断 | 71 (72分频) | 1us | 999 | 1us×1000=1ms1us×1000=1ms |
| PWM周期(20ms) | 71 (72分频) | 1us | 19999 | 1us×20000=20ms1us×20000=20ms |
版权声明: 本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
更多推荐





所有评论(0)