定时器是STM32嵌入式开发中当之无愧的“核心外设”,从简单的毫秒级延时、定时中断,到复杂的PWM调速、输入捕获,几乎所有项目都离不开它。本文结合实战经验,从基础概念到代码落地,全方位拆解STM32定时器的使用技巧,尤其适合嵌入式新手快速上手。
一、定时器基础认知
1.1 定时器分类(以STM32F407ZGT6为例)
STM32的定时器按功能和复杂度分为三类:
- 基本定时器(TIM6、TIM7):仅支持定时中断,无IO通道,常用于简单定时;
- 通用定时器(TIM2-TIM5、TIM9-TIM14):支持定时中断、PWM输出、输入捕获,功能全面;
- 高级定时器(TIM1、TIM8):在通用定时器基础上,增加死区控制、刹车功能,适合电机控制等场景。
1.2 时基单元核心组成
无论哪种定时器,核心都是“时基单元”,由4部分组成:
- 时钟源:定时器的计数时钟(内部时钟/外部时钟/其他定时器触发);
- 预分频器(PSC):对时钟源分频,降低计数频率;
- 自动重装载寄存器(ARR):计数上限,计数器到该值后溢出;
- 计数器(CNT):当前计数值,支持向上/向下/中心对齐计数。
二、两类核心定时器:SysTick vs TIM外设
STM32中有两类“定时器”,定位不同,需按需选择:
2.1 内核级定时器:SysTick(滴答定时器)
SysTick是Cortex-M4内核自带的24位递减定时器,无需占用片上TIM外设,核心用于提供系统时基(比如HAL库的HAL_Delay)。
2.1.1 核心特性
- 归属:ARM Cortex-M4内核,所有M4芯片通用;
- 位数:24位递减计数器,最大计数值
2^24=16777216; - 时钟源:可选“内核时钟(168MHz)”或“AHB8分频(21MHz)”,HAL库默认AHB8分频;
- 触发机制:设定初值后,每1个时钟周期计数值-1,减至0触发中断,自动重装初值循环。
2.1.2 HAL_Delay实现原理(源码级解析)
HAL库的HAL_Delay是基于SysTick实现的阻塞延时函数,源码如下:
#define HAL_MAX_DELAY 0xFFFFFFFFU
HAL_TickFreqTypeDef uwTickFreq = HAL_TICK_FREQ_DEFAULT; // 默认1KHz(1ms)
__weak void HAL_Delay(uint32_t Delay)
{
uint32_t tickstart = HAL_GetTick(); // 获取初始时间戳
uint32_t wait = Delay;
// 防0操作:避免Delay=0时无等待,引入1ms误差
if (wait < HAL_MAX_DELAY)
{
wait += (uint32_t)(uwTickFreq);
}
// 死循环等待,直到时间差≥目标延时
while((HAL_GetTick() - tickstart) < wait)
{
}
}
⚠️ 关键注意:
HAL_Delay是阻塞函数,中断中禁止调用(会导致中断阻塞,甚至程序卡死)。
2.1.3 核心寄存器
SysTick仅需3个寄存器即可控制:
- CTRL:控制寄存器(定时器使能、时钟源选择、中断使能、计数标志);
- LOAD:重装载寄存器(存储计数初值);
- VAL:当前值寄存器(存储当前计数值)。
2.2 片上外设:TIM定时器(TIM1~TIM14)
TIM外设是嵌入式开发的“主力定时器”,支持定时中断、PWM、输入捕获等复杂功能,以下是实战配置与代码解析。
2.2.1 CubeMX配置步骤(核心)
- 时钟来源:默认选择“内部时钟(internal clock)”;
- 时基配置:设置预分频值(PSC)、计数方式(向上/向下)、自动重装载值(ARR);
- 中断配置:NVIC中开启定时器中断,设置中断优先级;
- 功能扩展:如需PWM/输入捕获,配置对应通道为“PWM输出/输入捕获模式”,并使能对应引脚。

📌 计算示例:
若时钟频率为4MHz,需实现1秒定时:
- 预分频值:
4000-1(分频后计数频率=4MHz/4000=1kHz,计数1次耗时1ms); - 自动重装载值:
1000-1(计数1000次耗时1ms×1000=1s)。

2.2.2 TIM定时器初始化代码(以TIM6为例)
/* TIM6 init function */
void MX_TIM6_Init(void)
{
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim6.Instance = TIM6;
htim6.Init.Prescaler = 84-1; // 预分频器:84分频→1MHz(1us/次)
htim6.Init.CounterMode = TIM_COUNTERMODE_UP; // 向上计数
htim6.Init.Period = 1000-1; // ARR:计数1000次→1ms
htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
// 开启定时器中断
HAL_TIM_Base_Start_IT(&htim6);
}
2.2.3 定时器中断回调实战(100ms翻转LED)
定时器中断无需主动调用,达到定时周期后自动触发,重写HAL库的弱回调函数即可实现自定义逻辑:
int count = 0;
// 定时器周期溢出回调函数(弱函数,可重写)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim6) // 判断定时器实例为TIM6
{
count++;
if(count>=100) // 1ms×100=100ms
{
count = 0;
// 翻转PE13引脚电平(控制LED)
HAL_GPIO_TogglePin(GPIOE,GPIO_PIN_13);
}
}
}
2.2.4 实战扩展:1秒串口上报“我来了”
结合定时器与串口,实现1秒定时上报,核心代码如下:
volatile int tim_count = 0; // 中断变量必须加volatile,防止编译器优化
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim == &htim6)
{
tim_count++;
if(tim_count>=1000)
{
tim_count = 0;
// 串口非阻塞发送(注意:strlen计算有效字符长度,避免多发\0)
HAL_UART_Transmit_IT(&huart1,(uint8_t*)"我来了",strlen((char*)"我来了"));
}
}
}
⚠️ 易错点:若用sizeof("我来了")会包含末尾的\0,导致串口多发1个无效字符,需用strlen计算有效长度。
三、PWM核心应用(基于TIM定时器)
PWM(脉宽调制)是TIM定时器的核心应用(电机调速、LED调光、舵机控制等),必须掌握原理与参数计算。
3.1 PWM核心原理
通过比较计数器(CNT)与捕获/比较寄存器(CCR)的值,控制IO口电平翻转:
- 向上计数模式(最常用):
CNT < CCR→ 高电平;CNT ≥ CCR→ 低电平; - 一个定时器最多输出4路PWM(4个通道),每个通道对应独立的CCR寄存器。
3.2 关键参数计算
// 分辨率(CCR的最小调整步距)
分辨率 = 1 / (ARR + 1)
// PWM频率
频率 = CK_PSC / [(PSC + 1) * (ARR + 1)]
// 占空比
占空比 = CCR / (ARR + 1)
📌 示例计算:
若CK_PSC=84MHz,PSC=84-1,ARR=100-1:
- 频率 = 84MHz / (84 × 100) = 10KHz;
- 分辨率 = 1/100 = 1%;
- 调整CCR值即可改变占空比(如CCR=50 → 占空比50%)。
四、开发避坑指南(必看)
- volatile关键字:定时器/中断相关变量必须加
volatile,防止编译器优化(确保每次从内存读取,而非寄存器缓存); - NVIC配置:定时器中断需设置优先级并使能,同一条中断线的定时器(如TIM1/TIM10)需避免优先级冲突;
- 总线挂载:TIM定时器分属APB1(低速,如TIM6/7)、APB2(高速,如TIM1/8)总线,需参考芯片手册确认;
- SysTick与TIM选择:简单延时用SysTick,复杂定时/PWM/输入捕获用TIM外设;
- 寄存器操作:STM32无位操作指令,需通过“按位与/按位或+位移”操作寄存器;
- 中断嵌套:定时器中断优先级需合理设置,避免高优先级中断阻塞核心业务逻辑。
五、总结
STM32定时器的核心是“分频+计数+中断/输出”,掌握分类、时基计算、CubeMX配置是基础,结合实战(定时中断、PWM)才能真正落地。
开发中建议:
- 简单延时用SysTick(HAL_Delay),但中断中禁用;
- 复杂定时/外设控制用TIM外设,善用HAL库简化开发;
- 结合芯片手册(定时器章节)理解寄存器细节,兼顾效率与可读性。
希望本文能帮助大家摆脱定时器“配置难、调试卡”的问题,真正做到学以致用!

1万+

被折叠的 条评论
为什么被折叠?



