1、异常和中断的概念
bash
在 ARM Cortex-M 架构中,异常(Exception) 是指导致处理器暂停当前正常程序流、转而执行特定处理程序
(异常服务例程,Exception Handler)的事件。异常分为两类:
(1)系统异常(System Exceptions):由处理器内核产生,用于处理内部事件,如复位(Reset)、
不可屏蔽中断(NMI)、硬故障(HardFault)、内存管理故障(MemManage)、总线故障(BusFault)、
使用故障(UsageFault)、系统调用(SVC)、PendSV、SysTick 等。
(2)外部中断(Interrupts):由片上外设(如 EXTI、TIM、USART、ADC 等)或外部引脚触发,属于可屏蔽中断,
通过 NVIC(Nested Vectored Interrupt Controller)进行管理。
2、中断响应与中断优先级NVIC
bash
(1)中断响应流程
当中断条件满足且未被屏蔽时,处理器执行以下操作:
完成当前指令(部分指令如 multi-cycle load/store 会提前终止);
自动保存上下文:将 R0--R3、R12、LR、PC 和 xPSR 共 8 个寄存器压入当前栈(MSP 或 PSP);
根据中断号从向量表中取出对应异常服务例程(ISR)入口地址;
跳转至 ISR 执行;
ISR 执行完毕后,执行异常返回指令(如 BX LR),硬件自动恢复上下文并返回原程序断点继续执行。
该过程由硬件自动完成,具有低延迟、高确定性的特点。
(2)中断优先级机制
Cortex-M 处理器通过 NVIC(Nested Vectored Interrupt Controller) 实现可嵌套、可配置的中断优先级管理。
每个异常/中断具有一个 8 位优先级字段,但实际可用位数由芯片实现决定(如 STM32F1 为 4 位有效)。
优先级分为两个逻辑部分:
抢占优先级(Preemption Priority):决定是否允许高优先级中断嵌套打断正在执行的低优先级 ISR。数值越小,优先级越高。
子优先级(Subpriority / Response Priority):当多个中断具有相同抢占优先级且同时挂起时,
用于确定响应顺序,但不支持相互嵌套。
中断挂起概念:
外设已经"举手申请"了中断(比如按键按下、定时器溢出);
但因为某些原因(如当前在执行更高优先级的中断,或全局中断被关闭),CPU 还没来得及响应;
此时,该中断就被 标记为"挂起",等待后续处理。
整体按照:抢占优先级>子优先级>IRQ编号
3、EXIT扩展中断与事件控制器
bash
(1)事件的概念
当检测到触发信号时,直接告知其他外设进行事件的响应而不经过中断
(2)中断
中断则包括中断响应与事件触发,EXIT检测到触发信号,向NVIC报告中断进行裁决,之后进行事件响应
4、EXIT与NVIC搭配使用
bash
(1)当STM32的EXTI监测到GPIO或内部外设满足配置的边沿或电平触发条件,且其中断屏蔽位已使能时
(2)EXTI硬件会自动置位挂起寄存器,并向NVIC发出中断请求。
(3)NVIC根据该中断的优先级、当前CPU执行状态及屏蔽标志进行仲裁:若优先级足够高且未被屏蔽,则在当前指令完成后触发中断响应。
(4)Cortex-M内核自动保存上下文、从向量表获取对应ISR地址,并跳转执行。
(5)用户必须在ISR中手动清除EXTI_PR挂起位(写1清零),否则将导致重复进入中断。
(6)执行完毕后,通过异常返回指令自动恢复上下文,继续主程序。
(7)若中断发生时CPU正处理更高优先级任务,该中断将保持挂起状态,待条件满足后仍会被可靠处理,确保不丢失。
5、代码使用
bash
STM32Cube中配置:
(1)RCC使用外部晶振,CLOCK中选择PLL,频率设置72M
(2)SYS中配置程序使用Serial wire
(3)GPIO中断事件的配置(上升、下降)
(4)NVIC优先级分组,中断线使能
查看外部中断/事件控制器章节获取不同引脚对应的EXIT线,
通过PA0按键控制PB0的亮灭
bash
GPIO配置:
void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/*配置按键PA0中断 */
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_NVIC_SetPriority(EXTI0_IRQn, 12, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
// 配置LED灯PB0
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
}
中断程序配置:
bash
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_0) // 判断是 PA0 触发(EXTI0)
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); // PB0 = 低电平
HAL_Delay(500);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
}
}
bash
个人总结:
对于STM32有多条EXTI总线,都是调用HAL_GPIO_EXTI_Callback触发中断,在回调内部通过GPIO判断属于哪条总线。
对于同一总线则根据优先级