2. STM32定时器原理:通用定时器与高级定时器结构、时钟源选择、时基单元配置
定时器是STM32里最常用的外设之一。说白点,它就是一块能精确计时的硬件。PWM信号、电机控制、舵机驱动,全都离不开它。我刚开始学的时候,总觉得定时器配置很绕,后来发现只要抓住三个核心——时钟源、时基单元、输出通道,基本就通了。
2.1 定时器家族:你用的是哪一类?
STM32的定时器分三类:基本定时器、通用定时器、高级定时器。咱们做PWM控制,主要用后两种。
| 类型 | 典型型号 | 主要功能 | 适用场景 |
|---|---|---|---|
| 基本定时器 | TIM6、TIM7 | 基础计时、触发DAC | 简单延时、定时中断 |
| 通用定时器 | TIM2~TIM5 | PWM输出、输入捕获、编码器接口 | 舵机控制、电机测速 |
| 高级定时器 | TIM1、TIM8 | 互补PWM、死区插入、刹车功能 | 无刷电机、H桥驱动 |
我个人习惯:做舵机控制用通用定时器就够了。做直流电机或步进电机,尤其是需要互补PWM的场合,直接上高级定时器。
2.2 定时器内部结构:一张图看懂
下面这张图是我自己画的通用定时器结构简图。你看一遍,基本就明白数据怎么流了。
你看,数据从左往右流:时钟源 → 时基单元 → 输出通道。时基单元是心脏,输出通道是手脚。
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 配置流程:三步走
不管用哪个定时器,配置流程都差不多:
- 使能时钟:RCC_APB1PeriphClockCmd() 或 RCC_APB2PeriphClockCmd()。
- 配置时基:TIM_TimeBaseInit(),填PSC、ARR、计数模式。
- 配置输出: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%的问题。