4. 定时器中断:基本定时器、通用定时器、高级定时器的中断配置
定时器中断,说白了就是让硬件定时器在“闹钟响了”的时候,自动跳进你写的中断服务函数里干活。我刚开始接触STM32那会儿,总觉得定时器配置特别绕,尤其是基本、通用、高级这三类,长得像但脾气完全不同。今天咱们就把它们掰开揉碎了讲清楚。
4.1 三类定时器的“人设”差异
先给个总览,方便你心里有个谱:
| 定时器类型 | 核心功能 | 中断源数量 | 典型应用 |
|---|---|---|---|
| 基本定时器(TIM6/TIM7) | 仅时基、触发DAC | 1个(更新中断) | 精准延时、DAC触发 |
| 通用定时器(TIM2~TIM5) | 时基+输入捕获+输出比较+PWM | 多个(更新、捕获、比较等) | PWM生成、脉冲计数 |
| 高级定时器(TIM1/TIM8) | 通用功能+互补输出+刹车+死区 | 多个+故障中断 | 电机控制、数字电源 |
嗯,这里要注意:基本定时器只有更新中断,说白了就是“计数值溢出”这一种中断。通用和高级就丰富多了,你甚至可以配置成“计数值刚好等于某个数”时触发中断。
核心记忆点:基本定时器是“单间”,通用定时器是“三室一厅”,高级定时器是“带花园的别墅”。配置复杂度依次递增,但功能也越强大。
4.2 基本定时器中断配置——最简模型
我个人习惯用基本定时器做系统心跳,因为它简单到几乎没有坑。配置流程就三步:
- 使能时钟——别漏了,漏了定时器不工作
- 设置预分频和自动重装值——决定中断频率
- 使能更新中断并启动定时器
举个例子,我想让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 知识体系总览
下面这张图帮你理清三类定时器中断的配置脉络:
嗯,看到这张图你应该能明白:基本定时器是入门,通用定时器是主力,高级定时器是进阶。我个人建议初学者先从基本定时器练手,把更新中断玩熟了,再逐步挑战通用和高级。
最后说一句:定时器中断配置看似繁琐,但只要你理解了“时钟→预分频→自动重装→中断使能”这条主线,剩下的都是细节。我在项目中调试定时器中断时,最常用的方法就是先让LED在中断里闪烁——灯亮了,说明配置对了。
可可精品资料库