STM32定时器基础:分类、工作原理与核心应用

各位同学,咱们今天聊聊定时器。说实话,定时器是STM32里我打交道最多的外设之一。刚入行那会儿,我总觉得定时器不就是数数嘛,有什么难的?直到有一次做电机控制项目,PWM频率抖动导致电机异响,排查了整整两天——最后发现是定时器配置时预分频器没算对。从那以后,我对定时器就多了几分敬畏。

这一章,咱们把定时器的底裤扒干净。从分类到原理,从中断到PWM,一步到位。

定时器家族:三类定时器,各司其职

STM32的定时器分三类:基本定时器、通用定时器、高级定时器。说白了,就是低配、中配、高配。

类型 定时器实例 核心功能 典型应用场景
基本定时器 TIM6、TIM7 基础定时、触发DAC 简单延时、周期性中断
通用定时器 TIM2~TIM5、TIM9~TIM14 定时、输入捕获、输出比较、PWM 舵机控制、频率测量、呼吸灯
高级定时器 TIM1、TIM8 全部通用功能 + 互补PWM、刹车、死区 电机驱动、三相逆变、开关电源
我的习惯:做普通项目,通用定时器基本够用。只有做电机控制或电源类产品,我才会动用高级定时器。别一上来就开高级定时器,资源浪费不说,配置还复杂。

定时器工作原理:从时钟到计数,再到中断

定时器工作的核心,就三句话:

  1. 时钟源提供节拍
  2. 预分频器(PSC)把节拍降速
  3. 计数器(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和ARR都要减1,因为寄存器是从0开始计数的。我见过不少新手在这里翻车,算出来的时间总是差一个数。

定时器中断:让CPU从轮询中解放

定时器中断,说白了就是让定时器自己干活,干完了喊一声。CPU不用一直盯着。

配置步骤其实很简单:

  1. 使能定时器时钟
  2. 配置PSC和ARR
  3. 使能更新中断
  4. 使能定时器
  5. 编写中断服务函数

代码示例(以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的步骤:

  1. 配置定时器为PWM模式
  2. 设置输出通道对应的GPIO为复用功能
  3. 设置PSC和ARR确定频率
  4. 设置CCR确定占空比
  5. 使能输出和定时器

代码示例(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°。

知识体系总览

下面这张图,把定时器的分类、工作原理和核心应用串起来了。建议你保存下来,以后做项目时对照着看。

STM32定时器知识体系 基本定时器 通用定时器 高级定时器 核心组成:时钟源 → 预分频器(PSC) → 计数器(CNT) → 自动重装载(ARR) 定时周期 = (PSC+1) × (ARR+1) / 时钟频率 定时中断模式 PWM输出模式 输入捕获模式 周期性任务、延时 呼吸灯、舵机、电机调速 频率测量、脉冲计数 💡 通用定时器覆盖80%的应用场景,高级定时器专为电机控制等复杂场景设计

避坑指南:定时器配置常见问题

最后,分享几个我这些年踩过的坑,希望能帮你少走弯路。

  • 预分频器更新时机:PSC修改后,要等到下一次更新事件才会生效。如果你需要立即生效,可以手动产生一次更新事件(设置UG位)。
  • ARR和CCR的关系:PWM模式下,CCR不能大于ARR,否则输出一直为高电平。我曾经犯过这个错,查了半天才发现是CCR写大了。
  • 定时器时钟源:APB1上的定时器(TIM2~TIM7)时钟可能不是直接等于APB1时钟。如果APB1预分频系数不为1,定时器时钟会加倍。具体看参考手册的时钟树。
  • 中断优先级:多个定时器中断时,优先级设置要合理。我一般把时间要求严格的(如电机控制)设高优先级,普通的(如LED闪烁)设低优先级。