中断是MCU的基操。 当一个外设产生中断请求时内核会根据中断请求号,每个外设的每个中断请求都有唯一的中断号,从中断向量表中查找到中断函数入口,跳转到对应的中断服务函数。这个过程首先是要保存现场,所谓现场即MCU在收到中断请求前的状态,大部分的MCU都会针对中断有一些硬件自动压栈的操作,这个过程不需要软件参与,确保中断低延时响应。
M0的自动压栈包括R0 – R3、R12、PSR、LR、PC,一共8个。从低地址向高地址依次压入以下寄存器(共8个32位寄存器,占8个字):xPSR → PC → LR → R12 → R3 → R2 → R1 → R0,其中,xPSR最先压入栈底,R0最后压入栈顶。为什么是这几个寄存器和顺序?R0-R3、R12:根据程序过程调用标准,这些寄存器可能被调用者修改,需优先保存,其中R0-R3是用来做函数传参的,如果参数很多则需要增加软件压栈,不过一般也是由编译器去产生对应的指令;LR在中断中保存EXC_RETURN值,用于确定中断返回模式;PC和xPSR:确保程序执行流和状态标志的连续性。还有一个需要注意的点,堆栈指针是分主堆栈和线程堆栈,主程序使用的是线程堆栈,中断使用的是主堆栈,所以主程序跳转中断会有一个堆栈模式的切换。
搞清楚这个非常关键,比如当我们出现hardfault之后,可以根据CPU寄存器中的堆栈地址,按压栈顺序找到第6个也就是LR寄存器,就能知道出错前程序是执行到那个函数,Debug时更容易定位。
中断的压栈与非中断(程序跳转)的压栈有什么区别?
中断压栈,硬件自动完成:当异常(如中断或系统异常)发生时,Cortex-M0硬件会自动将关键寄存器压入当前栈(通常是主堆栈)。这一过程无需软件干预,确保低延迟响应。压栈内容:包括程序计数器(PC)、链接寄存器(LR)、程序状态寄存器(xPSR)以及通用寄存器(R0-R3、R12),共8个寄存器。目的:保存中断前的完整执行现场,确保中断服务程序(ISR)执行后能正确恢复原任务。特殊返回指令:中断退出时需通过BX LR指令加载EXC_RETURN值到PC,触发硬件自动弹栈并恢复现场。优先级与嵌套处理:若发生中断嵌套,硬件会根据优先级自动管理多级栈帧,确保高优先级中断抢占后能逐级恢复。
非中断函数压栈,使用进程堆栈(PSP)或当前栈:普通函数调用通常使用进程堆栈(若操作系统支持)或当前活动的栈指针。按需保存寄存器:编译器根据函数复杂度优化压栈操作,仅保存可能被修改的寄存器,减少栈空间占用。普通返回指令:通过POP {PC}或BX LR指令直接跳转回调用点,无特殊状态处理。无嵌套优先级机制:函数调用遵循栈的先进后出原则,不涉及硬件优先级管理。
Cortex-M0通过硬件自动化的上下文保存/恢复和简化的NVIC设计,实现了高效的中断响应(最低12周期延迟),适用于实时性要求较高的嵌入式场景(如电机控制、传感器采集)。开发者需重点关注中断标志管理和优先级配置,避免常见问题如中断丢失或重复触发。