当前位置: 首页 > news >正文

Cortex-M 中断挂起、丢中断与 EXC_RETURN 机制详解

Cortex-M 中断挂起、丢中断与 EXC_RETURN 机制详解

本文基于《ARM Cortex-M 权威指南》第 7 章,结合实际工程经验,深入讲解 Cortex-M 处理器的中断挂起(Pending)机制、丢中断问题的本质,以及异常返回时 LR 寄存器中 EXC_RETURN 的作用。适合嵌入式开发者发表在 CSDN 等技术博客。


一、中断的三种状态

在 Cortex-M 处理器中,每个中断都有 3 个属性

  1. 使能/禁止(Enable/Disable):控制中断是否可以被触发
  2. 挂起/未挂起(Pending/Not Pending):表示中断请求是否已产生但未处理
  3. 活跃/非活跃(Active/Inactive):表示中断是否正在被处理(ISR 正在执行)
    在这里插入图片描述

1.1 状态组合与转换

这些状态可以有多种组合,例如:

状态组合含义
未挂起 + 非活跃中断空闲,无请求
挂起 + 非活跃中断请求已产生,等待处理
挂起 + 活跃ISR 正在执行,但又产生了新的请求(重复挂起
非挂起 + 活跃ISR 正在执行,无新请求

1.2 NVIC 寄存器操作

NVIC(嵌套向量中断控制器)提供了多个寄存器来控制这些状态:

  • NVIC_ISERx / NVIC_ICERx:使能/禁止中断
  • NVIC_ISPRx / NVIC_ICPRx:设置/清除挂起状态(软件触发/清除中断)
  • NVIC_IABRx:读取活跃状态(只读)
  • NVIC_IPRx:设置中断优先级

关键特性

  • 挂起状态位存储在 NVIC 的可编程寄存器中,即使中断被禁止,挂起状态仍会保留。
  • 当 NVIC 确认中断请求后,会引发该中断的挂起状态;即使请求被取消,挂起状态仍会为高。

二、中断挂起(Pending)机制

2.1 什么是挂起?

挂起(Pending) 表示处理器已经接收到中断请求,但由于以下原因尚未处理:

  1. 优先级不足:当前正在处理更高优先级的中断
  2. 全局中断被禁止:通过 PRIMASK / FAULTMASK / BASEPRI 屏蔽
  3. 中断未使能:通过 NVIC 的 NVIC_ICERx 禁止了该中断

2.2 挂起状态的自动管理

根据图 7.14 和 7.15 的时序,挂起状态的管理遵循以下规则:

情况 1:中断请求被清除(图 7.15)

时间线:中断请求 ───┐ ┌─────────────  (外设产生脉冲)└─┘挂起状态 ────┐    ┌────────── (请求确认后置位)└────┘            (软件清除或取消前被清除)处理器模式 ─────────────────── (始终在线程模式,未进入 ISR)

说明

  • 如果中断请求在处理器执行操作前被清除(例如外设标志被软件清除),挂起状态会自动清除。
  • 不会丢中断,但也不会进入 ISR。
    在这里插入图片描述
    在这里插入图片描述

情况 2:中断被抢占(图 7.14 和 7.19)

时间线:中断请求 X ───┐ ┌─────────────  (低优先级中断)└─┘挂起状态 X ────┐   ┌──────┐──── (被确认,处理时又被挂起)└───┘      └────活跃状态 X ────────┐        ┌─── (ISR 执行中)└────────┘处理器模式 ───线程──┐ ISR X ┌─线程── (中断返回后可能再次进入)└───────┘

说明

  • 若在 ISR 执行期间,相同中断再次产生请求,挂起状态会再次置位。
  • 当前 ISR 返回后,处理器会再次进入该中断(图 7.19 所示)。
  • 不会丢中断,但只会挂起一次(即使产生多次请求)。

三、丢中断问题的本质

3.1 什么情况下会"丢"中断?

核心原理:Cortex-M 的挂起状态是 1 位标志(置位/清除),不是计数器。

图 7.18 所示场景

时间线:中断脉冲 ───┐ ┐ ┐──────────────  (进入 ISR 前连续 3 次脉冲)└─┘ └─┘挂起状态 ────┐         ┌──────── (只记录"有请求",不计数)└─────────┘处理器模式 ───线程──┐ ISR ┌─线程─── (只进入一次 ISR)└─────┘

结论

  • 如果在 ISR 进入之前,同一中断连续产生多次脉冲,只会挂起一次
  • 处理器只会进入一次 ISR,其余脉冲会被"合并"。
  • 这不是硬件 BUG,而是设计权衡:节省硬件资源(不需要为每个中断维护计数器)。

3.2 如何避免丢中断?

方案 1:使用硬件 FIFO 或计数器(推荐)

示例(UART 接收)

// 外设配置:启用 FIFO
USART1->CR1 |= USART_CR1_FIFOEN;  // 启用 FIFO(STM32G4 等)void USART1_IRQHandler(void) {while (USART1->ISR & USART_ISR_RXNE) {  // 循环读取 FIFOuint8_t data = USART1->RDR;buffer[write_idx++] = data;}
}

优点

  • 硬件 FIFO 可以缓存多个数据,避免软件来不及处理。
  • 适用于 UART、SPI、ADC 等外设。
方案 2:在 ISR 中循环处理标志位

示例(GPIO 外部中断)

volatile uint32_t edge_count = 0;  // 软件计数器void EXTI0_IRQHandler(void) {if (EXTI->PR1 & EXTI_PR1_PIF0) {edge_count++;  // 记录边沿次数EXTI->PR1 = EXTI_PR1_PIF0;  // 清除挂起标志// 检查是否有新的挂起(在清除后立即检查)if (EXTI->PR1 & EXTI_PR1_PIF0) {edge_count++;  // 再次记录EXTI->PR1 = EXTI_PR1_PIF0;}}
}

注意

  • 这种方法只能部分缓解问题,无法完全避免(如果连续脉冲间隔 < ISR 执行时间)。
方案 3:提高 ISR 优先级 + 减少处理时间
// 设置最高优先级(0 = 最高)
NVIC_SetPriority(EXTI0_IRQn, 0);// ISR 中只做最少的工作
void EXTI0_IRQHandler(void) {timestamp[write_idx++] = DWT->CYCCNT;  // 快速记录时间戳EXTI->PR1 = EXTI_PR1_PIF0;             // 清除标志// 复杂处理放到主循环或低优先级任务
}

3.3 典型错误案例

❌ 错误做法(在 ISR 中处理过慢):

void TIM2_IRQHandler(void) {if (TIM2->SR & TIM_SR_UIF) {TIM2->SR &= ~TIM_SR_UIF;  // 清除标志// 错误:在 ISR 中执行耗时操作for (int i = 0; i < 1000; i++) {process_data(buffer[i]);  // 可能需要几毫秒}}
}

问题

  • 如果定时器周期是 1ms,而 ISR 执行需要 5ms,会丢失 4 次中断。

✅ 正确做法

volatile bool timer_flag = false;void TIM2_IRQHandler(void) {if (TIM2->SR & TIM_SR_UIF) {TIM2->SR &= ~TIM_SR_UIF;timer_flag = true;  // 设置标志,快速退出}
}int main(void) {while (1) {if (timer_flag) {timer_flag = false;process_data(buffer, 1000);  // 在主循环处理}}
}

四、EXC_RETURN 机制详解

4.1 什么是 EXC_RETURN?

EXC_RETURN 是一个特殊的返回地址,存储在 链接寄存器(LR) 中,用于触发异常返回流程。

关键特性

  • 当处理器进入异常(中断或其他异常)时,硬件自动将 EXC_RETURN 写入 LR。
  • 当 ISR 执行返回指令(如 BX LR)时,若 LR 的值是 EXC_RETURN,处理器会触发异常返回。

4.2 EXC_RETURN 的编码格式

EXC_RETURN 是一个 32 位值,格式为:0xFxxxxxxx(高 8 位固定为 0xFF)。

表 7.8:常用的 EXC_RETURN 值

返回指令EXC_RETURN 值描述
BX 0xFFFFFFF9返回到线程模式,使用 MSP(主堆栈指针)
POP {PC} 或 POP {…, PC}0xFFFFFFFD返回到线程模式,使用 PSP(进程堆栈指针)
加载(LDR)或多加载(LDM)0xFFFFFFF1返回到处理模式(Handler Mode),使用 MSP
在这里插入图片描述

编码细节(低 4 位):

EXC_RETURN[3:0]:Bit 3: 0 = 返回到处理模式, 1 = 返回到线程模式Bit 2: 0 = 使用 MSP, 1 = 使用 PSPBit 1: 保留(通常为 0)Bit 0: 0 = 标准栈帧, 1 = 扩展栈帧(带 FPU 寄存器)

示例

  • 0xFFFFFFF9:二进制 1111 1111 ... 1001 → 返回线程模式,使用 MSP,无 FPU 上下文。
  • 0xFFFFFFED:二进制 1111 1111 ... 1101 → 返回线程模式,使用 PSP,有 FPU 上下文。
    在这里插入图片描述

4.3 异常返回的执行流程

步骤

  1. 压栈(入口):异常发生时,硬件自动将寄存器压入栈:

    栈顶(高地址)
    ┌─────────────┐
    │ xPSR        │ <- 程序状态寄存器
    │ PC          │ <- 返回地址
    │ LR          │ <- 线程模式的 LR
    │ R12         │
    │ R3          │
    │ R2          │
    │ R1          │
    │ R0          │
    └─────────────┘ <- SP(栈指针)
    
  2. 执行 ISR:处理器进入处理模式,执行中断服务例程。

  3. 出栈(退出):ISR 执行 BX LR 时,硬件检测到 LR = EXC_RETURN:

    • 从栈中恢复 R0-R3, R12, LR, PC, xPSR。
    • 根据 EXC_RETURN 的编码,切换到线程模式并选择 MSP 或 PSP。
    • 更新 NVIC 寄存器(如活跃状态、挂起状态)。
    • 恢复 PC,继续执行被中断的代码。

4.4 C 语言中的自动处理

关键:在 C 语言编写 ISR 时,编译器会自动处理 EXC_RETURN:

void TIM2_IRQHandler(void) {// 编译器生成的入口代码:压栈 + 保存上下文// 用户代码TIM2->SR &= ~TIM_SR_UIF;// 编译器生成的出口代码:// BX LR(LR 中存储的是 EXC_RETURN)
}

生成的汇编(示例)

TIM2_IRQHandler:; 硬件自动压栈 R0-R3, R12, LR, PC, xPSR; ...用户代码...LDR  R0, =TIM2_BASELDR  R1, [R0, #SR_OFFSET]BIC  R1, R1, #TIM_SR_UIFSTR  R1, [R0, #SR_OFFSET]BX   LR  ; LR = 0xFFFFFFF9,触发异常返回

4.5 手动调用 EXC_RETURN(高级用法)

场景:在 RTOS 或 Bootloader 中,可能需要手动构造异常返回。

示例(切换到用户模式并使用 PSP)

void switch_to_user_task(void) {// 构造栈帧uint32_t *psp = (uint32_t *)0x20008000;  // 用户栈顶psp -= 8;  // 为栈帧分配空间psp[0] = 0;  // R0psp[1] = 0;  // R1psp[2] = 0;  // R2psp[3] = 0;  // R3psp[4] = 0;  // R12psp[5] = (uint32_t)user_task_exit;  // LR(任务返回地址)psp[6] = (uint32_t)user_task_entry; // PC(任务入口)psp[7] = 0x01000000;  // xPSR(Thumb 位置位)// 设置 PSP__set_PSP((uint32_t)psp);// 切换到线程模式 + PSP__asm volatile ("MOV LR, #0xFFFFFFFD \n"  // EXC_RETURN:线程模式 + PSP"BX  LR              \n"  // 触发异常返回);
}

注意

  • 这种手动操作通常只在 RTOS 内核或启动代码中使用。
  • 栈帧格式必须严格符合 ARM 规范,否则会触发 HardFault。

五、实战案例:结合挂起与 EXC_RETURN

5.1 场景:在 ISR 中再次产生中断挂起

图 7.19 所示

volatile uint32_t count = 0;void EXTI0_IRQHandler(void) {count++;EXTI->PR1 = EXTI_PR1_PIF0;  // 清除挂起标志// 模拟耗时操作(此时可能有新的中断请求)for (volatile int i = 0; i < 10000; i++);// ISR 返回后,若有新的挂起,会再次进入此函数
}

时序分析

时间线:中断请求 ───┐     ┐─────────────  (第一次脉冲)(第二次脉冲)└─────┘挂起状态 ────┐   ┌─┐─────────────  (第一次清除,第二次再次挂起)└───┘ └─────────────处理器模式 ───线程──┐ ISR ┌─┐ ISR ┌─线程──  (连续进入两次)└─────┘ └─────┘

5.2 调试技巧:查看 LR 寄存器

在调试器中断点停在 ISR 时

(gdb) info registers lr
lr    0xfffffff9

含义

  • 0xFFFFFFF9 → 返回线程模式 + MSP。
  • 如果看到其他值(如 0xFFFFFFF1),说明是嵌套中断(ISR 中又进入了另一个 ISR)。

六、总结与最佳实践

6.1 关键结论

问题原因解决方案
丢中断挂起状态是 1 位标志,不计数使用硬件 FIFO 或软件循环检测
重复挂起ISR 执行期间再次产生请求在 ISR 中快速清除标志,避免耗时操作
EXC_RETURN 错误手动修改 LR 导致栈帧损坏使用 C 语言编写 ISR,让编译器自动处理

6.2 编码建议

✅ 推荐做法
  1. ISR 中只做必要的工作

    void UART_IRQHandler(void) {if (UART->ISR & UART_ISR_RXNE) {buffer[write_idx++] = UART->RDR;  // 快速读取}
    }
    
  2. 使用外设的硬件计数器

    // 定时器捕获多次边沿
    TIM1->ARR = 0xFFFF;  // 最大计数
    TIM1->CNT = 0;
    // 在 ISR 中读取 CNT 即可知道脉冲数
    
  3. 优先级分层

    // 高优先级:快速响应
    NVIC_SetPriority(EXTI0_IRQn, 0);// 低优先级:复杂处理
    NVIC_SetPriority(TIM2_IRQn, 5);
    
❌ 避免的陷阱
  1. 在 ISR 中调用阻塞函数(如 printfHAL_Delay
  2. 在 ISR 中禁止全局中断时间过长
  3. 手动修改 LR 寄存器(除非你非常清楚后果)

6.3 调试工具推荐

  • DWT(Data Watchpoint and Trace):测量 ISR 执行时间
  • ETM(Embedded Trace Macrocell):跟踪异常入口/出口
  • SEGGER SystemView:可视化中断时序

七、参考资料

  1. ARM 官方文档

    • ARM Cortex-M3/M4/M7 Generic User Guide
    • ARMv7-M Architecture Reference Manual
  2. 经典教材

    • 《ARM Cortex-M 权威指南》(Joseph Yiu)第 7 章
  3. ST 应用笔记

    • AN4776: General-purpose timer cookbook for STM32 microcontrollers
    • AN4995: How to improve ADC accuracy in STM32 microcontrollers
  4. 在线资源

    • ARM Community: https://community.arm.com
    • Stack Overflow: [cortex-m] 标签

版权声明:本文基于公开技术资料整理,遵循 CC BY-SA 4.0 协议。

http://www.dtcms.com/a/453137.html

相关文章:

  • Qt C++ :QWidget类的主要属性和接口函数
  • 串扰14-蛇形走线与信号延迟
  • Java SpringBoot(一)--- 下载Spring相关插件,创建一个Spring项目,创建项目出现的问题
  • 业务过程需求在软件需求中的特殊性与核心地位
  • 域名哪个网站续费商洛市住房城乡建设厅网站
  • 笛卡尔积 = 所有可能组合 = 行数相乘
  • MySQL——数据类型和表的操作
  • 工作笔记-----ICache对中文显示的影响问题
  • 什么是 Maven?关于 Maven 的命令、依赖传递、聚合与继承
  • nat静态地址转化
  • 计算机网站开发要考什么证竞价培训班
  • 《算法与数据结构》第七章[算法3]:图的最小生成树
  • 文科和理科思维差异:推演与归纳
  • 雨雪“开关式”监测:0.5秒精准响应,守护户外安全
  • 做文化传播公司网站手机建立网站
  • HTML的本质——网页的“骨架”
  • 徐州双语网站制作wordpress 外链视频
  • React 快速入门:菜谱应用实战教程
  • 网站备案和域名备案网页源码app
  • Tomcat本地部署SpringBoot项目
  • 大模型开发 - 04 QuickStart_DeepSeek 模型调用流程源码解析:从 Prompt 到远程请求
  • 怎么把在微企点做响应式网站深圳专业网站建
  • 认识三极管
  • gRPC从0到1系列【23】
  • Element Plus 完整教程:从背景到实践
  • Qt编写上下界面切换效果/前进到下一个界面/后退到上一个页面/零件工艺及管理设计系统
  • 第3章 多线程服务器的适用场合与常用编程模型
  • 网站开发什么课程佛山建站模板制作
  • Lua语法(2)
  • npm、npx、pnpm 深度解析:从原理到实战的全方位指南