STM32定时器基础:分类、工作原理与核心应用
各位同学,咱们今天聊聊定时器。说实话,定时器是STM32里我打交道最多的外设之一。刚入行那会儿,我总觉得定时器不就是数数嘛,有什么难的?直到有一次做电机控制项目,PWM频率抖动导致电机异响,排查了整整两天——最后发现是定时器配置时预分频器没算对。从那以后,我对定时器就多了几分敬畏。
这一章,咱们把定时器的底裤扒干净。从分类到原理,从中断到PWM,一步到位。
定时器家族:三类定时器,各司其职
STM32的定时器分三类:基本定时器、通用定时器、高级定时器。说白了,就是低配、中配、高配。
| 类型 | 定时器实例 | 核心功能 | 典型应用场景 |
|---|---|---|---|
| 基本定时器 | TIM6、TIM7 | 基础定时、触发DAC | 简单延时、周期性中断 |
| 通用定时器 | TIM2~TIM5、TIM9~TIM14 | 定时、输入捕获、输出比较、PWM | 舵机控制、频率测量、呼吸灯 |
| 高级定时器 | TIM1、TIM8 | 全部通用功能 + 互补PWM、刹车、死区 | 电机驱动、三相逆变、开关电源 |
我的习惯:做普通项目,通用定时器基本够用。只有做电机控制或电源类产品,我才会动用高级定时器。别一上来就开高级定时器,资源浪费不说,配置还复杂。
定时器工作原理:从时钟到计数,再到中断
定时器工作的核心,就三句话:
- 时钟源提供节拍
- 预分频器(PSC)把节拍降速
- 计数器(CNT)按降速后的节拍累加
举个例子。假设时钟源是72MHz,预分频器设为7200-1,那计数器每加1,实际耗时就是:
定时器时钟 = 72MHz / 7200 = 10kHz
每个计数周期 = 1 / 10kHz = 100μs
计数器从0累加到自动重装载值(ARR),产生一次更新事件。如果使能了中断,就会触发定时器中断。
关键公式:
定时周期 = (PSC + 1) × (ARR + 1) / 定时器时钟频率
举个例子:PSC=7199,ARR=999,时钟72MHz
周期 = 7200 × 1000 / 72,000,000 = 0.1秒 = 100ms
定时周期 = (PSC + 1) × (ARR + 1) / 定时器时钟频率
举个例子:PSC=7199,ARR=999,时钟72MHz
周期 = 7200 × 1000 / 72,000,000 = 0.1秒 = 100ms
嗯,这里要注意:PSC和ARR都要减1,因为寄存器是从0开始计数的。我见过不少新手在这里翻车,算出来的时间总是差一个数。
定时器中断:让CPU从轮询中解放
定时器中断,说白了就是让定时器自己干活,干完了喊一声。CPU不用一直盯着。
配置步骤其实很简单:
- 使能定时器时钟
- 配置PSC和ARR
- 使能更新中断
- 使能定时器
- 编写中断服务函数
代码示例(以TIM2为例,1ms中断一次):
// 定时器初始化
void TIM2_Init(void)
{
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN; // 使能TIM2时钟
TIM2->PSC = 72 - 1; // 72MHz / 72 = 1MHz
TIM2->ARR = 1000 - 1; // 1MHz / 1000 = 1kHz → 1ms
TIM2->DIER |= TIM_DIER_UIE; // 使能更新中断
TIM2->CR1 |= TIM_CR1_CEN; // 使能定时器
NVIC_EnableIRQ(TIM2_IRQn); // 使能NVIC中断
NVIC_SetPriority(TIM2_IRQn, 2); // 设置优先级
}
// 中断服务函数
void TIM2_IRQHandler(void)
{
if(TIM2->SR & TIM_SR_UIF) // 检查更新中断标志
{
TIM2->SR = ~TIM_SR_UIF; // 清除标志位
// 用户代码:LED翻转、数据采集等
}
}
我曾经踩过的坑:中断服务函数里一定要先清除标志位,再执行用户代码。否则如果用户代码执行时间过长,可能再次触发中断,导致程序卡死。另外,NVIC优先级别忘了配,默认优先级可能不是你想要的。
PWM输出:定时器最常用的高级功能
PWM,脉冲宽度调制。说白了就是通过调节高电平占空比,模拟出不同的电压效果。呼吸灯、舵机、电机调速,全靠它。
PWM的核心参数就两个:
- 频率:由PSC和ARR决定
- 占空比:由比较值(CCR)决定
配置PWM的步骤:
- 配置定时器为PWM模式
- 设置输出通道对应的GPIO为复用功能
- 设置PSC和ARR确定频率
- 设置CCR确定占空比
- 使能输出和定时器
代码示例(TIM3通道1,PA6输出,50Hz,50%占空比):
void PWM_Init(void)
{
// 1. 使能时钟
RCC->APB1ENR |= RCC_APB1ENR_TIM3EN;
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;
// 2. 配置GPIO为复用功能
GPIOA->MODER |= GPIO_MODER_MODER6_1; // 复用模式
GPIOA->AFR[0] |= 2 << (6 * 4); // AF2 = TIM3_CH1
// 3. 配置定时器
TIM3->PSC = 72 - 1; // 1MHz
TIM3->ARR = 20000 - 1; // 1MHz / 20000 = 50Hz
// 4. 配置PWM模式
TIM3->CCMR1 |= (6 << 4); // PWM模式1
TIM3->CCER |= TIM_CCER_CC1E; // 使能输出
// 5. 设置占空比
TIM3->CCR1 = 10000; // 50%占空比
// 6. 使能定时器
TIM3->CR1 |= TIM_CR1_CEN;
}
我的经验:PWM频率的选择有讲究。做呼吸灯,50~100Hz就够了,频率太高人眼看不出来。做电机控制,一般10kHz~20kHz,太低电机会啸叫,太高MOS管开关损耗大。做舵机控制,50Hz是标准频率,占空比5%~10%对应0°~180°。
知识体系总览
下面这张图,把定时器的分类、工作原理和核心应用串起来了。建议你保存下来,以后做项目时对照着看。
避坑指南:定时器配置常见问题
最后,分享几个我这些年踩过的坑,希望能帮你少走弯路。
- 预分频器更新时机:PSC修改后,要等到下一次更新事件才会生效。如果你需要立即生效,可以手动产生一次更新事件(设置UG位)。
- ARR和CCR的关系:PWM模式下,CCR不能大于ARR,否则输出一直为高电平。我曾经犯过这个错,查了半天才发现是CCR写大了。
- 定时器时钟源:APB1上的定时器(TIM2~TIM7)时钟可能不是直接等于APB1时钟。如果APB1预分频系数不为1,定时器时钟会加倍。具体看参考手册的时钟树。
- 中断优先级:多个定时器中断时,优先级设置要合理。我一般把时间要求严格的(如电机控制)设高优先级,普通的(如LED闪烁)设低优先级。