我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

STM32中断机制详解:NVIC与EXTI配置从入门到实战

STM32中断机制详解:NVIC与EXTI配置从入门到实战 1. 写在前面为什么第四章要单独讲中断拿到这个标题你可能有点疑惑前面三章已经把GPIO、时钟树、串口这些基础玩得差不多了第四章为什么不继续往外设堆反而要专门停下来讲中断我的理解是中断是STM32从“点灯”走向“做产品”的分水岭。前几章你操作的都是“轮询模式”——主循环一遍遍去查标志位来了数据就处理没来就空转。这在学习阶段没问题但到了真实项目里你会发现轮询模式根本忙不过来。主循环要管按键、要刷新屏幕、要处理通信协议如果每次都停下来等串口数据系统就卡死了。中断的存在就是让CPU能够在“正在干正事”的时候被一个突发事件拉去处理更紧急的事情处理完再回到原来的工作。举个生活化的例子你在写文档突然来电了你接电话、说完事、挂电话继续写文档。电话铃就是中断源接电话的动作就是中断服务函数回到写文档就是现场恢复。没有中断系统你就得一直盯着电话等它响文档一个字也写不下去。第四章在整本手册里的定位也很有意思。前面几章解决的是“怎么让芯片动起来”第四章解决的是“怎么让芯片反应快、响应准”。寄存器手册讲到这其实是开始进入芯片内部逻辑的核心了——中断控制器NVIC和外中断控制器EXTI连着CPU核的调试与异常处理机制理解透了后面学定时器、DMA、各种通信协议都会轻松很多。这篇文章不需要你有多深的基础只要你把前三章的GPIO和时钟配好过能跑个点灯例程就可以直接上手。2. 整体设计与思路拆解中断系统到底怎么组织的2.1 把中断这件事拆成两个层次来理解开始配寄存器之前我建议你先在脑子里把中断系统的结构画出来。STM32的中断体系本质上分两层一层是“谁触发了中断”另一层是“触发了之后谁来安排优先级、怎么跳转”。第一层叫中断源。芯片上所有外设都可能成为中断源串口收到数据、定时器计数溢出、引脚电平变化这些事件都能产生中断请求。但外设那么多中断请求也很多总不能每个都直接连到CPU核上那样CPU核的引脚根本不够用。所以ST的做法是——芯片里有一个专门的中断控制器叫NVIC嵌套向量中断控制器所有外设的中断请求都先汇集到它这里。这就是第二层。NVIC负责两件核心的事一是给每个中断源分配一个唯一的编号这个编号就是“中断向量表”里的索引CPU响应中断后直接根据这个编号跳到对应的服务函数去执行二是管理优先级如果同时来了好几个中断谁先执行谁能打断谁都由NVIC里的优先级寄存器说了算。你可能会问那EXTI是什么角色EXTI是外部中断/事件控制器它是外设和NVIC之间的一个“翻译官”。你配置引脚为外部中断输入引脚上的电平变化先被EXTI捕获EXTI产生一个中断请求信号再送到NVIC。所以EXTI属于第一层NVIC属于第二层。我们实际配置寄存器也是先配EXTI告诉它监听哪个引脚再配NVIC告诉它这个中断的优先级是多少。2.2 为什么优先级分组这个设计容易被新手忽略第一次接触中断的人往往只关心“怎么进中断”不太关心“进了中断之后别的中断还能不能进来”。但真实项目里这个问题几乎一定会遇到——串口正在处理半包数据这时候定时器中断来了要求必须马上响应不然PWM输出就卡顿了。如果两者的优先级没有配好系统行为就会变得不可预测。NVIC的优先级设计是分两段的抢占优先级和子优先级也叫响应优先级。抢占优先级决定了一个中断能不能打断另一个正在执行的中断服务函数。比如A的抢占优先级是0B的抢占优先级是1那么A在跑的时候B来了也进不来得等A跑完反过来B在跑的时候A来了可以立刻打断B先处理A。子优先级解决的是“抢占优先级相同”的情况如果A和B的抢占优先级一样那么谁也不能打断谁只能排队。排队的顺序就由子优先级决定数字越小越先执行。这个设计看着简单实操里有个容易懵的地方占用几位给抢占优先级、几位给子优先级不是固定不变的而是由SCB-AIRCR寄存器里的PRIGROUP位决定的。有5种分组方式分别对应“全部是抢占优先级”“抢占4位子1位”“抢占3位子2位”等等。选择哪种分组方式取决于你的应用场景。如果系统里中断比较少且不需要相互打断我一般直接用分组2抢占2位子2位兼顾了灵活性和易理解性。2.3 这套设计解决了什么问题直接轮询不好吗为什么非要折腾中断这个问题我想认真回答一下因为它直接决定了你愿不愿意花精力去学这一章。轮询模式下CPU的时间片被浪费在“反复确认有没有事发生”上。串口波特率9600大约1ms收一个字节如果主循环里每隔50μs去查一次标志位那就意味着大部分时间都在“空转”。更麻烦的是如果主循环里有一段耗时操作比如刷屏或者写Flash可能要几十毫秒这期间来的数据就全丢了。中断模式下数据来了会主动通知CPUCPU放下手头的事先存数据再继续干原活。既不会丢数据又不需要牺牲主循环的效率。还有一类场景是中断不可替代的——精准定时。你让定时器工作在中断模式每1ms进一次中断在中断里翻转一个GPIO就能得到非常精确的方波。如果用轮询主循环里哪怕多一条判断语句都会导致波形抖动。这些经验我在后面的实操部分还会具体讲。3. 核心寄存器细节解析NVIC与EXTI逐个拆3.1 先看NVIC寄存器组别被长长一串吓到打开参考手册的中断章节你会看到一堆NVIC开头的寄存器比如NVIC_ISER、NVIC_ICER、NVIC_ISPR、NVIC_ICPR、NVIC_IABR、NVIC_IPR。第一次看确实吓人但你只要记住它们的职责分工就会发现其实只有三类操作使能/失能、挂起/清除挂起、配置优先级。NVIC_ISERInterrupt Set-Enable Register是使能寄存器的核心。每个位对应一个中断源写1使能写0对该位无效注意是无效不是失能。这里有个细节你迟早会遇到——官方CMSIS封装里使能一个中断通常写NVIC_EnableIRQ(IRQn_Type)它内部会根据中断号去操作对应的ISER寄存器你不需要自己算哪个寄存器第几位但理解原理对排查问题很有帮助。比如串口全局中断它的中断号是USART1_IRQn使能后系统就能响应USART1的所有中断事件前提是串口外设自己的中断标志也开了。NVIC_IPRInterrupt Priority Register是配置优先级的。每个中断源占用8位但实际只用了高4位取决于优先级分组低4位读回来是0。什么意思呢如果你配置分组为“抢占4位子0位”那么优先级寄存器的高4位全是抢占优先级取值范围是0到15如果分组为“抢占3位子1位”那高4位里前3位是抢占最后1位是子优先级。我见过不少人在这个细节上翻车本想着设一个高优先级结果因为分组没搞清楚设置的值被数据手册的表格解释成了别的含义。NVIC_ISPR和NVIC_ICPR是软件触发挂起和清除挂起的。这俩在调试的时候特别好用——你可以在线仿真时手动给某个中断置一个挂起位看它会不会进入对应的服务函数用来验证中断链路是否通畅。实际产品里很少主动去操作它们除非你要实现软件触发的中断同步。3.2 EXTI寄存器组外部中断的第一道入口EXTI这个外设说难不难但有几个细节很容易踩坑。先看它的寄存器组EXTI_IMR中断屏蔽寄存器、EXTI_EMR事件屏蔽寄存器、EXTI_RTSR上升沿触发选择寄存器、EXTI_FTSR下降沿触发选择寄存器、EXTI_SWIER软件中断事件寄存器、EXTI_PR挂起寄存器。配置一个外部中断的流程是这样的首先把GPIO复用为EXTI模式然后在EXTI_IMR里取消屏蔽对应线再配置RTSR或FTSR选择触发边沿。线号跟引脚号是对应的PA0用EXTI0PB5用EXTI5类推。注意一个细节——同一条EXTI线只能同时监听一个引脚。什么意思EXTI0接了PA0你就不能再用PB0触发EXTI0中断了。这在项目里是个很常见的冲突尤其是IO资源紧张的时候你可能想把几个按键分别放在PA0和PB0上结果发现根本配不了。解决方案也很简单用EXTI线分组或换引脚。EXTI_PR是挂起寄存器中断进来后这个寄存器对应位会被硬件置1。务必记住在中断服务函数里退出前必须把这位清零否则中断会一直被触发看起来就像程序卡死在中断里。标准写法是读一下PR寄存器再把对应位写1来清除。这个“写1清零”的习惯在你后面接触大量外设时都会用到建议现在就养成肌肉记忆。3.3 SYSCFG_EXTICR最容易被漏掉的配置外部中断还有一个前置步骤很容易漏——EXTI的引脚连接选择寄存器在SYSCFG外设里。很多人配完GPIO和EXTI发现中断怎么都不触发最后排查一圈发现是SYSCFG_EXTICR没有配置。原因是这样的EXTI0这条线既可以选择PA0也可以选择PB0芯片需要一个寄存器来告诉你“当前这条线连的是哪个端口”。这个选择就在SYSCFG_EXTICR里完成。你在标准库或者HAL库里会看到一行SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource0)这就是在设置SYSCFG。用寄存器操作就是往SYSCFG_EXTICR1的相应位段里写端口编号。还有一个前置步骤是开启SYSCFG外设的时钟。它在APB2总线上使能位在RCC_APB2ENR里。没有开时钟写SYSCFG寄存器统统无效而且表现得很诡异——不报错就是中断不进来。我见过有人在这个问题上卡了一整天。所以配外部中断的完整顺序我建议你严格按“开SYSCFG时钟→配置SYSCFG_EXTICR→配置EXTI边沿和屏蔽→配置NVIC”来顺序错了就容易出现“看起来对了实际没生效”的情况。4. 实操过程与核心环节实现一个完整的外部中断例程4.1 硬件连接和需求定义为了把这章内容串起来我设计一个实际场景一个按键接在PA0引脚按下时产生一个下降沿触发EXTI0中断在中断服务函数里翻转LEDLED接在PC13同时用一个全局变量记录按键次数主循环里通过串口打印这个计数。这个例子虽然简单但覆盖了GPIO、EXTI、NVIC、串口、主循环交互这几个核心链路非常适合作为第四章的动手练习。硬件上要注意按键如果没有外部上拉需要在GPIO配置里开启内部上拉电阻。按这个设计PA0空闲时是高电平按下时被拉低松手恢复高。这样我们能得到一个明确的下降沿触发源。4.2 寄存器级代码逐行解析先看GPIO配置。把PA0设为输入模式开启内部上拉使能GPIOC时钟控制LED。我用寄存器方式写方便你对照数据手册的位定义RCC-APB2ENR | RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPCEN; GPIOA-CRL ~(0x0F 0); // 先清零PA0的4位配置 GPIOA-CRL | (0x08 0); // 输入模式上拉/下拉由ODR决定 GPIOA-ODR | (1 0); // ODR置1配合输入模式选择内部上拉 GPIOC-CRH ~(0x0F (4 * 5)); // PC13是高位端口清除第5组配置 GPIOC-CRH | (0x03 (4 * 5)); // 推挽输出50MHz这里有个小细节值得说一下——GPIOA-CRL ~(0x0F 0)这行的作用是把CNF0和MODE0这4位全部清零。如果不先清零就OR残留的位会让配置结果变得不可预测。这个“先清零再置位”的习惯在寄存器操作里几乎处处要用到你可以把它当成一条铁律。接着是SYSCFG配置。这里要注意STM32F1系列的SYSCFG在APB2总线上对应寄存器是SYSCFG_EXTICR1PA0对应的是EXTI0它用的是EXTICR1的低4位RCC-APB2ENR | RCC_APB2ENR_AFIOEN; // F1系列叫AFIOF4以后叫SYSCFG AFIO-EXTICR[0] ~(0x0F 0); // 清零EXTI0的端口选择位 AFIO-EXTICR[0] | (0x00 0); // 选择GPIOAF1系列的外设名是AFIOF4系列是SYSCFG功能一样就是改了个名字。如果你用的是F4记得把AFIO替换成SYSCFG总线时钟和寄存器名也对应调整。这是移植时最容易翻车的差异点。然后是EXTI配置EXTI-IMR | (1 0); // 开EXTI0中断屏蔽 EXTI-EMR ~(1 0); // 不需要事件模式保持关闭 EXTI-FTSR | (1 0); // 下降沿触发按键按下时触发 EXTI-RTSR ~(1 0); // 关闭上升沿触发防止松手时二次触发 EXTI-PR | (1 0); // 清除挂起标志防止上电误触发注意最后一行很多人会忽略。芯片刚上电时引脚电平可能还没稳定EXTI挂起寄存器里可能已经有一个残留的标志。如果不先清一次中断服务函数会被立刻执行一次表现就是程序一跑LED先闪一下按键还没按呢。NVIC配置NVIC-ISER[0] | (1 6); // EXTI0_IRQn NVIC-IPR[6] ~(0xF0); // 清优先级位 NVIC-IPR[6] | (0x50); // 抢占优先级5子优先级0取决于分组中断号这里说一下EXTI0对应的向量号是6。这个编号在不同型号上基本一样但保险起见还是查一下你所用芯片的中断向量表。ISER[0]的第6位对应的是系统异常编号6这在ARM内核的NVIC里是固定的。中断服务函数volatile uint32_t key_count 0; void EXTI0_IRQHandler(void) { if (EXTI-PR (1 0)) // 确认是EXTI0触发的 { EXTI-PR | (1 0); // 写1清标志 GPIOC-ODR ^ (1 13); // 翻转LED key_count; } }主循环里就可以用key_count做你想做的事了。我建议你在主循环里把它通过串口打印出来观察按键计数是否稳定。4.3 中断优先级分组的配置示例前面的NVIC配置里我写了优先级分组这个概念但没有具体配置。这一步是在系统初始化时完成的#define AIRCR_PRIGROUP_2 ((uint32_t)0x500) // 抢占2位子2位 SCB-AIRCR (SCB-AIRCR ~(0x7 8)) | AIRCR_PRIGROUP_2;为什么是0x500看数据手册AIRCR寄存器第10位到第8位是PRIGROUP取值010表示“抢占2位子2位”即16个优先级里前4级是抢占分组后4级是子分组。手动设置的时候记得先保留其他位只修改PRIGROUP段否则可能连带影响寄存器里其他位。需要提醒的是优先级分组必须在系统启动早期设置且只能在“没有任何中断正在服务”的时候修改。如果在中断服务函数里改了分组可能造成优先级乱套行为不可预测。开发调试时全工程统一用一套分组别在中间又去改。4.4 串口中断与外部中断协同主从优先级实际体验很多项目不只是单个中断工作而是多个中断同时跑。我把串口接收中断也打开模拟一个“多中断协同”的场景。串口中断的优先级如果配得太低可能被按键中断打断导致接收超时配得太高又可能影响实时性要求更高的任务。这里给出两组推荐配置你可以对照实际场景选用中断源抢占优先级子优先级适用场景EXTI0按键20按键响应要求高可打断串口处理USART1串口30串口数据允许延后处理EXTI0按键00按键处理极简仅置标志USART1串口10串口数据量大避免丢失我自己的习惯是中断服务函数里“越短越好”只做置标志、存数据、清标志这三件事真正的处理逻辑放主循环。这样即使多个中断互相打断也不会因为中断函数执行时间过长导致其他中断长时间得不到响应。5. 中断系统实操中的常见问题与排查实录5.1 问题一中断死活不触发这个现象排在故障榜第一位。按键按下去毫无反应程序像什么都没发生一样。根据我的经验按顺序排查以下几处SYSCFG/AFIO时钟是否打开。这是最隐蔽的一个很容易漏。GPIO是否配置成输入模式上拉电阻是否开启。如果引脚浮空电平不确定边沿也可能不稳定。SYSCFG_EXTICR是否把对应的EXTI线连到了正确的端口。配错端口直接导致中断源没接上。EXTI_IMR里对应位是否置1。屏蔽状态的话信号进不来。NVIC里是否使能了对应的IRQn优先级是否配置合理。有些型号默认分组是全抢占优先级值要按分组重新算。最后用调试器在线仿真查一下EXTI-PR对应位是否变1。如果变1说明信号到达了EXTI问题在NVIC或向量表如果没有问题出在前面几级配置。我调试时最喜欢用最后这个方法它能一下子把故障定位到前置链路还是NVIC链路节省大量时间。5.2 问题二中断频繁触发像卡死在中断里按键明明只按了一下程序却像疯了似的不停翻转LED。这个问题的根源99%是EXTI_PR没有及时清除。上一节代码里我写了“读一下、写1清零”这个操作。这里再展开说一下为什么这么写。EXTI_PR本质是一个电平状态的锁存器中端请求触发后硬件会把这个位拉高。如果你不清除这个拉高状态会持续存在NVIC会反复看到这个中断请求于是不断进入服务函数。只有你主动写1清除硬件设计如此写0无效中断请求才会撤销。还有一种情况是引脚边沿抖动导致的重复触发。虽然按键是机械结构按下过程会抖动产生一串脉冲。如果你只配了下降沿触发理想情况下按下只有一个下降沿但实际抖动会带来额外电平跳变。最简单的解法是用外部RC滤波电路或者软件上在中断里加一个“消抖”逻辑——记录进入时间一段时间内忽略同源中断。这个思路在真实项目中非常实用。5.3 问题三多个中断互相“打架”典型场景按键中断一直在翻转某个全局变量串口中断在接收大数据包。一旦按键频繁按串口接收变得断断续续。排查下来发现两个中断的抢占优先级都是0谁也不让谁只能排队。解决思路其实简单分析中断的“时间敏感度”。如果按键只是记录一个标志响应慢几十微秒完全没影响那就把按键的抢占优先级降下来保证串口收数不被频繁打断如果按键要驱动一个实时性要求高的动作比如紧急停止那就把按键优先级提上去必要时可以让它抢占串口。还有一点容易被忽略中断服务函数里的代码长度直接影响系统整体响应。我建议服务函数里只做“置标志位”“拷贝几个字节”“清标志”这类微秒级操作不要把数据处理、协议解析、LCD刷屏这些耗时逻辑放进去。一个几十微秒的中断可能没什么感觉但两个上百微秒的中断叠加主循环几乎得不到运行时间系统就“假死”了。5.4 常见问题速查表现象可能原因排查/处理方法中断完全不触发SYSCFG/AFIO时钟未开RCC-APB2ENR对应位置1中断完全不触发EXTI线接错端口检查SYSCFG_EXTICR设置中断完全不触发NVIC未使能查看ISER对应位是否为1中断不停触发EXTI_PR未清除服务函数内写1清标志重复触发多次引脚边沿抖动软件消抖或硬件RC滤波多中断相互干扰优先级分组不合理重新规划抢占/子优先级优先级设置无效分组选择与预期不符统一设置AIRCR的PRIGROUP上电立刻进一次中断PR残留标志初始化时先清EXTI_PR6. 写在最后一个关于调试的小习惯第四章的核心内容到上面已经讲得差不多了最后我想分享一个自己调试中断系统时形成的小习惯可能对你有帮助。每次写完中断相关代码先在主循环里用一个全局变量统计进入中断的次数然后在程序开头把次数清零。有了这个计数器你可以很容易判断问题出在哪——按一次按键计数增加1说明链路基本通计数不动说明信号根本没到服务函数计数疯狂跳动说明清除挂起或消抖有问题。这个习惯帮我省了非常多查错时间。另外一个建议是刚开始学中断不要急着把所有外设都配上中断。先在GPIO外部中断上跑通一个完整流程理解了NVIC和EXTI的配合再去开串口中断、定时器中断。每加一种中断就专门验证一下它的触发和清除逻辑。这样一步步来后面即使遇到复杂的中断嵌套场景你也能从已经建立的心智模型里找到定位问题的思路。第四章的内容就到这里。下一章如果继续沿着这条线走大概率会讲定时器或者DMA到时候中断的知识还会再次用到尤其是DMA和中断的配合那才是STM32真正高效运作的形态。先把这一章的链路吃透后面的路就好走了。
返回列表