摘要:本文系统讲解 STM32 定时器(TIM)的核心原理与四种常用工作模式。文章从定时器的基本概念和分类入手,介绍时钟来源、关键寄存器(PSC、ARR、CNT、CCR)以及定时时间计算公式;随后重点剖析基本定时中断、PWM 输出、输入捕获和编码器接口四种模式,并结合 CubeMX 配置与 HAL 库代码给出 LED 翻转、呼吸灯、PWM 频率/占空比测量等实战案例;最后总结 HAL 库使用步骤、常见注意事项以及 PSC/ARR 差 1、时钟源配置错误、中断不触发、PWM 无输出等高频问题的排查方法。
一、TIM 定时器是什么
STM32 的 TIM(Timer)是内核之外的独立硬件外设,核心是一个可编程计数器,在时钟驱动下自动累加/递减,达到设定值时产生事件或中断。它不占用 CPU 循环,是嵌入式中“精确延时、定时任务、波形生成、信号测量”的基础。
二、定时器分类(以 STM32F4 为例)
|
类型 |
代表型号 |
计数器位数 |
主要功能 |
|---|---|---|---|
|
基本定时器 |
TIM6、TIM7 |
16 位 |
仅定时 + 触发 DAC,无外部通道 |
|
通用定时器(全功能) |
TIM2、TIM3、TIM4、TIM5 |
TIM2/TIM5 为 32 位;TIM3/TIM4 为 16 位 |
定时中断、输入捕获、输出比较、PWM、编码器接口;均为 4 通道 |
|
通用定时器(简化) |
TIM9~TIM14 |
16 位 |
定时、PWM、输入捕获;TIM9/TIM12 为 2 通道,TIM10/11/13/14 为 1 通道 |
|
高级定时器 |
TIM1、TIM8 |
16 位 |
在通用基础上增加互补 PWM 输出、死区时间、刹车输入,多用于电机驱动 |
F4/H7 等系列部分定时器为 32 位(如 TIM2/TIM5),计数范围更大。
三、核心工作原理

上图是 TIM 定时器的内部结构框图,展示了预分频器(PSC)、自动重装载寄存器(ARR)、计数器(CNT)以及捕获/比较通道(CH1~CH4)之间的连接关系,是理解后续时钟配置和计数流程的基础。
1. 时钟来源
定时器挂在 APB1 或 APB2 总线上。当 APB 预分频系数 ≠ 1 时,定时器时钟 = APB 总线时钟 × 2。
以 F103(72 MHz 系统时钟)为例:
-
APB1 = 36 MHz → TIM2~7 时钟 = 72 MHz
-
APB2 = 72 MHz → TIM1 时钟 = 72 MHz
2. 关键寄存器
|
寄存器 |
作用 |
|
PSC(预分频器) |
对输入时钟再分频,实际计数时钟 = TIM_CLK / (PSC+1) |
|
ARR(自动重装载值) |
计数器计数上限,计到 ARR 后 |
|
CNT(计数器) |
当前计数值,硬件自动累加 |
|
CCR1~CCR4 |
捕获/比较寄存器,用于 PWM 占空比或输入捕获 |
|
DIER / SR |
中断使能 / 状态标志 |
3. 定时时间计算公式
举例:72 MHz 时钟,想产生 1 ms 中断:
-
取 PSC = 71 → 计数时钟 = 72 MHz / 72 = 1 MHz(1 μs 计一次)
-
取 ARR = 999 → 1000 × 1 μs = 1 ms
四、四种核心工作模式
配置定时器:咱们用的 F4 芯片有 8 个 TIM 定时器

下图是 STM32CubeMX 中 TIM 的时钟源选择界面,这里需要勾选 Internal Clock(内部时钟),让定时器使用 APB1 总线提供的时钟信号。
-
使能 TIM

-
定时器配置

上图是 TIM1 的参数配置界面,在这里设置预分频系数(Prescaler)和自动重装载值(Counter Period),这两个参数直接决定定时中断的时间间隔。

上图是 TIM1 的中断使能配置界面,勾选 TIM1 global interrupt 后,定时器溢出时才会触发更新中断,从而进入中断回调函数。
以下是在 Keil 工程中找到 TIM 定时器中断函数的方法:




1. 基本定时中断(最常用)
计数器
时触发更新中断(Update Interrupt),在中断服务函数里执行周期性任务。
// HAL 库示例:1 ms 定时中断
htim2.Instance = TIM2;
htim2.Init.Prescaler = 71; // 72MHz / 72 = 1MHz
htim2.Init.Period = 999; // 1MHz / 1000 = 1kHz → 1ms
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
HAL_TIM_Base_Init(&htim2);
HAL_TIM_Base_Start_IT(&htim2); // 启动定时中断
// 中断回调
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM2) {
// 每 1ms 执行一次
}
}
实战案例:TIM1 实现 LED 每秒翻转一次
下面以 STM32F4 系列为例,使用高级定时器 TIM1 实现 LED 每 1 秒翻转一次。系统时钟为 168 MHz,APB2 总线时钟为 84 MHz,由于 APB2 预分频系数为 2(不等于 1),TIM1 的时钟 = 84 MHz × 2 = 168 MHz。
第一步:CubeMX 配置
-
在 RCC 中配置 HSE 外部晶振,并在 Clock Configuration 中确认系统时钟为 168 MHz。
-
在 Timers 中选择 TIM1,勾选 Internal Clock(内部时钟),使 TIM1 使用 APB2 总线提供的时钟信号。
-
在 Parameter Settings 中设置:
-
Prescaler(预分频系数)= 1679,即 PSC = 1679,实际分频 = 168 MHz / (1679+1) = 100 kHz(10 μs 计一次)
-
Counter Period(自动重装载值)= 4999,即 ARR = 4999,溢出周期 = 5000 × 10 μs = 50 ms
-
Counter Mode = Up(向上计数)
-
-
在 NVIC Settings 中勾选 TIM1 update interrupt,使能 TIM1 更新中断。
-
在 GPIO 中配置一个普通输出引脚(如 PA5)作为 LED 控制引脚,输出模式为 Push-Pull,速度可选 Low。
-
生成代码(选择 MDK-ARM 或 STM32CubeIDE 均可)。
第二步:HAL 库完整代码
在 main.c 中,先定义一个全局变量用于计数:
// main.c 中定义全局变量
volatile uint16_t tim1_50ms_count = 0; // 50ms 中断计数
在 main() 函数中初始化 LED 引脚并启动 TIM1 定时中断:
int main(void)
{
HAL_Init();
SystemClock_Config();
// 初始化 LED 引脚(PA5)
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_5;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 启动 TIM1 定时中断
HAL_TIM_Base_Start_IT(&htim1);
while (1)
{
// 主循环可执行其他任务
}
}
在 stm32f4xx_it.c 中,TIM1 更新中断服务函数会调用 HAL 库的回调函数。在 main.c 末尾重写回调函数,实现 LED 每秒翻转一次:
// 中断回调:每 50ms 进入一次,累计 20 次(1 秒)翻转 LED
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM1)
{
tim1_50ms_count++;
if (tim1_50ms_count >= 20) // 20 × 50ms = 1000ms = 1s
{
tim1_50ms_count = 0;
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5); // 翻转 LED
}
}
}
第三步:预期现象
-
编译下载后,LED 以 1 秒间隔稳定翻转(亮 1 秒、灭 1 秒)。
-
若使用示波器测量 PA5 引脚,可观察到周期为 2 秒、占空比 50% 的方波。
-
若 LED 翻转速度异常,优先检查 PSC 和 ARR 是否差 1,以及 TIM1 时钟是否为 168 MHz。
2. PWM 输出(输出比较模式)
CNT 从 0 累加到 ARR,当 CNT < CCRx 时输出高电平,CNT ≥ CCRx 时输出低电平,从而产生占空比可调的方波。
频率 = f_TIM / [(PSC+1) × (ARR+1)]
占空比 = CCRx / (ARR+1)
// 输出 PWM,ARR=999(分辨率 1/1000)
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 500); // 50% 占空比
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
实战案例:TIM1_CH1 输出 1kHz、50% 占空比 PWM 驱动 LED 呼吸灯
下面以 STM32F4 系列为例,使用高级定时器 TIM1 的通道 1(PA8)输出 1 kHz、50% 占空比的 PWM 波形,并通过动态调节占空比实现 LED 呼吸灯效果。系统时钟为 168 MHz,APB2 总线时钟为 84 MHz,由于 APB2 预分频系数为 2(不等于 1),TIM1 的时钟 = 84 MHz × 2 = 168 MHz。
第一步:CubeMX 配置
-
在 RCC 中配置 HSE 外部晶振,并在 Clock Configuration 中确认系统时钟为 168 MHz,APB2 总线时钟为 84 MHz。
-
在 Timers 中选择 TIM1,勾选 Internal Clock(内部时钟),使 TIM1 使用 APB2 总线提供的时钟信号。
-
在 Parameter Settings 中设置:
-
Prescaler(预分频系数)= 167,即 PSC = 167,实际分频 = 168 MHz / (167+1) = 1 MHz(1 μs 计一次)
-
Counter Period(自动重装载值)= 999,即 ARR = 999,PWM 周期 = 1000 × 1 μs = 1 ms,对应频率 = 1 kHz
-
Counter Mode = Up(向上计数)
-
Pulse(初始 CCR1 值)= 500,即初始占空比 = 500 / 1000 = 50%
-
CH Polarity = High(高电平有效)
-
-
在 GPIO Settings 中确认 PA8 已被自动配置为 AF1(TIM1_CH1 复用功能),输出模式为 Push-Pull,速度可选 High。
-
在 NVIC Settings 中勾选 TIM1 update interrupt,使能 TIM1 更新中断(用于中断方式实现呼吸灯)。
-
生成代码(选择 MDK-ARM 或 STM32CubeIDE 均可)。
第二步:HAL 库完整代码
在 main.c 中,先定义一个全局变量用于呼吸灯方向控制:
// main.c 中定义全局变量
volatile uint16_t pwm_duty = 0; // 当前占空比(0~999)
uint8_t duty_direction = 1; // 1=递增,0=递减
在 main() 函数中初始化 TIM1 PWM 输出并启动。注意:高级定时器 TIM1 必须先使能主输出(MOE),否则即使配置正确也没有波形输出:
int main(void)
{
HAL_Init();
SystemClock_Config();
// 高级定时器 TIM1 必须使能主输出(MOE),否则无波形输出
__HAL_TIM_MOE_ENABLE(&htim1);
// 启动 TIM1_CH1 PWM 输出(PA8)
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
while (1)
{
// 呼吸灯:占空比从 0 递增到 999,再从 999 递减到 0
if (duty_direction == 1)
{
pwm_duty += 5;
if (pwm_duty >= 999)
{
pwm_duty = 999;
duty_direction = 0;
}
}
else
{
pwm_duty -= 5;
if (pwm_duty <= 0)
{
pwm_duty = 0;
duty_direction = 1;
}
}
// 更新 CCR1,改变占空比
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pwm_duty);
HAL_Delay(10); // 每 10ms 调整一次,呼吸周期约 4 秒
}
}
若希望呼吸灯在定时器中断中更新,可在 main.c 末尾重写回调函数,配合 TIM1 更新中断实现:
// 中断回调:每 1ms 进入一次,动态调整占空比实现呼吸灯
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM1)
{
if (duty_direction == 1)
{
pwm_duty += 1;
if (pwm_duty >= 999)
{
pwm_duty = 999;
duty_direction = 0;
}
}
else
{
pwm_duty -= 1;
if (pwm_duty <= 0)
{
pwm_duty = 0;
duty_direction = 1;
}
}
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pwm_duty);
}
}
第三步:预期现象
-
编译下载后,LED 呈现由暗到亮、再由亮到暗的呼吸效果,周期约 2 秒。
-
若使用示波器测量 PA8 引脚,可观察到频率为 1 kHz、占空比在 0%~100% 之间周期性变化的方波。
-
若 LED 亮度不变或没有输出,优先检查 GPIO 复用配置是否正确(PA8 必须为 AF1),以及 MOE 主输出是否已使能。
第四步:常见问题排查
-
无波形输出:检查 GPIO 是否配置为复用功能(AF1),确认
HAL_TIM_PWM_Start()已调用,并确认已执行__HAL_TIM_MOE_ENABLE(&htim1)使能主输出。 -
频率不对:确认 TIM1 时钟是否为 168 MHz,PSC 和 ARR 是否差 1(实际分频 = PSC+1,周期 = ARR+1)。
-
占空比不变化:确认
__HAL_TIM_SET_COMPARE()的通道参数为TIM_CHANNEL_1,且 CCR1 值在 0~ARR 范围内。 -
引脚无输出但代码正常:确认 PA8 是否被其他外设占用,检查 CubeMX 中 PA8 的复用功能是否确实为 TIM1_CH1(AF1)。
-
LED 亮度异常:检查 LED 限流电阻是否合适,PWM 频率过低(如低于 100 Hz)会导致 LED 明显闪烁。
3. 输入捕获
测量外部信号的频率、周期、脉宽。当通道引脚出现设定边沿(上升/下降)时,硬件自动锁存当前 CNT 值到 CCR 寄存器。
典型用法:两次捕获的 CNT 差值即为信号周期。
// HAL 库示例:输入捕获测量信号周期(以 TIM3_CH1 为例)
// 1. 初始化配置:TIM3 时钟 72MHz,预分频 71 → 计数时钟 1MHz(1us 计一次)
htim3.Instance = TIM3;
htim3.Init.Prescaler = 71; // 72MHz / 72 = 1MHz
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 0xFFFF; // 16 位计数器最大值
HAL_TIM_IC_Init(&htim3);
// 配置输入捕获通道:上升沿捕获,直接映射到 TI1
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING; // 上升沿触发捕获
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI; // 直接映射
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1; // 不分频
sConfigIC.ICFilter = 0; // 不滤波
HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1);
// 2. 启动函数:使能捕获通道和捕获中断
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
// 3. 中断回调处理:两次捕获差值即为信号周期
uint32_t capture_val = 0; // 上一次捕获的 CNT 值
uint32_t period_us = 0; // 测量到的周期(us)
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM3 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
uint32_t now = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_1);
period_us = (now >= capture_val) ? (now - capture_val) : (0xFFFF - capture_val + now);
capture_val = now; // 更新基准值,等待下一次捕获
}
}
实战案例:TIM3_CH1 测量外部 PWM 信号的频率和占空比
下面以 STM32F4 系列为例,使用通用定时器 TIM3 的通道 1(PA6)测量外部 PWM 信号的频率和占空比。系统时钟为 168 MHz,APB1 总线时钟为 84 MHz,由于 APB1 预分频系数为 2(不等于 1),TIM3 的时钟 = 84 MHz × 2 = 168 MHz。
第一步:CubeMX 配置
-
在 RCC 中配置 HSE 外部晶振,并在 Clock Configuration 中确认系统时钟为 168 MHz,APB1 总线时钟为 84 MHz。
-
在 Timers 中选择 TIM3,在 Channel1 下拉框中选择 Input Capture direct mode(输入捕获直接模式),使 TIM3_CH1 使用 APB1 总线提供的时钟信号。
-
在 Parameter Settings 中设置:
-
Prescaler(预分频系数)= 167,即 PSC = 167,实际分频 = 168 MHz / (167+1) = 1 MHz(1 μs 计一次)
-
Counter Period(自动重装载值)= 0xFFFF = 65535,即 ARR = 65535,16 位计数器最大值,避免测量长周期信号时溢出
-
Counter Mode = Up(向上计数)
-
Polarity Selection = Rising Edge(上升沿捕获)
-
IC Selection = Direct(直接映射到 TI1)
-
Prescaler(捕获分频) = No division(不分频)
-
Filter = 0(不滤波)
-
-
在 GPIO Settings 中确认 PA6 已被自动配置为 AF2(TIM3_CH1 复用功能),输入模式。
-
在 NVIC Settings 中勾选 TIM3 global interrupt,使能 TIM3 捕获中断。
-
生成代码(选择 MDK-ARM 或 STM32CubeIDE 均可)。
第二步:HAL 库完整代码
在 main.c 中,先定义全局变量用于保存捕获值和计算结果:
// main.c 中定义全局变量
volatile uint32_t ic_capture_val = 0; // 上一次捕获的 CNT 值
volatile uint32_t ic_period_us = 0; // 信号周期(us)
volatile uint32_t ic_freq_hz = 0; // 信号频率(Hz)
volatile uint32_t ic_duty = 0; // 占空比(0~1000,对应 0.0%~100.0%)
volatile uint8_t ic_capture_count = 0; // 捕获计数,用于区分上升沿和下降沿
在 main() 函数中初始化 TIM3 输入捕获并启动:
int main(void)
{
HAL_Init();
SystemClock_Config();
// 启动 TIM3_CH1 输入捕获(PA6)
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
while (1)
{
// 主循环可周期打印测量结果
// printf("Freq = %lu Hz, Duty = %lu.%lu%%\r\n",
// ic_freq_hz, ic_duty / 10, ic_duty % 10);
HAL_Delay(500);
}
}
在 stm32f4xx_it.c 中,TIM3 捕获中断服务函数会调用 HAL 库的回调函数。在 main.c 末尾重写回调函数,实现频率和占空比测量:
// 中断回调:测量 PWM 频率和占空比
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM3 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
uint32_t now = HAL_TIM_ReadCapturedValue(&htim3, TIM_CHANNEL_1);
if (ic_capture_count == 0)
{
// 第一次上升沿:记录基准值
ic_capture_val = now;
ic_capture_count = 1;
// 切换为下降沿捕获,测量高电平脉宽
__HAL_TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_1,
TIM_INPUTCHANNELPOLARITY_FALLING);
}
else if (ic_capture_count == 1)
{
// 下降沿:计算高电平脉宽(占空比)
uint32_t high_us = (now >= ic_capture_val) ?
(now - ic_capture_val) :
(0xFFFF - ic_capture_val + now);
ic_duty = high_us * 1000 / ic_period_us; // 占空比 = 高电平 / 周期
ic_capture_count = 2;
// 切换为上升沿捕获,测量完整周期
__HAL_TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_1,
TIM_INPUTCHANNELPOLARITY_RISING);
}
else
{
// 第二次上升沿:计算周期和频率
uint32_t period = (now >= ic_capture_val) ?
(now - ic_capture_val) :
(0xFFFF - ic_capture_val + now);
ic_period_us = period;
ic_freq_hz = 1000000UL / period; // 频率 = 1 / 周期
ic_capture_val = now;
ic_capture_count = 1;
// 切换为下降沿捕获,准备测量下一个高电平脉宽
__HAL_TIM_SET_CAPTUREPOLARITY(&htim3, TIM_CHANNEL_1,
TIM_INPUTCHANNELPOLARITY_FALLING);
}
}
}
第三步:预期现象
-
将信号发生器输出 1 kHz、50% 占空比的 PWM 接到 PA6,串口打印结果应为 Freq = 1000 Hz,Duty = 50.0%。
-
改变信号发生器频率或占空比,串口打印值应随之实时更新,误差通常在 ±1% 以内。
-
若使用示波器对比,测量结果应与示波器读数基本一致。
第四步:常见问题排查
-
捕获不到中断:检查 PA6 是否配置为复用功能(AF2),确认
HAL_TIM_IC_Start_IT()已调用,并确认 NVIC 中 TIM3 全局中断已使能。 -
频率测量偏大或偏小:确认 TIM3 时钟是否为 168 MHz,PSC 是否差 1(实际分频 = PSC+1)。若信号频率较高,可适当增大预分频以降低计数时钟。
-
占空比始终为 0 或 100%:检查边沿切换逻辑是否正确,确认
__HAL_TIM_SET_CAPTUREPOLARITY()的通道参数为TIM_CHANNEL_1。 -
测量结果跳变不稳定:信号抖动较大时,可在 CubeMX 中适当增大 Filter 值(如 0x0F)进行硬件滤波,或检查信号源是否稳定。
-
低频率信号测量不准:当信号频率低于约 15 Hz 时,周期可能超过 16 位计数器范围(65535 μs),此时应增大 PSC 降低计数时钟,或改用 32 位定时器(如 TIM2/TIM5)。
4. 编码器接口
通用定时器的 CH1、CH2 可配置为正交编码器接口,直接读取旋转编码器的脉冲和方向,无需外部中断,CPU 开销极低。
// HAL 库示例:正交编码器接口(以 TIM4_CH1/CH2 为例)
// 1. 初始化配置:编码器模式,计数范围 0~999
htim4.Instance = TIM4;
htim4.Init.Prescaler = 0; // 编码器模式不分频
htim4.Init.CounterMode = TIM_COUNTERMODE_UP;
htim4.Init.Period = 999; // 计数上限,溢出后回绕
HAL_TIM_Encoder_Init(&htim4);
// 配置编码器接口:CH1/CH2 均作为输入,上升沿+下降沿都计数(4 倍频)
sEncoderConfig.EncoderMode = TIM_ENCODERMODE_TI12; // TI1+TI2 双边沿计数
sEncoderConfig.IC1Polarity = TIM_INPUTCHANNELPOLARITY_RISING;
sEncoderConfig.IC2Polarity = TIM_INPUTCHANNELPOLARITY_RISING;
sEncoderConfig.IC1Prescaler = TIM_ICPSC_DIV1;
sEncoderConfig.IC2Prescaler = TIM_ICPSC_DIV1;
sEncoderConfig.IC1Filter = 0;
sEncoderConfig.IC2Filter = 0;
HAL_TIM_Encoder_ConfigChannel(&htim4, &sEncoderConfig, TIM_CHANNEL_ALL);
// 2. 启动函数:使能编码器接口
HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL);
// 3. 读取编码器计数和方向(可在主循环或定时中断中调用)
int16_t encoder_count = (int16_t)__HAL_TIM_GET_COUNTER(&htim4); // 当前脉冲计数
// 正转时计数递增,反转时计数递减;方向由计数增减方向自动判断
五、使用步骤总结(HAL 库)
使能时钟:
__HAL_RCC_TIM2_CLK_ENABLE()配置 GPIO(PWM/捕获时需要):复用推挽/输入模式
初始化定时器:设置 PSC、ARR、计数模式
配置通道:PWM 模式 / 输入捕获模式 / 编码器模式
启动:
HAL_TIM_Base_Start_IT()/HAL_TIM_PWM_Start()等编写中断回调:处理 $T_{溢出} = \frac{(PSC+1) \times (ARR+1)}{f_{TIM}}$ 或捕获事件
六、常见注意事项
-
PSC 和 ARR 都是从 0 开始计数,所以实际分频系数是 PSC+1,周期是 ARR+1,容易差 1。
-
定时器中断优先级要合理设置,避免被其他高优先级中断长时间阻塞。
-
高级定时器 TIM1/TIM8 的 PWM 输出需要额外使能主输出:
__HAL_TIM_MOE_ENABLE(&htim1),否则没有波形输出。 -
多个定时器共享中断向量时(如 TIM2 全局中断),需在回调中判断
htim->Instance区分来源。
五、常见问题排查
定时器配置过程中,最容易踩坑的往往是分频系数、时钟源、中断使能和高级定时器主输出这几类问题。下面总结 5 个高频问题及对应的排查思路。
1. PSC / ARR 差 1 导致定时时间或频率不对
现象:定时中断周期比预期慢或快,PWM 频率与计算值不符。
原因:PSC 和 ARR 都是从 0 开始计数的,实际分频系数是 PSC+1,计数周期是 ARR+1。很多初学者直接把 PSC 和 ARR 当作实际值代入公式,导致结果差 1。
解决方案:计算时务必使用 PSC+1 和 ARR+1。例如想用 72 MHz 时钟产生 1 ms 中断,应取 PSC = 71(实际分频 72)、ARR = 999(实际计数 1000),而不是 PSC = 72、ARR = 1000。
2. 时钟源配置错误导致定时器不工作或频率异常
现象:定时器完全没有计数,或计数频率与预期相差很大。
原因:CubeMX 中未勾选 Internal Clock(内部时钟),或 APB 预分频系数不为 1 时未意识到定时器时钟会翻倍。例如 F4 系列 APB1 为 84 MHz 且预分频系数为 2 时,TIM2~TIM5 的时钟实际是 168 MHz。
解决方案:在 CubeMX 的 Timers 中确认已勾选 Internal Clock;计算前先确认定时器挂在哪条总线上,并根据 APB 预分频系数正确推导定时器时钟(预分频系数不为 1 时定时器时钟 = APB 时钟 × 2)。
3. 中断不触发或回调不执行
现象:定时器配置正确,但中断服务函数始终不进入,或回调函数没有反应。
原因:常见原因有三个:一是 NVIC 中未勾选对应的定时器全局中断;二是启动函数用错,例如用 HAL_TIM_Base_Start() 而不是 HAL_TIM_Base_Start_IT();三是回调函数名写错或未在 main.c 中重写。
解决方案:在 CubeMX 的 NVIC Settings 中勾选对应定时器的全局中断;确认启动函数带 _IT 后缀;检查回调函数名是否为 HAL_TIM_PeriodElapsedCallback(),并在回调中通过 htim->Instance 判断中断来源。
4. PWM 无输出或输出为恒定电平
现象:代码编译下载后,引脚上测不到 PWM 波形,或输出一直为高/低电平。
原因:常见原因有三个:一是 GPIO 未配置为复用功能(AF);二是高级定时器 TIM1/TIM8 未使能主输出(MOE);三是通道启动函数未调用或通道号写错。
解决方案:确认 GPIO 已配置为对应定时器通道的复用功能(如 PA8 为 AF1);高级定时器必须执行 __HAL_TIM_MOE_ENABLE(&htim1) 使能主输出;确认已调用 HAL_TIM_PWM_Start() 且通道参数正确。
5. 输入捕获测量结果不稳定或始终为 0
现象:捕获到的频率或占空比跳变剧烈,或始终为 0 / 100%。
原因:常见原因有三个:一是捕获边沿切换逻辑有误,导致高电平脉宽或周期计算错误;二是信号抖动较大,未做硬件滤波;三是计数器溢出处理不当,导致差值计算错误。
解决方案:检查边沿切换逻辑,确认上升沿和下降沿交替捕获;在 CubeMX 中适当增大 Filter 值(如 0x0F)进行硬件滤波;处理计数器回绕时使用 (now >= capture_val) ? (now - capture_val) : (0xFFFF - capture_val + now) 的方式计算差值。
七、总结
本文系统梳理了 STM32 定时器(TIM)从原理到实战的完整知识体系。核心要点可归纳为以下几点:
- 理解时钟链路:定时器时钟由 APB 总线时钟经预分频推导而来,APB 预分频系数不为 1 时定时器时钟会翻倍,这是所有计算的前提。
- 掌握两个关键参数:PSC 决定计数时钟频率,ARR 决定计数周期,二者均从 0 开始计数,实际分频为 PSC+1、周期为 ARR+1,务必避免差 1 错误。
- 分清四种工作模式:基本定时中断用于周期性任务,PWM 输出用于波形生成,输入捕获用于测量外部信号,编码器接口用于读取旋转编码器,按需求选择对应模式。
- 牢记高级定时器特例:TIM1/TIM8 输出 PWM 前必须使能主输出(MOE),否则即使配置正确也没有波形。
- 善用中断回调:通过
htim->Instance区分中断来源,并确认启动函数带_IT后缀、NVIC 中断已使能。
建议读者先按「五、使用步骤总结」的流程跑通一个基本定时中断案例,再逐步扩展到 PWM、输入捕获和编码器接口,遇到问题时对照「五、常见问题排查」逐项定位。掌握定时器后,无论是 LED 控制、电机驱动还是信号测量,都能事半功倍。
1647



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



