解码STM32定时器:原理、配置与实战
定时器核心概述
定时器是STM32微控制器中核心的外设之一,核心功能是通过精准的时钟计数实现定时控制、中断触发、波形生成等功能。在嵌入式系统中,定时器的应用场景极为广泛,例如:
- 定时控制:LED灯周期性翻转、系统延时函数实现、任务调度器时钟基准;
- 波形生成:通过PWM(脉冲宽度调制)控制电机转速、舵机角度、LED亮度;
- 信号测量:通过输入捕获功能测量外部信号的脉冲宽度(如红外遥控信号)、频率;
- 同步控制:多定时器联动实现复杂时序逻辑,或与其他外设(如ADC、DAC)同步工作。
对于STM32F407系列微控制器,内部集成14个定时器,按功能可分为三类,各类定时器的核心差异在于支持的功能和计数位数:
| 定时器类型 | 具体型号 | 计数位数 | 核心功能 |
|---|---|---|---|
| 基本定时器 | TIM6、TIM7 | 16位 | 仅定时功能,可触发DAC转换 |
| 通用定时器 | TIM2TIM5、TIM9TIM14 | TIM2/TIM5为32位,其余16位 | 定时、输入捕获、输出比较、PWM生成、单脉冲模式 |
| 高级定时器 | TIM1、TIM8 | 16位 | 包含通用定时器所有功能,新增互补输出、死区控制(适配电机驱动) |
定时器核心原理
时钟来源与频率计算
定时器的计数时钟源自系统时钟(RCC),需经过APB总线分频后传入,不同总线挂载的定时器,其时钟频率计算规则不同:

- APB1总线:最大频率42MHz,挂载定时器TIM2TIM5、TIM6TIM7、TIM12~TIM14;若APB1分频系数为1(不分频),定时器时钟=APB1频率;若分频系数>1,定时器时钟=APB1频率×2(最终为84MHz);
- APB2总线:最大频率84MHz,挂载定时器TIM1、TIM8~TIM11;若APB2分频系数为1,定时器时钟=APB2频率;若分频系数>1,定时器时钟=APB2频率×2(最终为168MHz)。
核心结论:STM32F407中,APB1挂载的定时器实际工作频率为84MHz,APB2挂载的为168MHz。

定时时间计算
定时器的定时时间由“预分频器(PSC)”和“自动重载寄存器(ARR)”共同决定,核心公式如下:
定时时间 = (PSC + 1)×(ARR + 1) / 定时器时钟频率
参数说明:
- PSC(预分频器):16位寄存器,取值范围0~65535,作用是将定时器时钟分频,分频后的频率=定时器时钟/(PSC+1);
- ARR(自动重载值):16位或32位寄存器(取决于定时器位数),取值范围0~最大值,计数器计数到ARR值后会触发更新事件(溢出/下溢),并重新开始计数。
示例:使用TIM6(APB1挂载,时钟84MHz)定时500ms,计算PSC和ARR值:
已知定时时间=500ms=0.5s,定时器时钟=84MHz=84×10⁶Hz;
代入公式:(PSC+1)×(ARR+1)= 0.5 × 84×10⁶ = 42×10⁶;
可选配置:PSC=8399(分频后频率=84MHz/8400=10kHz),ARR=4999(计数5000次),则(8399+1)×(4999+1)=8400×5000=42×10⁶,刚好满足500ms定时。
计数模式
不同类型定时器支持的计数模式不同,核心模式分为三类:
- 递增计数(默认):计数器从0开始递增,计数到ARR值后触发更新事件,重新从0开始;
- 递减计数:计数器从ARR值开始递减,计数到0后触发更新事件,重新从ARR开始;
- 中心对齐计数:计数器从0递增到ARR-1(上溢),再递减到1(下溢),循环往复,适用于对称PWM生成。
注意:TIM9TIM14仅支持递增计数,TIM2TIM5支持三种模式,高级定时器支持所有模式。
定时器基础配置(定时中断)
以TIM6定时1s实现LED0翻转为例,讲解基础配置流程,核心步骤:开启时钟→配置时基→配置中断→使能定时器→编写中断服务函数。
代码(标准库)
#include "stm32f4xx.h"
/**
* @brief TIM6初始化函数,配置1s定时中断
* @param 无
* @return 无
* @note TIM6挂载APB1总线,时钟84MHz;定时1s配置:PSC=8399,ARR=9999
* 定时时间=(8399+1)*(9999+1)/84e6 = 8400*10000/84e6 = 1s
*/
void TIM6_Init(void) {
// 1. 开启TIM6和APB1总线时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);
// 2. 配置定时器时基结构体
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_Prescaler = 8399; // 预分频器值
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; // 递增计数模式
TIM_TimeBaseInitStruct.TIM_Period = 9999; // 自动重载值
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; // 时钟分频因子(不分频)
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; // 重复计数器(仅高级定时器有效)
TIM_TimeBaseInit(TIM6, &TIM_TimeBaseInitStruct);
// 3. 配置NVIC中断控制器(允许TIM6中断)
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM6_DAC_IRQn; // TIM6中断通道
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2; // 抢占优先级2
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; // 响应优先级0
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; // 使能中断通道
NVIC_Init(&NVIC_InitStruct);
// 4. 使能TIM6更新中断
TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);
// 5. 使能TIM6计数器
TIM_Cmd(TIM6, ENABLE);
}
/**
* @brief TIM6中断服务函数,处理定时中断事件
* @param 无
* @return 无
* @note 中断触发条件:TIM6计数器计数到ARR值(9999)后产生更新中断
* 需先判断中断标志位,处理完成后清除标志位
*/
void TIM6_DAC_IRQHandler(void) {
// 判断是否为TIM6更新中断
if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET) {
GPIO_ToggleBits(GPIOF, GPIO_Pin_9); // 翻转LED0(假设LED0接PF9)
TIM_ClearITPendingBit(TIM6, TIM_IT_Update); // 清除中断标志位
}
}
/**
* @brief GPIO初始化函数,配置PF9为输出(控制LED0)
* @param 无
* @return 无
*/
void LED_Init(void) {
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE); // 开启GPIOF时钟
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOF, &GPIO_InitStruct);
GPIO_SetBits(GPIOF, GPIO_Pin_9); // 初始熄灭LED0
}
int main(void) {
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 配置中断优先级分组2
LED_Init(); // 初始化LED
TIM6_Init(); // 初始化TIM6定时中断
while (1) {
// 主循环无需处理,中断中完成LED翻转
}
}
通用定时器进阶功能
通用定时器(如TIM2TIM5、TIM9TIM14)相比基本定时器,新增输入捕获和输出比较功能,核心应用为PWM生成。

查表 数据手册中的引脚功能表获取用MCU的某个IO口去控制硬件、用哪个定时器的哪个通道

PWM核心原理
PWM(脉冲宽度调制)是通过周期性脉冲信号的高电平占空比调节,实现模拟信号输出的数字技术,核心参数:
-
频率:单位Hz,指每秒输出的脉冲个数,频率=定时器时钟/[(PSC+1)×(ARR+1)];
-
占空比:指一个周期内高电平持续时间占总周期的百分比,占空比=(CCR+1)/(ARR+1)×100%(PWM模式1,高电平有效)。

PWM模式分类(通过TIMx_CCMRx寄存器的OCxM位配置):


- PWM模式1:递增计数时,CNT<CCR时输出有效电平,CNT≥CCR时输出无效电平;
- PWM模式2:递增计数时,CNT<CCR时输出无效电平,CNT≥CCR时输出有效电平。
注:CCR(捕获/比较寄存器):16位寄存器,用于设定PWM占空比,取值范围0~ARR。

PWM配置实战(舵机控制)
舵机(如SG90)的控制要求:20ms周期(频率50Hz)的PWM信号,高电平时间0.5ms2.5ms对应0°180°角度。以下以TIM9_CH2(PE6引脚)生成舵机控制PWM为例。
硬件接线
- 舵机红色线:接5V电源(舵机工作电压3~7.2V,开发板5V供电即可);
- 舵机棕色线:接GND;
- 舵机黄色线:接TIM9_CH2(PE6引脚,复用功能GPIO_AF_TIM9)。

代码
#include "stm32f4xx.h"
/**
* @brief 延时微秒
* @param nus: 待延时时间,单位是微秒
* @retval 无
* @note 系统滴答定时器(Systick)采用21MHZ的时钟源
*/
void delay_us(u32 nus)
{
SysTick->CTRL = 0; // 关闭定时器
SysTick->LOAD = nus*21 - 1; // 设置重载值
SysTick->VAL = 0; // 清除当前值
SysTick->CTRL = 1; // 启动定时器
while ((SysTick->CTRL & 0x00010000)==0);// 等待时间到达
SysTick->CTRL = 0; // 关闭定时器
}
/**
* @brief 延时毫秒
* @param nms: 待延时时间,单位是毫秒
* @retval 无
* @note 系统滴答定时器(Systick)采用21MHZ的时钟源,该函数存在微小误差
*/
void delay_ms(u32 nms)
{
while(nms--)
{
SysTick->CTRL = 0; // 关闭定时器
SysTick->LOAD = 21*1000 - 1; // 设置重载值
SysTick->VAL = 0; // 清除当前值
SysTick->CTRL = 1; // 启动定时器
while ((SysTick->CTRL & 0x00010000)==0);// 等待时间到达
SysTick->CTRL = 0; // 关闭定时器
}
}
/**
* @brief 配置TIM9输出通道(用于舵机控制)
* @param 无
* @retval 无
*/
void TIM_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
// 1. 开启TIM9的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM9, ENABLE);
// 2. 开启GPIO端口时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
// 3. 配置GPIO引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; // 复用模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; // 100MHz输出速率
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; // 推挽输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ; // 上拉电阻
GPIO_Init(GPIOE, &GPIO_InitStructure);
// 4. 选择GPIO引脚要复用的功能(TIM9)
GPIO_PinAFConfig(GPIOE, GPIO_PinSource6, GPIO_AF_TIM9);
// 5. 配置定时器的时基(生成20ms周期)
TIM_TimeBaseStructure.TIM_Prescaler = 168-1; // TIM9挂载在APB2总线,分频后频率1MHz(1us计数1次)
TIM_TimeBaseStructure.TIM_Period = 20000-1; // 自动重载值,计数20000次对应20ms
TIM_TimeBaseStructure.TIM_ClockDivision = 0; // 不分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 递增计数模式
TIM_TimeBaseInit(TIM9, &TIM_TimeBaseStructure);
// 6. 配置TIM的输出比较(PWM模式)
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM1模式:递增计数时,CNT<CCR通道有效,否则无效
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 开启输出比较功能
TIM_OCInitStructure.TIM_Pulse = 500; // 舵机转动0度,对应0.5ms高电平(1us×500=500us=0.5ms)
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 高电平有效
TIM_OC2Init(TIM9, &TIM_OCInitStructure);
// 7. 使能TIM9通道2预装载寄存器(确保CCR值更新后在下一周期生效)
TIM_OC2PreloadConfig(TIM9, TIM_OCPreload_Enable);
// 8. 使能TIM9自动重载预装载寄存器(确保ARR值更新后同步生效)
TIM_ARRPreloadConfig(TIM9, ENABLE);
// 9. 开启定时器
TIM_Cmd(TIM9, ENABLE);
}
/**
* @brief 主程序(程序入口)
* @param 无
* @retval 无
*/
int main(void)
{
// 1. 进行NVIC优先级分组:分组4,抢占优先级4位(取值范围0~15)
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
// 2. 硬件初始化(仅初始化TIM9,用于舵机控制)
TIM_Config();
/* 无限循环 */
while (1)
{
// 角度为0度
TIM_SetCompare2(TIM9,500);
delay_ms(500);
// 角度为45度
TIM_SetCompare2(TIM9,1000);
delay_ms(500);
// 角度为90度
TIM_SetCompare2(TIM9,1500);
delay_ms(500);
// 角度为135度
TIM_SetCompare2(TIM9,2000);
delay_ms(500);
// 角度为180度
TIM_SetCompare2(TIM9,2500);
delay_ms(500); // 补充180度停留延时,使角度切换更均匀
}
}
PWM实战拓展(LED呼吸灯)
利用PWM占空比渐变实现LED呼吸灯效果,以TIM14_CH1(PF9引脚)为例,配置1kHz频率PWM,通过循环修改CCR值实现亮度渐变。
#include "stm32f4xx.h"
/**
* @brief TIM14_CH1(PF9)PWM初始化函数,配置1kHz频率
* @param 无
* @return 无
* @note TIM14挂载APB1总线,时钟84MHz;1kHz频率配置:PSC=83,ARR=999
* 频率=84e6/[(83+1)*(999+1)] = 84e6/(84*1000) = 1000Hz
*/
void TIM14_PWM_Init(void) {
// 1. 开启时钟(TIM14和GPIOF)
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM14, ENABLE);
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
// 2. 配置PF9为复用功能(AF9,对应TIM14_CH1)
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOF, &GPIO_InitStruct);
GPIO_PinAFConfig(GPIOF, GPIO_PinSource9, GPIO_AF_TIM14);
// 3. 配置时基
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_Prescaler = 83;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = 999;
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInit(TIM14, &TIM_TimeBaseInitStruct);
// 4. 配置PWM模式
TIM_OCInitTypeDef TIM_OCInitStruct;
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStruct.TIM_Pulse = 0; // 初始亮度最低
TIM_OC1Init(TIM14, &TIM_OCInitStruct);
// 5. 使能预装载
TIM_OC1PreloadConfig(TIM14, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM14, ENABLE);
// 6. 使能定时器
TIM_Cmd(TIM14, ENABLE);
}
int main(void) {
uint16_t ccr_val = 0;
uint8_t dir = 1; // 亮度变化方向:1=递增,0=递减
TIM14_PWM_Init();
while (1) {
if (dir) {
ccr_val++;
if (ccr_val > 999) dir = 0;
} else {
ccr_val--;
if (ccr_val <= 0) dir = 1;
}
TIM_SetCompare1(TIM14, ccr_val); // 更新占空比
delay_ms(5); // 控制渐变速度
}
}
软件模拟PWM
可以利用定时器基本定时功能,通过编程控制普通 IO 口输出高低电平并配合精准延时,软件模拟生成可调占空比的 PWM 信号;该方法不受硬件定时器通道限制,可用于任意普通输出 IO 口,通用性与可移植性更强。
模拟PWM配置(PF9口,50Hz)
int main(void)
{
// 配置NVIC优先级分组为4(仅抢占优先级,0-15)
NVIC_SetPriorityGrouping(NVIC_PriorityGroup_4);
// 初始化按键模块
PF9_Init();
// 主循环
while (1)
{
GPIO_ResetBits(GPIOF, GPIO_Pin_9); // 低电平
delay_ms(19);
GPIO_SetBits(GPIOF, GPIO_Pin_9); // 高电平
delay_ms(1);
}
}
示波器检测结果

关键注意事项
- 时钟使能:所有定时器和对应GPIO口必须先开启时钟,否则配置无效;
- 复用功能:PWM输出或输入捕获时,GPIO口需配置为复用模式,并正确设置AF值;
- 中断优先级:多个定时器中断同时使用时,需合理配置抢占优先级和响应优先级,避免中断冲突;
- 预装载寄存器:PWM模式下,建议使能CCR和ARR的预装载功能,保证波形稳定;
- 假设你在 STM32 的定时器中生成 PWM,且未开启预装载:
- ARR(周期)立即修改:如果在计数器计数过程中修改 ARR,当前周期会被强行截断 / 延长,导致这一个 PWM 周期的时长变得不规则(比如原本 1ms 的周期,中途改成 0.8ms,这个周期就会变成 0.8ms,而下一个周期才是正常的 1ms)。
- CCR(占空比)立即修改:如果在计数器计数过程中修改 CCR,比如计数器已经计数到 500,你突然把 CCR 从 600 改成 400,那么本周期的比较事件会提前触发,导致 PWM 的高 / 低电平时长被 “打断”,出现毛刺或不规则的电平跳变。
- 假设你在 STM32 的定时器中生成 PWM,且未开启预装载:
- 硬件 vs 软件PWM:硬件PWM精度高、CPU占用低,适合对时序要求严格的场景;软件PWM通用性强(任意IO口),但精度依赖定时器定时精度,CPU占用略高。

浙公网安备 33010602011771号