定时器作为微控制器不可或缺的外设,在STM32开发中扮演着核心角色。尽管其内部结构相对复杂,但只要掌握了GPIO、串口通信等基础外设,理解定时器的工作原理并加以应用并非难事。
定时器基本介绍
1.1 STM32定时器
简单来说,定时器就是存在于STM32单片机中的一个用于精确计时的外设模块。STM32F103系列总共有8个定时器,分别是2个高级定时器(TIM1、TIM8),4个通用定时器(TIM2、TIM3、TIM4、TIM5)和2个基本定时器(TIM6、TIM7),具体型号分布如下表所示:

这三种定时器的主要区别在于其功能丰富程度:

概括来讲:高级定时器具备完整的捕获/比较通道和互补输出功能;通用定时器拥有捕获/比较通道;而基本定时器则没有以上两者,功能最为基础。
1.2 通用定时器功能和特点
在实际项目中,我们最常使用的是高级定时器和通用定时器,且高级定时器也常被配置为通用功能。下面以通用定时器为例,详细罗列其核心功能与特点:
- 位于低速的 APB1总线上。
- 具备16位向上、向下、向上/向下(中心对齐) 计数模式,并包含自动装载计数器(TIMx_CNT)。
- 拥有16位可编程预分频器(TIMx_PSC),可在1~65535之间任意设置分频系数,并且支持运行时修改。
- 提供4个独立通道(TIMx_CH1~4),这些通道可用于:输入捕获、输出比较、PWM生成(边沿或中心对齐模式)、单脉冲模式输出。
- 支持使用外部信号(TIMx_ETR) 控制定时器,以及定时器之间的互连同步。
- 在以下事件发生时可以产生中断或DMA请求(共6个独立的IRQ/DMA请求生成器):
- 更新事件:计数器向上/向下溢出,或计数器被初始化。
- 触发事件:计数器启动、停止、初始化或由内/外部触发计数。
- 输入捕获事件。
- 输出比较事件。
- 支持针对定位的增量(正交)编码器和霍尔传感器电路。
- STM32的通用定时器常用于测量输入信号的脉冲宽度(输入捕获) 或产生输出波形(输出比较和PWM)。
- 通过结合定时器预分频器和RCC时钟控制器预分频器,可以对脉冲长度和波形周期进行从微秒到毫秒级别的精细调整。每个通用定时器都是完全独立的。
1.3 计数器模式
通用定时器支持三种基本的计数模式:
- 向上计数模式:计数器从0开始计数,达到自动加载值(TIMx_ARR)后,复位到0重新开始,并产生一个计数器溢出事件。
- 向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后重新从ARR值开始计数,并产生一个计数器向下溢出事件。
- 中央对齐模式(向上/向下计数):计数器从0计数到(ARR-1),产生一个溢出事件,然后向下计数到1并再次产生溢出事件,如此循环。

1.4 定时器工作原理
1.4.1 定时器框图
理解定时器的框图是掌握其工作原理的关键。许多开发者仅通过库函数配置实现了功能,却忽略了底层原理,这可能导致在复杂应用设计时出现错误。因此,深入理解下图所示的工作框图至关重要。

整个框图可以划分为四大功能部分:
- 时钟产生器:负责选择并提供定时器的计数时钟源。
- 时基单元:定时器的核心,包含计数器、预分频器和自动重装载寄存器。
- 输入捕获:用于测量外部信号的脉宽或周期。
- 输出比较:用于产生PWM等输出波形。
1.4.2 时钟产生器部分
STM32定时器有四种时钟源可供选择(图中蓝色部分):
- 内部时钟(CK_INT):最常用的时钟源,来自APB总线。
- 外部时钟模式1:外部触发输入(ETR):通过特定的外部引脚输入时钟。
- 内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,实现定时器级联。
- 外部时钟模式2:外部输入脚(TIx):通过捕获/比较通道的引脚输入时钟。

1.4.3 时基单元
时基单元是定时器的心脏,它包含三个核心寄存器:
- 计数器寄存器(TIMx_CNT):进行实际的向上、向下或中心对齐计数。
- 预分频器寄存器(TIMx_PSC):对输入时钟进行1~65535的分频,从而改变计数器的实际计数频率。
- 自动重装载寄存器(TIMx_ARR):设定计数器的周期。当使能了预装载功能(ARPE位),ARR的值会在更新事件发生时才生效。
1.4.4 输入捕获通道
- 输入通道IC1、IC2和IC3、IC4可以分别映射到外部引脚TI1、TI2和TI3、TI4。
- 当检测到输入信号的边沿时,当前计数器的值会被锁存到对应的16位捕获/比较寄存器中。
- 每次捕获发生时,相应的标志位(CCxIF)会被置1。如果中断或DMA使能,则会触发中断或DMA请求。

1.4.5 输出比较通道(PWM)
(1)PWM信号的产生
定时器通过比较计数器值(CNT)与捕获/比较寄存器值(CCRx)来产生PWM信号。ARR寄存器决定了PWM的周期,CCRx寄存器则决定了占空比。
(2)PWM对齐模式
PWM输出有两种对齐模式,主要影响在中心对齐计数模式下的波形对称性:

定时器中断应用
2.1 内部时钟选择
通用定时器的时钟通常来自APB1总线。这里有一个关键点:除非APB1的分频系数设置为1,否则通用定时器的时钟频率等于APB1时钟频率的2倍。
在默认调用 SystemInit 函数的情况下,系统时钟为72MHz,APB1预分频系数为2,因此APB1时钟为36MHz。根据上述规则,连接到APB1的通用定时器(如TIM2~TIM7)的时钟最终为 36MHz * 2 = 72MHz。


2.2 计数器模式时序详解
向下计数模式(时钟分频因子=1):
计数器从ARR值开始递减,减到0时产生更新事件并重置。


向上计数模式(时钟分频因子=1):
计数器从0开始递增,达到ARR值时产生更新事件并清零。


中央对齐计数模式(时钟分频因子=1, ARR=6):
计数器从0向上计数到ARR,然后向下计数到1,如此往复,在计数方向改变时可能产生更新事件。


2.3 定时器中断实验相关寄存器
实现定时器中断,需要配置以下几个关键寄存器:
- 计数器当前值寄存器(TIMx_CNT)
存储计数器的当前值。

- 预分频寄存器(TIMx_PSC)
设置对输入时钟CK_PSC的分频系数,实际计数器时钟CK_CNT = fCK_PSC / (PSC[15:0] + 1)。

- 自动重装载寄存器(TIMx_ARR)
存储计数器的重载值,即计数周期。

- 控制寄存器1(TIMx_CR1)
控制计数器的基本行为,如计数方向(DIR位)、使能(CEN位)等。

- DMA/中断使能寄存器(TIMx_DIER)
用于使能或禁止各类定时器中断,如更新中断(UIE位)、捕获/比较中断(CCxIE位)等。

2.4 常用库函数
定时器时基初始化:
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
其配置结构体如下:
typedef struct
{
uint16_t TIM_Prescaler; // 预分频值 (PSC)
uint16_t TIM_CounterMode; // 计数模式 (向上、向下等)
uint16_t TIM_Period; // 自动重载值 (ARR)
uint16_t TIM_ClockDivision; // 时钟分频 (与死区时间相关,通常为TIM_CKD_DIV1)
uint8_t TIM_RepetitionCounter; // 重复计数器 (高级定时器用)
} TIM_TimeBaseInitTypeDef;
典型初始化代码示例:
TIM_TimeBaseStructure.TIM_Period = 4999;
TIM_TimeBaseStructure.TIM_Prescaler = 7199;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
定时器使能函数:
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
定时器中断使能函数:
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
状态标志位获取和清除函数:
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
2.5 定时器中断实现步骤
- 使能定时器时钟:
RCC_APB1PeriphClockCmd();
- 初始化定时器:配置ARR、PSC等参数。
TIM_TimeBaseInit();
- 开启定时器中断并配置NVIC:
TIM_ITConfig(); 与 NVIC_Init();
- 使能定时器:
TIM_Cmd();
- 编写中断服务函数:
TIMx_IRQHandler();
2.6 应用实例
下面是一个使用通用定时器3实现500ms中断的示例代码,中断服务函数中控制LED状态翻转。定时时间 T = (ARR+1) * (PSC+1) / Tclk。假设系统时钟72MHz,设置 ARR=4999, PSC=7199,则 T = 5000 * 7200 / 72,000,000 = 0.5s。
//timer.c源文件
#include "timer.h"
#include "led.h"
//通用定时器3中断初始化
//时钟选择为APB1的2倍,APB1默认分频后为36M,故TIM3时钟为72M
//arr:自动重装值。
//psc:时钟预分频数
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能
//定时器TIM3初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
//中断优先级NVIC设置
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级3级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
TIM_Cmd(TIM3, ENABLE); //使能TIMx
}
//定时器3中断服务程序
void TIM3_IRQHandler(void) //TIM3中断
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查TIM3更新中断发生与否
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update ); //清除TIMx更新中断标志
LED1=!LED1;
}
}
定时器PWM输出实验
3.1 通用定时器PWM概述
PWM (Pulse Width Modulation),即脉冲宽度调制,通过对一系列脉冲的宽度进行调制,来等效地获得所需波形。它本质上是一种用数字信号对模拟信号电平进行编码的方法。占空比是指一个周期内,高电平时间占总周期的百分比。
在STM32中,可以利用定时器的输出比较功能来产生PWM波:PWM模式的频率由TIMx_ARR寄存器决定,占空比则由TIMx_CCRx寄存器决定。其工作原理如下图所示:

横轴是时间,纵轴是计数器CNT的值。CNT值随时间从0递增到ARR,然后复位,循环往复。CCRx是比较值。通过配置,当CNT < CCRx时输出一种电平,反之输出另一种电平,这样就产生了PWM波形。调节ARR可改变周期,调节CCRx则可改变占空比。
以通道1为例,PWM信号的产生路径如下:

信号流向:时钟源(CNT或ETRF) -> 输出模式控制器(由OC1M[2:0]位配置PWM模式) -> 极性选择器(由CC1P位设置) -> 输出使能门控(由CC1E位控制) -> 最终从OC1引脚输出。
关键寄存器位:
- CCR1:捕获/比较寄存器,设置比较值。
- CCMR1中的OC1M[2:0]:设置为110(PWM模式1)或111(PWM模式2)。
- CCER中的CC1P:设置输出极性。0:高电平有效;1:低电平有效。
- CCER中的CC1E:输出使能。0:关闭;1:打开。
3.2 PWM模式
PWM有模式1和模式2两种,区别在于有效电平的判定逻辑(参考TIMx_CCMR1寄存器的OC1M位描述):

可以简单理解为:
- PWM模式1:计数器值小于比较值时,通道为有效电平。
- PWM模式2:计数器值大于比较值时,通道为有效电平。
“有效电平”的具体高低由CCxP位进一步决定。理解这一点对正确配置PWM至关重要。
下图展示了PWM模式1在向上计数时的波形实例:

3.3 相关寄存器介绍
- 捕获/比较寄存器1(TIMx_CCR1)
当通道配置为输出时,用于写入PWM的比较值(占空比)。

- 捕获/比较模式寄存器1(TIMx_CCMR1)
每个寄存器控制两个通道,其中OCxM位用于设置PWM模式。


- 捕获/比较使能寄存器(TIMx_CCER)
CCxE位使能输出,CCxP位设置输出极性。

- 自动重装载寄存器(TIMx_ARR)
设定PWM的周期,通常通过库函数配置,无需直接操作寄存器。

3.4 定时器输出通道引脚
PWM输出需要映射到具体的物理引脚。与定时器中断不同,必须配置正确的引脚复用功能。以下是TIM3通道引脚的重映射关系:

3.5 定时器PWM库函数配置
PWM输出配置在初始化时基之后,还需初始化输出比较通道。
输出比较初始化函数:
void TIM_OCxInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
其结构体成员如下:
typedef struct
{
uint16_t TIM_OCMode; // PWM模式1或模式2 (TIM_OCMode_PWM1/2)
uint16_t TIM_OutputState; // 输出使能 (TIM_OutputState_Enable/Disable)
uint16_t TIM_OutputNState; // 互补输出使能 (高级定时器用)
uint16_t TIM_Pulse; // 比较值,即CCRx的初始值
uint16_t TIM_OCPolarity; // 输出极性 (TIM_OCPolarity_High/Low)
uint16_t TIM_OCNPolarity;
uint16_t TIM_OCIdleState;
uint16_t TIM_OCNIdleState;
} TIM_OCInitTypeDef;
初始化实例(配置TIM3通道2):
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //PWM模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
TIM_OCInitStructure.TIM_Pulse = 100; //初始占空比对应的比较值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性高
TIM_OC2Init(TIM3, &TIM_OCInitStructure); //初始化TIM3的通道2
动态设置比较值函数:
void TIM_SetCompareX(TIM_TypeDef* TIMx, uint16_t Compare2); // X为通道号,如SetCompare2
使能通道预装载(使CCR值的修改在更新事件生效,防止毛刺):
void TIM_OCxPreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
使能ARR预装载(同理):
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
PWM输出配置步骤:
- 使能时钟:使能定时器时钟 (
RCC_APB1PeriphClockCmd) 和对应GPIO时钟 (RCC_APB2PeriphClockCmd)。
- 初始化IO为复用推挽输出:
GPIO_Init,模式设为 GPIO_Mode_AF_PP。
- 引脚重映射(如果需要):开启AFIO时钟 (
RCC_APB2Periph_AFIO),并调用 GPIO_PinRemapConfig。
- 初始化定时器时基单元:
TIM_TimeBaseInit,设定ARR和PSC。
- 初始化输出比较参数:
TIM_OCxInit,设定PWM模式和初始占空比。
- 使能预装载寄存器:
TIM_OCxPreloadConfig 和 TIM_ARRPreloadConfig。
- 使能定时器:
TIM_Cmd。
- 动态调节占空比:在程序中调用
TIM_SetCompareX 改变CCRx的值。
3.6 实例:呼吸灯
本例使用TIM3的通道2(映射到PB5)输出PWM,通过循环改变比较值CCR2来调节LED亮度,实现呼吸灯效果。
//timer.c源文件
#include "timer.h"
#include "led.h"
#include "usart.h"
//TIM3 PWM部分初始化
//arr:自动重装值(决定频率)
//psc:时钟预分频数
void TIM3_PWM_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //使能定时器3时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE); //使能GPIOB和AFIO时钟
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //Timer3部分重映射 TIM3_CH2->PB5
//设置PB5为复用推挽输出,用于输出TIM3 CH2的PWM波形
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//初始化TIM3时基单元
TIM_TimeBaseStructure.TIM_Period = arr;
TIM_TimeBaseStructure.TIM_Prescaler = psc;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
//初始化TIM3通道2为PWM模式2
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC2Init(TIM3, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能TIM3 CCR2的预装载寄存器
TIM_Cmd(TIM3, ENABLE); //使能TIM3
}
在主循环中,可以不断调整 TIM_SetCompare2(TIM3, pwm_val) 的 pwm_val 值,使其在0到ARR之间循环变化,即可观察到LED亮度平滑变化。
总结
本文系统地讲解了STM32通用定时器的基本原理、寄存器功能,并通过定时器中断和PWM输出两个经典应用,给出了从寄存器配置到C语言代码的完整实现流程。理解时钟系统(如APB1的倍频关系)和计数器的工作模式(向上/向下/中心对齐)是正确配置定时器的计算机基础。对于PWM应用,关键在于理清模式1/2与有效电平的关系,以及正确完成引脚复用和重映射配置。掌握这些核心概念和网络/系统层面的硬件操作,便能驾驭定时器这一强大外设,满足各类精准定时和波形生成的需求。希望这篇详解能帮助你在嵌入式开发路上更进一步。欢迎在云栈社区交流讨论更多技术细节。