4. 定时器中断:基本定时器、通用定时器、高级定时器的中断配置

定时器中断,说白了就是让硬件定时器在“闹钟响了”的时候,自动跳进你写的中断服务函数里干活。我刚开始接触STM32那会儿,总觉得定时器配置特别绕,尤其是基本、通用、高级这三类,长得像但脾气完全不同。今天咱们就把它们掰开揉碎了讲清楚。

4.1 三类定时器的“人设”差异

先给个总览,方便你心里有个谱:

定时器类型 核心功能 中断源数量 典型应用
基本定时器(TIM6/TIM7) 仅时基、触发DAC 1个(更新中断) 精准延时、DAC触发
通用定时器(TIM2~TIM5) 时基+输入捕获+输出比较+PWM 多个(更新、捕获、比较等) PWM生成、脉冲计数
高级定时器(TIM1/TIM8) 通用功能+互补输出+刹车+死区 多个+故障中断 电机控制、数字电源

嗯,这里要注意:基本定时器只有更新中断,说白了就是“计数值溢出”这一种中断。通用和高级就丰富多了,你甚至可以配置成“计数值刚好等于某个数”时触发中断。

核心记忆点:基本定时器是“单间”,通用定时器是“三室一厅”,高级定时器是“带花园的别墅”。配置复杂度依次递增,但功能也越强大。

4.2 基本定时器中断配置——最简模型

我个人习惯用基本定时器做系统心跳,因为它简单到几乎没有坑。配置流程就三步:

  1. 使能时钟——别漏了,漏了定时器不工作
  2. 设置预分频和自动重装值——决定中断频率
  3. 使能更新中断并启动定时器

举个例子,我想让TIM6每1ms触发一次中断:

// 假设系统时钟72MHz,APB1时钟36MHz(TIM6挂载在APB1上)
// 注意:APB1预分频不为1时,定时器时钟会翻倍

RCC->APB1ENR |= RCC_APB1ENR_TIM6EN;   // 使能TIM6时钟

TIM6->PSC = 71;   // 预分频:36MHz / (71+1) = 500kHz
TIM6->ARR = 499;  // 自动重装:500kHz / (499+1) = 1kHz → 1ms

TIM6->DIER |= TIM_DIER_UIE;  // 使能更新中断
NVIC_EnableIRQ(TIM6_DAC_IRQn); // 使能NVIC中断

TIM6->CR1 |= TIM_CR1_CEN;    // 启动定时器

中断服务函数里记得清标志位:

void TIM6_DAC_IRQHandler(void) {
    if(TIM6->SR & TIM_SR_UIF) {
        TIM6->SR = ~TIM_SR_UIF;  // 写0清除标志位
        // 你的1ms任务代码
    }
}

小技巧:我曾经在项目里忘了清标志位,结果中断一直进一直进,CPU直接卡死在中断里。嗯,从那以后我每次写完中断函数第一件事就是检查标志位清除。

4.3 通用定时器中断——多中断源管理

通用定时器的中断就丰富多了。除了更新中断,还有输入捕获中断、输出比较中断等。你想想看,一个定时器可以同时做PWM输出和脉冲计数,每个事件都能独立触发中断。

配置一个输出比较中断,让它在计数值到达500时触发:

// 以TIM2为例,配置通道1输出比较中断
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;

TIM2->PSC = 71;   // 1MHz计数频率
TIM2->ARR = 999;  // 自动重装1000

TIM2->CCR1 = 500; // 比较值设为500
TIM2->CCMR1 |= TIM_CCMR1_OC1M_0 | TIM_CCMR1_OC1M_1; // 配置为输出比较模式
TIM2->CCER |= TIM_CCER_CC1E;  // 使能通道1输出

TIM2->DIER |= TIM_DIER_CC1IE; // 使能通道1比较中断
NVIC_EnableIRQ(TIM2_IRQn);

TIM2->CR1 |= TIM_CR1_CEN;

中断处理时要注意区分中断源:

void TIM2_IRQHandler(void) {
    if(TIM2->SR & TIM_SR_CC1IF) {
        TIM2->SR = ~TIM_SR_CC1IF;
        // 比较匹配事件处理
    }
    if(TIM2->SR & TIM_SR_UIF) {
        TIM2->SR = ~TIM_SR_UIF;
        // 更新事件处理
    }
}

避坑指南:我曾经在项目里同时使能了更新中断和比较中断,结果在中断服务函数里只处理了比较中断,忘了处理更新中断。导致更新中断标志位一直挂起,系统频繁进入中断但什么也不做。记住:使能了的中断,服务函数里必须处理!

4.4 高级定时器中断——多了个“刹车”

高级定时器在通用定时器的基础上,增加了刹车中断(Break Interrupt)和互补输出相关的故障中断。这在电机控制里特别重要——一旦检测到过流,刹车中断能立即关断PWM输出。

配置刹车中断的要点:

// 以TIM1为例,使能刹车中断
TIM1->BDTR |= TIM_BDTR_BKE;   // 使能刹车功能
TIM1->BDTR |= TIM_BDTR_BKP;   // 刹车输入极性(高电平有效)

TIM1->DIER |= TIM_DIER_BIE;   // 使能刹车中断
NVIC_EnableIRQ(TIM1_BRK_IRQn);

// 刹车中断服务函数
void TIM1_BRK_IRQHandler(void) {
    if(TIM1->SR & TIM_SR_BIF) {
        TIM1->SR = ~TIM_SR_BIF;
        // 紧急停机处理
        // 比如:关闭所有PWM输出,记录故障状态
    }
}

经验之谈:高级定时器的刹车中断优先级我一般设到最高。为什么?因为这是安全相关的。我在做伺服驱动器项目时,刹车中断响应慢了1微秒,电机就烧了。从那以后,刹车中断优先级我永远设成0。

4.5 中断优先级与嵌套——别让中断“打架”

多个定时器中断同时发生时,优先级配置就很重要了。我见过不少新手把所有中断优先级设成一样,结果系统响应乱套。

我的建议:

  • 时间敏感的中断(如刹车中断、PWM周期中断)→ 高优先级
  • 非时间敏感的中断(如普通定时器更新中断)→ 低优先级
  • 避免在中断里做耗时操作——中断服务函数越短越好
// NVIC优先级分组:抢占优先级2位,子优先级2位
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

// 刹车中断:抢占优先级0,子优先级0
NVIC_SetPriority(TIM1_BRK_IRQn, 0);

// 通用定时器更新中断:抢占优先级2,子优先级0
NVIC_SetPriority(TIM2_IRQn, (2 << 2) | 0);

// 基本定时器更新中断:抢占优先级3,子优先级0
NVIC_SetPriority(TIM6_DAC_IRQn, (3 << 2) | 0);

小提示:中断优先级分组一旦设定,整个工程里就不要改了。我见过有人在不同文件里设了不同的分组,结果中断优先级乱成一锅粥。建议在main函数一开始就设置好。

4.6 知识体系总览

下面这张图帮你理清三类定时器中断的配置脉络:

定时器中断配置知识体系 基本定时器 通用定时器 高级定时器 中断源:仅更新中断 配置步骤: 1. 使能时钟 2. 设置PSC+ARR 3. 使能UIE中断 4. 启动定时器 典型应用: 系统心跳、DAC触发 中断源:更新/捕获/比较 配置步骤: 1. 使能时钟 2. 设置PSC+ARR 3. 配置通道模式 4. 使能对应中断 5. 区分中断源处理 典型应用: PWM、脉冲计数 中断源:通用+刹车 配置步骤: 1. 通用配置 2. 配置BDTR刹车 3. 使能BIE中断 4. 设最高优先级 5. 紧急停机处理 典型应用: 电机控制、数字电源 复杂度递增 · 功能递增 · 中断源递增

嗯,看到这张图你应该能明白:基本定时器是入门,通用定时器是主力,高级定时器是进阶。我个人建议初学者先从基本定时器练手,把更新中断玩熟了,再逐步挑战通用和高级。

最后说一句:定时器中断配置看似繁琐,但只要你理解了“时钟→预分频→自动重装→中断使能”这条主线,剩下的都是细节。我在项目中调试定时器中断时,最常用的方法就是先让LED在中断里闪烁——灯亮了,说明配置对了。


可可精品资料库