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参考手册重新画的。它展示了定时器的核心数据流。你看一遍,再去看手册里的框图,会清晰很多。

STM32通用定时器核心结构框图 时钟源 CK_INT (内部) TIx / ETR (外部) ITRx (触发) PSC 预分频器 16位 (1~65536) CNT 计数器 16/32位 递增/递减/中心 ARR 自动重装载 捕获/比较通道 (CCRx) 输入捕获:测量外部信号频率/脉宽 输出比较:产生PWM / 定时触发 比较匹配 中断 / DMA 更新 / 捕获 / 比较事件 输出引脚 (TIMx_CHy) PWM波形输出 模式控制 (SLVCRL) 从模式:复位/门控/触发 💡 数据流说明: 时钟源 → 预分频器(PSC)分频 → 计数器(CNT)计数 → 与自动重装载(ARR)比较 捕获/比较通道(CCR)与CNT匹配时,触发输出电平翻转或产生中断

这张图里,我特意把数据流方向标出来了。你顺着箭头看:时钟进来,经过预分频器,喂给计数器,计数器跟ARR和CCR比较,最后产生输出或中断。嗯,就是这么个流程。

1.5 定时器工作模式:三种计数方式

定时器的计数器,有三种工作模式。选哪种,取决于你要干什么。

  • 递增计数模式:从0数到ARR,然后归零重新开始。最常用,比如生成周期性的PWM。
  • 递减计数模式:从ARR往下数到0,然后重新加载ARR。适合需要精确控制下降沿的场景。
  • 中心对齐模式:先递增到ARR,再递减到0,如此循环。做电机控制时,这种模式能产生对称的PWM波形,谐波更小。

我的经验:做呼吸灯效果时,用中心对齐模式。LED亮度从暗到亮再到暗,过渡非常平滑。如果用递增模式,你得在中断里手动改变占空比,麻烦不少。

1.6 定时器配置流程:三步走

配置一个定时器,其实就三步。我总结了一个口诀:“时钟、模式、中断”。

  1. 使能定时器时钟:RCC_APB1PeriphClockCmd() 或 RCC_APB2PeriphClockCmd()
  2. 配置时基结构体:设置PSC、ARR、计数模式
  3. 配置中断(可选):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分频系数别算错
  • 配置流程就三步:开时钟、设参数、启中断