2. STM32定时器原理:通用定时器与高级定时器结构、时钟源选择、时基单元配置

定时器是STM32里最常用的外设之一。说白点,它就是一块能精确计时的硬件。PWM信号、电机控制、舵机驱动,全都离不开它。我刚开始学的时候,总觉得定时器配置很绕,后来发现只要抓住三个核心——时钟源、时基单元、输出通道,基本就通了。

2.1 定时器家族:你用的是哪一类?

STM32的定时器分三类:基本定时器、通用定时器、高级定时器。咱们做PWM控制,主要用后两种。

类型 典型型号 主要功能 适用场景
基本定时器 TIM6、TIM7 基础计时、触发DAC 简单延时、定时中断
通用定时器 TIM2~TIM5 PWM输出、输入捕获、编码器接口 舵机控制、电机测速
高级定时器 TIM1、TIM8 互补PWM、死区插入、刹车功能 无刷电机、H桥驱动

我个人习惯:做舵机控制用通用定时器就够了。做直流电机或步进电机,尤其是需要互补PWM的场合,直接上高级定时器。

2.2 定时器内部结构:一张图看懂

下面这张图是我自己画的通用定时器结构简图。你看一遍,基本就明白数据怎么流了。

通用定时器内部结构 时钟源 时基单元 PSC 预分频器 CNT 计数器 ARR 自动重装载 RCR 重复计数器 输出通道 捕获/比较寄存器 输出比较逻辑 PWM生成 极性选择 中断/DMA 说明: 1. 时钟源经过PSC分频后,驱动CNT计数器递增/递减。 2. CNT与ARR比较,产生更新事件(UEV)。 3. CNT与CCR比较,产生PWM跳变沿。 4. 高级定时器多了RCR,用于控制N个周期后才触发中断。

你看,数据从左往右流:时钟源 → 时基单元 → 输出通道。时基单元是心脏,输出通道是手脚。

2.3 时钟源选择:定时器的“心跳”从哪来?

定时器要工作,必须先有时钟。STM32给定时器准备了四种时钟源:

  • 内部时钟(CK_INT):来自APB总线,最常用。我99%的项目都用这个。
  • 外部时钟模式1(TIx):从某个GPIO引脚输入,用于外部信号计数。
  • 外部时钟模式2(ETR):专用ETR引脚,速度更快。
  • 内部触发(ITRx):一个定时器去触发另一个,实现级联。

小技巧:做PWM控制时,直接用内部时钟就行。APB1上的定时器(TIM2~TIM7)通常跑在36MHz或45MHz,APB2上的(TIM1、TIM8)跑在72MHz或90MHz。具体看你的芯片型号。

2.4 时基单元配置:三个寄存器搞定一切

时基单元是定时器的核心。它由三个寄存器组成:

  • PSC(预分频器):把时钟频率降下来。16位,可设0~65535。
  • CNT(计数器):真正计数的家伙。16位或32位,取决于定时器型号。
  • ARR(自动重装载):计到多少就归零。16位,决定PWM周期。

计算公式很简单:

定时器频率 = 时钟源频率 / (PSC + 1)
PWM周期    = (ARR + 1) / 定时器频率

举个例子。假设时钟源是72MHz,我想输出50Hz的PWM(舵机标准频率):

先设PSC = 71,得到定时器频率 = 72MHz / 72 = 1MHz
再设ARR = 19999,得到PWM周期 = 20000 / 1MHz = 20ms = 50Hz

嗯,这里要注意:PSC和ARR都是加1再参与运算的。我刚开始写代码时老忘记这个+1,结果频率总是不对。

关键点:PSC决定计数精度,ARR决定周期长度。两者配合,就能得到你想要的PWM频率。

2.5 高级定时器的特殊配置

高级定时器(TIM1、TIM8)比通用定时器多了几个东西:

  • 重复计数器(RCR):控制多少个更新事件后才产生中断。做电机控制时很有用。
  • 死区插入:互补PWM之间插入一段延迟,防止上下桥臂直通。
  • 刹车功能(BKIN):紧急情况下强制关闭PWM输出。

避坑指南:我曾经在一个无刷电机项目里,忘了配置死区时间。结果上电一瞬间,H桥上下管同时导通,直接烧了两个MOS管。从那以后,我只要用高级定时器做互补PWM,第一件事就是设死区。

2.6 配置流程:三步走

不管用哪个定时器,配置流程都差不多:

  1. 使能时钟:RCC_APB1PeriphClockCmd() 或 RCC_APB2PeriphClockCmd()。
  2. 配置时基:TIM_TimeBaseInit(),填PSC、ARR、计数模式。
  3. 配置输出:TIM_OC1Init(),设PWM模式、极性、CCR值。

下面是一个完整的初始化代码示例,输出50Hz、占空比7.5%的PWM(舵机中位):

void TIM2_PWM_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;

    // 1. 使能时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

    // 2. 配置PA0为TIM2_CH1复用输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    // 3. 配置时基:72MHz / 72 = 1MHz, ARR=19999 => 50Hz
    TIM_TimeBaseStructure.TIM_Prescaler = 71;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseStructure.TIM_Period = 19999;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

    // 4. 配置PWM输出:占空比 = 1500/20000 = 7.5%
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 1500;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM2, &TIM_OCInitStructure);

    // 5. 使能定时器
    TIM_Cmd(TIM2, ENABLE);
}

这段代码我用了很多年,基本没改过框架。你只要改PSC、ARR、Pulse三个值,就能适配不同的电机和舵机。

2.7 常见问题与排查

现象 可能原因 排查方法
PWM无输出 GPIO未配置复用功能 检查GPIO_Mode是否为AF_PP
频率不对 PSC或ARR计算错误 用示波器实测,反推实际分频值
占空比不准 CCR值超出ARR范围 确保CCR ≤ ARR
高级定时器无输出 未使能主输出 调用TIM_CtrlPWMOutputs(TIMx, ENABLE)

个人经验:排查PWM问题时,我习惯先看GPIO配置,再看时钟使能,最后查时基参数。这个顺序能快速定位90%的问题。