1. 定时器基础:STM32定时器家族概览
各位同学,咱们今天聊聊STM32的定时器。说实话,我刚接触STM32那会儿,看到参考手册里定时器那一章,头都大了——什么高级、通用、基本,还有一堆寄存器。但后来做项目多了,才发现定时器其实是MCU里最灵活、最常用的外设之一。
你想想看,不管是做电机控制、LED呼吸灯、还是测量脉冲宽度,都离不开它。所以这一章,咱们先把定时器的家底盘清楚。
1.1 定时器家族:三兄弟各有绝活
STM32的定时器分三类:基本定时器、通用定时器和高级定时器。它们的关系,有点像工具套装里的螺丝刀——基本款只能拧螺丝,通用款带棘轮,高级款还带电动功能。
| 类型 | 典型型号 | 主要特性 | 常见应用 |
|---|---|---|---|
| 基本定时器 | TIM6、TIM7 | 仅基础定时功能,无外部引脚 | 产生时基、触发DAC |
| 通用定时器 | TIM2~TIM5 | PWM输出、输入捕获、编码器接口 | 电机控制、信号测量 |
| 高级定时器 | TIM1、TIM8 | 互补PWM、死区插入、刹车功能 | 三相电机、数字电源 |
我个人习惯,做简单延时或触发任务时,优先用基本定时器——配置少,不容易出错。但如果你要做PWM输出,那至少得用通用定时器。
关键区别:高级定时器比通用多了“互补输出”和“刹车输入”。我在做无刷电机驱动时,刹车功能救过我的板子——电机堵转时能立刻关断MOS管,避免烧毁。
1.2 定时器时钟树:你的定时器跑多快?
定时器的工作频率,不是随便设的。它取决于时钟树怎么配置。说白了,定时器的时钟源来自APB总线,而APB总线又来自系统时钟(SYSCLK)。
这里有个坑,我踩过好几次:APB1和APB2的时钟频率可能不同。比如STM32F103,APB1最高36MHz,APB2最高72MHz。如果你把定时器挂在APB1上,却按72MHz去算预分频,那定时器跑得比你预期的慢一半。
注意:当APB时钟分频系数不为1时,定时器时钟会倍频。例如APB1=36MHz(分频系数2),则挂载在APB1上的定时器时钟=72MHz。这个细节,我见过不少工程师在这里栽跟头。
1.3 时钟源选择:不止一种玩法
STM32定时器的时钟源,其实有四种选择。大多数时候我们用内部时钟(CK_INT),但有些场景需要外部信号驱动。
- 内部时钟(CK_INT):来自APB总线,最常用
- 外部时钟模式1(TIx):从定时器输入引脚捕获外部脉冲
- 外部时钟模式2(ETR):通过ETR引脚输入,可配置极性/预分频
- 内部触发(ITRx):一个定时器作为另一个的时钟源
我记得有个项目,需要测量发动机转速。传感器输出的是脉冲信号,频率随转速变化。我直接用定时器的外部时钟模式1,把脉冲接到输入引脚,计数器直接累加脉冲数——省掉了外部计数芯片。
小技巧:如果你需要多个定时器同步工作,可以用内部触发方式。比如让TIM2作为主定时器,TIM3从模式跟随。这样能保证PWM波形相位严格对齐。
1.4 定时器结构框图:一张图看懂
下面这张图,是我根据STM32参考手册重新画的。它展示了定时器的核心数据流。你看一遍,再去看手册里的框图,会清晰很多。
这张图里,我特意把数据流方向标出来了。你顺着箭头看:时钟进来,经过预分频器,喂给计数器,计数器跟ARR和CCR比较,最后产生输出或中断。嗯,就是这么个流程。
1.5 定时器工作模式:三种计数方式
定时器的计数器,有三种工作模式。选哪种,取决于你要干什么。
- 递增计数模式:从0数到ARR,然后归零重新开始。最常用,比如生成周期性的PWM。
- 递减计数模式:从ARR往下数到0,然后重新加载ARR。适合需要精确控制下降沿的场景。
- 中心对齐模式:先递增到ARR,再递减到0,如此循环。做电机控制时,这种模式能产生对称的PWM波形,谐波更小。
我的经验:做呼吸灯效果时,用中心对齐模式。LED亮度从暗到亮再到暗,过渡非常平滑。如果用递增模式,你得在中断里手动改变占空比,麻烦不少。
1.6 定时器配置流程:三步走
配置一个定时器,其实就三步。我总结了一个口诀:“时钟、模式、中断”。
- 使能定时器时钟:RCC_APB1PeriphClockCmd() 或 RCC_APB2PeriphClockCmd()
- 配置时基结构体:设置PSC、ARR、计数模式
- 配置中断(可选):NVIC使能,编写中断服务函数
下面是个最简单的例子,让TIM2每1秒产生一次中断:
// 1. 使能TIM2时钟(挂在APB1上)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 2. 配置时基
TIM_TimeBaseInitTypeDef tim;
tim.TIM_Prescaler = 7200 - 1; // 72MHz / 7200 = 10kHz
tim.TIM_Period = 10000 - 1; // 10kHz / 10000 = 1Hz
tim.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &tim);
// 3. 使能更新中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// 4. 配置NVIC
NVIC_InitTypeDef nvic;
nvic.NVIC_IRQChannel = TIM2_IRQn;
nvic.NVIC_IRQChannelPreemptionPriority = 0;
nvic.NVIC_IRQChannelSubPriority = 0;
nvic.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic);
// 5. 启动定时器
TIM_Cmd(TIM2, ENABLE);
// 中断服务函数
void TIM2_IRQHandler(void) {
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
// 1秒到,做你想做的事
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
踩坑提醒:我曾经在配置TIM2时,忘了使能APB1时钟。结果定时器完全不工作,查了半天才发现RCC没开。另外,预分频器和周期值都要减1,因为寄存器是从0开始计数的。
1.7 本章小结
好了,这一章咱们把定时器的家底摸了一遍。你记住三件事:
- 定时器分三级:基本、通用、高级,选型看需求
- 时钟树决定定时器跑多快,APB分频系数别算错
- 配置流程就三步:开时钟、设参数、启中断