news 2026/4/28 20:33:21

STM32在Keil5中的中断配置:手把手教程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
STM32在Keil5中的中断配置:手把手教程

STM32中断配置实战:从Keil5底层逻辑讲透NVIC、EXTI与定时器

你有没有遇到过这样的情况?
按键按下后,程序毫无反应;
定时器设好了1秒中断,结果迟迟不进ISR;
更离谱的是,一进中断就死机——断点停在HardFault_Handler里,变量全乱套。

别急。这些问题背后,往往不是代码写错了,而是你没真正搞懂STM32的中断系统是如何联动工作的

今天我们就以“Keil5 + STM32F103”为平台,带你从硬件架构到软件实现,一步步揭开中断机制的神秘面纱。不靠CubeMX自动生成代码,只用最原始的手动配置方式,让你彻底掌握这套嵌入式开发中的“核心内功”。


为什么你的中断总是进不去?

先别急着看代码。我们得明白一件事:在STM32中,一个外部事件要变成CPU能响应的中断,需要跨越至少三层关卡

  1. 物理层:GPIO引脚电平变化;
  2. 外设层:EXTI或TIM捕获信号并生成中断请求;
  3. 内核层:NVIC判断优先级、触发跳转;
  4. 链接层:向量表指向正确的ISR函数地址。

任何一层出问题,都会导致“中断失联”。而Keil5作为开发环境,正是串联起这一切的关键工具链。

接下来我们就按这个逻辑链条,逐层拆解。


第一步:让PA0能触发中断 — EXTI到底怎么工作?

假设我们要用PA0接一个轻触按键,下降沿触发中断。很多人直接写GPIO初始化就完事了,但忽略了关键一步:PA0并不天然连到EXTI0

引脚映射必须手动开启

STM32允许不同端口的Pin0共享EXTI0线(比如PA0、PB0、PC0都能映射到EXTI0),但同一时间只能有一个有效。这就需要通过SYSCFG寄存器来指定到底是哪个端口。

而这一步的前提是:必须打开AFIO时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);

很多初学者只开了GPIOA时钟,却忘了AFIO,结果SYSCFG配置无效,EXTI根本收不到信号——这就是典型的“配置无效但无报错”的坑。

接着进行映射:

SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0);

这句代码的本质是操作SYSCFG->EXTICR[0]寄存器,把Bit[3:0] 设置为0(对应GPIOA)。如果你用的是PB0,则应设为1。

✅ 小贴士:EXTI_PortSourceGPIOxEXTI_PinSourceY都是宏定义,查看stm32f10x_exti.h即可找到对应关系。


第二步:告诉EXTI什么时候该发中断

现在PA0已经接入EXTI0了,但它还不知道“什么时候才算触发”。

我们需要设置边沿检测:

EXTI_InitTypeDef EXTI_InitStruct; EXTI_InitStruct.EXTI_Line = EXTI_Line0; EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt; // 区分中断 vs 事件 EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Falling; // 下降沿触发 EXTI_InitStruct.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStruct);

这里有两个易错点:

  • EXTI_Mode_Interrupt表示产生中断,会送到NVIC;
  • 如果选EXTI_Mode_Event,则只生成内部事件脉冲,可用于唤醒DMA或低功耗模式,但不会进入CPU中断流程。

另外,上升沿/下降沿/双边沿的选择也影响稳定性。机械按键建议使用下降沿触发 + 软件消抖,避免频繁误触发。


第三步:NVIC才是真正的“调度中心”

很多人以为只要EXTI使能了就能进中断,其实还差最后一步:NVIC使能

你可以把NVIC想象成一个带优先级排队系统的门卫。即使外设喊“有事!”,如果门卫没被授权开门,谁也进不来。

NVIC_InitTypeDef NVIC_InitStruct; NVIC_InitStruct.NVIC_IRQChannel = EXTI0_IRQn; // 指定中断源 NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; // 抢占优先级 NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; // 子优先级 NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct);

注意这里的EXTI0_IRQn是什么?它来自哪里?

答案是:启动文件 startup_stm32f103xb.s 中定义的中断向量表

打开这个文件,你会看到类似这样的一行:

DCD EXTI0_IRQHandler ; External Line0

这意味着当NVIC收到EXTI0的中断请求时,就会去查找符号EXTI0_IRQHandler的地址并跳转过去。

所以你必须保证:
- 函数名完全一致(区分大小写);
- 函数位于stm32f10x_it.c或已被包含的中断处理文件中;
- 没有重复定义或拼写错误。

否则,即使中断来了,也会跳进Default_Handler然后卡住。


写对ISR:不只是翻转LED那么简单

来看最常见的中断服务函数写法:

void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) != RESET) { GPIO_WriteBit(GPIOC, GPIO_Pin_13, !GPIO_ReadOutputDataBit(GPIOC, GPIO_Pin_13)); EXTI_ClearITPendingBit(EXTI_Line0); } }

这段代码看似没问题,实则暗藏玄机。

必须清标志位!否则无限循环!

EXTI_PR(挂起寄存器)一旦被置位,除非手动清除,否则NVIC会认为中断仍在等待处理。于是刚退出中断,又立刻重新进入——形成“中断风暴”。

解决办法就是这一句:

EXTI_ClearITPendingBit(EXTI_Line0);

它的作用是向PR寄存器对应位写1(注意:不是清零!这是ARM设计的一个反直觉点)。

ISR越短越好,千万别放delay!

新手常犯的错误是在ISR里加延时:

delay_ms(20); // 错!会导致主程序长时间阻塞

正确做法是:
- 在ISR中仅设置标志位;
- 主循环中检测标志并执行耗时操作。

例如:

volatile uint8_t flag_key_pressed = 0; // ISR中 flag_key_pressed = 1; EXTI_ClearITPendingBit(EXTI_Line0); // main循环中 if (flag_key_pressed) { delay_ms(20); // 此处可安全延时 do_something(); flag_key_pressed = 0; }

定时器中断:精准时间控制的核心

如果说EXTI是用来响应“突发事件”的话,那定时器中断就是构建“周期任务”的基石。

我们以TIM2为例,实现每1秒进入一次中断。

计算参数:别再瞎猜PSC和ARR

系统主频72MHz,想得到1Hz中断(即每1秒一次),该怎么设置?

  1. 先确定计数频率:假设预分频后为10kHz
    $$
    \text{PSC} = \frac{72\,\text{MHz}}{10\,\text{kHz}} - 1 = 7199
    $$

  2. 再设定自动重装载值:10k次计数产生一次更新
    $$
    \text{ARR} = 9999
    $$

代码如下:

TIM_TimeBaseInitTypeDef TIM_InitStruct; TIM_InitStruct.TIM_Period = 9999; TIM_InitStruct.TIM_Prescaler = 7199; TIM_InitStruct.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, &TIM_InitStruct); TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); // 使能更新中断 TIM_Cmd(TIM2, ENABLE);

别忘了NVIC配置:

NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct);

中断函数同样要清标志:

void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) { LED_Toggle(); // 假设有LED控制函数 TIM_ClearITPendingBit(TIM2, TIM_IT_Update); } }

多个GPIO共用一个EXTI线?怎么办?

前面提到PA0、PB0、PC0都可以接到EXTI0。但如果我同时想用PA0和PB1做中断呢?

注意:PB1对应的是EXTI1,不是EXTI0

每个Pin编号独立对应一条EXTI线(Pin0→EXTI0,Pin1→EXTI1……Pin15→EXTI15)。

但问题是:多个端口的同号Pin共享同一条EXTI线。也就是说:

  • PA1、PB1、PC1 都可以映射到 EXTI1;
  • 但你只能选择其中一个生效。

如果你想同时监控PA1和PB1的中断,就必须分别使用EXTI1和EXTI1?不行!它们都占EXTI1。

解决方案:换不同的Pin号,比如用PA1(EXTI1)和PB2(EXTI2)。

或者,使用外部中断扩展芯片(如PCA9535配合INT输出),但这属于高级方案。


优先级管理:别让中断互相打架

STM32支持最多16级抢占优先级(4位分组下)。合理规划优先级非常重要。

举个例子:

中断源抢占优先级说明
紧急停止按钮0最高,随时打断其他任务
USART接收2通信不能丢数据
定时器心跳15低优先级周期任务

配置方法:

NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); // 4位抢占,0位子优先级 // 在各NVIC_Init中设置PreemptionPriority

如果不分组,默认可能是NVIC_PriorityGroup_0(0位抢占,4位子优先级),这时所有中断都不能互相抢占,违背实时性要求。


常见问题排查清单

现象可能原因解决方法
根本不进中断NVIC未使能 / 向量名错误检查NVIC配置和函数命名
进中断但不停止未清除PR寄存器ClearITPendingBit
按键多次触发无消抖软件延时20ms或定时器消抖
程序崩溃在HardFaultISR中调用了复杂函数(如malloc、printf)移除阻塞操作
定时不准PSC/ARR计算错误重新核算时钟树
编译报错找不到IRQHandler启动文件与芯片型号不匹配更换正确startup文件

如何调试中断?Keil5给你三大利器

  1. 断点+单步跟踪
    在ISR第一行打断点,运行后观察是否命中。

  2. 寄存器窗口查看EXTI_PR、NVIC_ISER等
    查看中断是否已挂起、NVIC是否已使能。

  3. 逻辑分析仪或SWO输出
    使用ITM/SWO打印时间戳,分析中断响应延迟。


总结一下最关键的五个要点

  1. SYSCFG必须开AFIO时钟才能映射EXTI
  2. 每个EXTI线只能绑定一个GPIO引脚(跨端口复用但不可共存);
  3. NVIC必须单独使能,且优先级分组要明确
  4. ISR中必须清除挂起标志,否则反复进入
  5. 中断服务函数要短小精悍,避免调用阻塞函数

掌握了这些,你就不再依赖CubeMX也能独立完成中断系统搭建。更重要的是,你能读懂别人的代码,能在项目出问题时快速定位根源。


如果你正在学习STM32中断编程,不妨动手试一试:
👉 配置PB5上升沿中断,控制LED闪烁;
👉 再加一个TIM3的500ms中断,实现呼吸灯效果;
👉 最后尝试两个中断嵌套,看看高优先级如何打断低优先级。

实践才是检验理解的唯一标准。

如果你在实现过程中遇到了挑战,欢迎留言交流——我们一起把中断这件事,真正搞明白。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/25 23:58:33

深蓝词库转换:彻底告别输入法数据迁移困扰的终极解决方案

深蓝词库转换:彻底告别输入法数据迁移困扰的终极解决方案 【免费下载链接】imewlconverter ”深蓝词库转换“ 一款开源免费的输入法词库转换程序 项目地址: https://gitcode.com/gh_mirrors/im/imewlconverter 你是否曾经因为更换输入法而不得不放弃多年积累…

作者头像 李华
网站建设 2026/4/27 1:13:36

ST7735帧率限制因素硬件层面解读

深入ST7735:为什么你的TFT屏刷不动60帧? 你有没有遇到过这样的情况? 明明MCU主频都上到了100MHz,代码也用上了DMA、双缓冲、区域刷新,可那块小小的1.8寸TFT屏,动画还是卡得像幻灯片—— 满打满算也就15帧…

作者头像 李华
网站建设 2026/4/25 12:31:10

Windows平台Poppler安装指南:3步轻松部署PDF处理工具

Windows平台Poppler安装指南:3步轻松部署PDF处理工具 【免费下载链接】poppler-windows Download Poppler binaries packaged for Windows with dependencies 项目地址: https://gitcode.com/gh_mirrors/po/poppler-windows 想要在Windows系统上快速获得专业…

作者头像 李华
网站建设 2026/4/25 12:31:10

Blender MMD Tools完全指南:免费实现3D动画高效转换

Blender MMD Tools完全指南:免费实现3D动画高效转换 【免费下载链接】blender_mmd_tools MMD Tools is a blender addon for importing/exporting Models and Motions of MikuMikuDance. 项目地址: https://gitcode.com/gh_mirrors/bl/blender_mmd_tools 想要…

作者头像 李华
网站建设 2026/4/23 13:34:00

Lucky Draw抽奖系统:打造专业级活动抽奖解决方案

Lucky Draw抽奖系统:打造专业级活动抽奖解决方案 【免费下载链接】lucky-draw 年会抽奖程序 项目地址: https://gitcode.com/gh_mirrors/lu/lucky-draw 还在为各类活动的抽奖环节设计而困扰吗?Lucky Draw作为一款功能完备的开源抽奖系统&#xff…

作者头像 李华
网站建设 2026/4/27 3:27:54

百度网盘密码智能解析工具使用指南

百度网盘密码智能解析工具使用指南 【免费下载链接】baidupankey 项目地址: https://gitcode.com/gh_mirrors/ba/baidupankey 还在为百度网盘分享链接的提取码而烦恼吗?每次看到"请输入提取码"的提示框,是不是都有种无从下手的无奈感&…

作者头像 李华