目录
在嵌入式开发过程中,中断处理是一个不可或缺的环节。本篇博文将以STM32微控制器为核心案例,深入解析中断处理在MCU开发中的关键步骤和策略。主要有以下几个关键点:
- 中断向量表。
- NVIC(内嵌向量中断控制器)。
- 中断使能。
- 中断服务函数。
通过这篇博文,读者将获得对STM32 MCU中断处理流程和逻辑的全面理解。
1、中断向量表
中断向量表是关键的数据结构,用于存储中断服务程序的入口地址,这些地址被称为中断向量。具体来说,当一个中断发生时,系统会自动跳转到中断向量表中对应的地址,从而执行相应的中断服务程序(函数)。这个表由半导体制造商预先定义,确保每个中断都有一个唯一的入口地址。在程序架构中,中断向量表位于最前面,起到了连接硬件中断信号和相应处理程序的桥梁作用。中断向量表在整个程序的最前面,比如 STM32F103 的中断向量表如下所示:
中断向量表都是链接到代码的最前面,比如一般 ARM 处理器都是从地址 0X00000000 开始执行指令的,那么中断向量表就是从 0X00000000 开始存放的。代码第 1 行的"__initial_sp"就是第一条中断向量,存放的是栈顶指针,接下来是第 2 行复位中断复位函数 Reset_Handler 的入口地址,依次类推,直到第 27 行的最后一个中断服务函数DMA2_Channel4_5_IRQHandler 的入口地址,这样 STM32F103 的中断向量表就建好了。
虽然ARM处理器默认从地址0X00000000开始执行程序,但在学习STM32时,我们通常将代码下载到从0X8000000地址开始的存储区域。这一做法似乎与处理器的初始执行地址不符,看起来可能导致错误。然而,为了解决这一潜在问题,Cortex-M架构引入了中断向量表偏移的概念。这使得开发者可以将中断向量表放置在任何所需的地址位置。中断向量表的具体偏移配置是在'SystemInit'函数中完成的,方法是向SCB_VTOR寄存器写入新的中断向量表首地址。这种灵活的配置方式有效地克服了初始地址和程序存储地址之间的潜在冲突,确保了中断处理的正确进行。
代码如下所示:
第 8 行和第 10 行就是设置中断向量表偏移,第 8 行是将中断向量表设置到 RAM 中,第10 行是将中断向量表设置到 ROM 中,基本都是将中断向量表设置到 ROM 中,也就是地址0X8000000 处。第 10 行用到了 FALSH_BASE 和 VECT_TAB_OFFSET,这两个都是宏,定义如下所示:
cpp
#define FLASH_BASE ((uint32_t)0x08000000)
#define VECT_TAB_OFFSET 0x0
因此第 10 行的代码就是:SCB->VTOR=0X080000000,中断向量表偏移设置完成。
2、NVIC(内嵌向量中断控制器)
NVIC(Nested Vectored Interrupt Controller)是ARM Cortex-M微控制器的一个关键组件,用于管理中断。各个芯片厂商在设计芯片的时候会对Cortex-M内核里面的NVIC进行裁剪,把不需要的部分去掉,所以说STM32的NVIC是Cortex-M3的NVIC 的一个子集。
其主要功能包括:
- 中断优先级管理:NVIC允许为每个中断分配不同的优先级。这样,当多个中断同时发生时,处理器可以根据优先级来确定处理的顺序,确保更重要的任务得到优先处理。
- 嵌套中断处理:NVIC支持中断的嵌套。这意味着一个高优先级的中断可以打断一个低优先级的中断服务例程,使得系统能够及时响应更紧急的任务。
- 向量化中断处理:每个中断都有一个唯一的向量地址,该地址指向相应的中断服务例程。当中断发生时,处理器可以直接跳转到相应的服务例程,无需通过传统的中断查询方式,从而提高了处理速度。
- 中断屏蔽与启用:NVIC允许程序动态地使能或禁用特定中断。这种灵活性使得系统可以在不同的操作环境下,根据需要调整其中断响应。
- 中断挂起与恢复:NVIC能够挂起正在执行的中断,并在适当的时候恢复中断的处理。这对于管理复杂的中断场景尤为重要。
- 中断状态管理:NVIC提供接口来查询中断的状态,例如检查哪些中断处于挂起状态或哪些中断正在被处理。这有助于更好地理解和控制系统的行为。
- 低功耗模式支持:在低功耗模式下,NVIC可以帮助处理器在接收到中断信号时唤醒,这对于节能和延长电池使用寿命至关重要。
- 系统复位管理:NVIC还负责处理来自系统的复位请求,确保系统能够在出现问题时安全地重新启动。
通过查看STM32 NVIC寄存器,可以更清晰的解NVIC的主要功能:
cpp
/**
中断 NVIC 结构体定义
*/
typedef struct
{
__IO uint32_t ISER[8]; /*!<Offset: 0x000 中断使能寄存器 */
uint32_t RESERVED0[24];
__IO uint32_t ICER[8]; /*!<Offset: 0x080 中断清除寄存器 */
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8]; /*!<Offset: 0x100 中断使能悬起寄存器 */
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8]; /*!<Offset: 0x180 中断清除悬起寄存器 */
uint32_t RESERVED3[24];
__IO uint32_t IABR[8]; /*!<Offset:0x200 中断有效位寄存器 */
uint32_t RESERVED4[56];
__IO uint8_t IP[240]; /*!< Offset: 0x300 中断优先级寄存器 (8Bit wide) */
uint32_t RESERVED5[644];
__O uint32_t STIR; /*!< Offset: 0xE00 软件触发中断寄存器 */
} NVIC_Type;
3、中断使能
中断使能是指允许特定的中断源向处理器发出中断请求的过程。当一个中断被使能后,如果相应的事件发生(如外部信号、定时器溢出等),中断控制器会捕捉到这个事件,并通知处理器中断程序需要被执行。
中断使能通常通过设置特定的控制寄存器来完成。这些寄存器可能是微控制器的一部分,也可能位于外部设备中。通过编写特定的值到这些寄存器,可以选择性地使能或禁用特定的中断。
要使用某个外设的中断,肯定要先使能这个外设的中断,以 STM32F103 的 PE2 这个 IO 为例,假如我们要使用 PE2 的输入中断肯定要使用如下代码来使能对应的中断:
cpp
NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02; //抢占优先级 2,
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02; //子优先级 2
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道
NVIC_Init(&NVIC_InitStructure);
4、中断服务函数
中断服务函数是响应中断的专门函数。当特定的中断事件发生时,系统自动调用对应的中断服务函数来处理该事件。
当中断发生时,处理器暂停当前正在执行的任务,保存当前任务的上下文(如寄存器状态),然后跳转到相应的中断服务函数执行中断处理。一旦中断处理完成,处理器恢复之前任务的上下文,并继续执行被中断的任务。
同样以 STM32F103 的 PE2 为例,其中断服务函数如下所示:
cpp
/* 外部中断 2 服务程序 */
void EXTI2_IRQHandler(void)
{
/* 中断处理代码 */
}
当 PE2 引脚的中断触发以后就会调用其对应的中断处理函数 EXTI2_IRQHandler,我们可以在函数 EXTI2_IRQHandler 中添加中断处理代码。