中断是STM32微控制器中最重要的机制之一,它允许处理器在正常运行程序时,对外部事件或内部条件做出即时响应。想象一下,当你正在看书时,突然电话铃声响起,你会先做个书签标记当前阅读位置,然后接听电话,通话结束后再回到原位置继续阅读——这就是中断的生动比喻。
在STM32中,中断的工作流程可分为三个阶段:
STM32F103系列支持60个可屏蔽中断和16个内核异常,这些中断通道已固定分配给相应外设,如:
NVIC(Nested Vectored Interrupt Controller)是ARM Cortex-M内核内置的嵌套向量中断控制器,负责管理所有中断请求。在STM32中,NVIC具有以下关键特性:
NVIC的寄存器组主要包括:
typedef struct {
__IO uint32_t ISER[8]; // 中断使能寄存器
__IO uint32_t ICER[8]; // 中断除能寄存器
__IO uint32_t ISPR[8]; // 中断挂起设置寄存器
__IO uint32_t ICPR[8]; // 中断挂起清除寄存器
__IO uint32_t IABR[8]; // 中断活动状态寄存器
__IO uint8_t IP[240]; // 中断优先级寄存器
} NVIC_Type;
每个寄存器的作用:
STM32的中断优先级分为抢占优先级和响应优先级(又称子优先级):
特性 | 抢占优先级 | 响应优先级 |
---|---|---|
作用 | 决定中断能否嵌套 | 决定同组内的执行顺序 |
比较规则 | 数值越小优先级越高 | 数值越小优先级越高 |
嵌套能力 | 高优先级可打断低优先级 | 不能打断同级中断 |
STM32提供了5种优先级分组方式,通过SCB->AIRCR寄存器的bit[10:8]配置:
分组 | 抢占优先级位数 | 子优先级位数 | 优先级组合数量 |
---|---|---|---|
NVIC_PriorityGroup_0 | 0位 | 4位 | 16级响应优先级 |
NVIC_PriorityGroup_1 | 1位 | 3位 | 2x8=16级 |
NVIC_PriorityGroup_2 | 2位 | 2位 | 4x4=16级 |
NVIC_PriorityGroup_3 | 3位 | 1位 | 8x2=16级 |
NVIC_PriorityGroup_4 | 4位 | 0位 | 16级抢占优先级 |
配置示例:
// 设置优先级分组为组2(2位抢占,2位响应)
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
注意:整个系统只需设置一次分组,重复设置会导致中断管理混乱
当多个中断同时发生时,NVIC按照以下规则处理:
实战案例:
// 配置TIM3中断:抢占1,子优先级3
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_Init(&NVIC_InitStructure);
// 配置TIM4中断:抢占1,子优先级2
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStructure);
结果:TIM4优先级高于TIM3(抢占优先级相同,子优先级更高)
中断向量表是存储在Flash起始位置(0x00000000)的地址数组,每个条目包含一个中断服务函数的入口地址。STM32的中断向量表包含:
典型向量表示例(startup_stm32f10x_hd.s):
__Vectors DCD __initial_sp ; 栈顶指针
DCD Reset_Handler ; 复位中断
DCD NMI_Handler ; NMI
...
DCD TIM1_UP_IRQHandler ; TIM1更新中断
DCD TIM2_IRQHandler ; TIM2全局中断
默认向量表位于Flash,但可以重定位到RAM实现动态修改:
// 将向量表复制到RAM并重定位
SCB->VTOR = (uint32_t)0x20000000; // 设置VTOR寄存器
memcpy((void*)0x20000000, (void*)0x08000000, VECTOR_TABLE_SIZE);
应用场景:需要运行时更换中断处理函数的场合
STM32的EXTI控制器具有20条中断线,特点包括:
EXTI线路分配:
完整示例(按键触发中断):
// 1. 使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2. 配置PA0为输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; // 下拉输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 使能AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
// 4. 映射PA0到EXTI0
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
// 5. 配置EXTI线
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_Init(&EXTI_InitStructure);
// 6. 配置NVIC
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x01;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 7. 中断服务函数
void EXTI0_IRQHandler(void) {
if(EXTI_GetITStatus(EXTI_Line0) != RESET) {
// 处理按键事件
EXTI_ClearITPendingBit(EXTI_Line0); // 清除标志
}
}
使用STM32CubeMX工具可简化中断配置:
优化案例:
volatile uint8_t flag = 0;
void TIM2_IRQHandler(void) {
if(TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
flag = 1; // 设置标志,主循环处理
}
}
int main(void) {
while(1) {
if(flag) {
flag = 0;
// 执行耗时任务
}
}
}
中断未触发:
HardFault错误:
中断响应延迟:
STM32在STOP模式下可通过特定中断唤醒:
// 配置RTC唤醒中断
HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, wakeup_interval, RTC_WAKEUPCLOCK_CK_SPRE_16BITS);
// 进入STOP模式
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
注意:只有EXTI、RTC等特定中断能唤醒STOP模式
在RTOS环境中,需遵循:
配置示例:
// FreeRTOS兼容性配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
// 设置SysTick为最低优先级
HAL_NVIC_SetPriority(SysTick_IRQn, 15, 0);
通过深入理解NVIC机制和合理配置中断优先级,开发者可以构建出既稳定又高效的嵌入式系统。建议结合STM32CubeMX工具和硬件调试器(如ST-Link)进行实践,将理论转化为实际工程能力。
拓展阅读:
- 《Cortex-M3权威指南》— Joseph Yiu
- ST官方文档AN2593《STM32F10xxx硬件开发指南》
- 野火/安富莱STM32开发板配套教程