中断向量表与异常处理机制¶
学习目标¶
完成本文学习后,你将能够:
- 理解中断向量表的结构和组织方式
- 掌握ARM Cortex-M的异常类型分类
- 了解异常优先级的管理机制
- 掌握NVIC(嵌套向量中断控制器)的配置方法
- 理解异常处理的完整流程
- 能够编写中断服务函数
- 掌握中断优先级分组和抢占机制
前置要求¶
在开始本文学习之前,你需要:
知识要求: - 了解ARM Cortex-M架构基础 - 熟悉C语言和基本的汇编语言 - 掌握内存映射和地址空间概念 - 了解处理器工作模式和特权级别
技能要求: - 能够阅读和编写C代码 - 了解寄存器操作方法 - 熟悉基本的调试技巧
推荐但非必需: - 有STM32或其他ARM MCU开发经验 - 了解启动流程和系统初始化 - 熟悉外设编程基础
概述¶
中断和异常是嵌入式系统中最重要的机制之一,它们使处理器能够及时响应外部事件和内部错误。ARM Cortex-M处理器提供了一套完整的异常处理机制,包括中断向量表、NVIC控制器和灵活的优先级管理系统。
为什么需要中断机制¶
- 实时响应:
- 及时处理外部事件(按键、传感器、通信)
- 避免轮询带来的延迟和资源浪费
-
提高系统响应速度
-
多任务处理:
- 支持多个外设同时工作
- 实现事件驱动的程序架构
-
提高CPU利用率
-
错误处理:
- 检测和处理系统错误
- 实现故障恢复机制
-
提高系统可靠性
-
功耗优化:
- CPU可以进入低功耗模式
- 事件发生时自动唤醒
- 降低系统功耗
中断与异常的区别¶
在ARM Cortex-M中,"异常"是一个更广泛的概念,包括中断和其他异常事件:
异常 (Exception)
├── 系统异常 (System Exceptions)
│ ├── Reset (复位)
│ ├── NMI (不可屏蔽中断)
│ ├── HardFault (硬件错误)
│ ├── MemManage (内存管理错误)
│ ├── BusFault (总线错误)
│ ├── UsageFault (用法错误)
│ ├── SVC (系统服务调用)
│ ├── PendSV (可挂起的系统调用)
│ └── SysTick (系统滴答定时器)
└── 外部中断 (External Interrupts)
├── IRQ0 - IRQ239
└── 由外设产生
术语说明: - 异常(Exception):所有打断正常程序流程的事件 - 中断(Interrupt):由外设产生的异常 - 系统异常:由处理器内部产生的异常 - 外部中断:由外设产生的中断请求
第一部分:中断向量表结构¶
向量表的作用¶
中断向量表是一个包含所有异常处理函数地址的数组,位于内存的固定位置。当异常发生时,处理器会从向量表中读取对应的处理函数地址并跳转执行。
向量表的关键特性:
- 固定位置:
- 默认位于地址0x00000000
- 可以通过VTOR寄存器重定位
-
必须256字节对齐
-
固定格式:
- 第一个条目是初始栈指针值
- 后续条目是异常处理函数地址
-
每个条目占4字节(32位)
-
优先级顺序:
- 表中位置决定了异常编号
- 异常编号影响默认优先级
- 系统异常在前,外部中断在后
向量表详细结构¶
以STM32F103为例,向量表的完整结构如下:
地址偏移 异常编号 异常名称 说明
0x0000 - __initial_sp 初始栈指针值
0x0004 1 Reset_Handler 复位处理函数
0x0008 2 NMI_Handler 不可屏蔽中断
0x000C 3 HardFault_Handler 硬件错误
0x0010 4 MemManage_Handler 内存管理错误
0x0014 5 BusFault_Handler 总线错误
0x0018 6 UsageFault_Handler 用法错误
0x001C 7 保留 -
0x0020 8 保留 -
0x0024 9 保留 -
0x0028 10 保留 -
0x002C 11 SVC_Handler 系统服务调用
0x0030 12 DebugMon_Handler 调试监视器
0x0034 13 保留 -
0x0038 14 PendSV_Handler 可挂起的系统调用
0x003C 15 SysTick_Handler 系统滴答定时器
0x0040 16 WWDG_IRQHandler 窗口看门狗
0x0044 17 PVD_IRQHandler 电源电压检测
0x0048 18 TAMPER_IRQHandler 侵入检测
0x004C 19 RTC_IRQHandler RTC全局中断
0x0050 20 FLASH_IRQHandler Flash全局中断
0x0054 21 RCC_IRQHandler RCC全局中断
0x0058 22 EXTI0_IRQHandler 外部中断线0
0x005C 23 EXTI1_IRQHandler 外部中断线1
... ... ... 更多外设中断
重要说明:
- 异常编号:
- 从1开始编号(0是栈指针)
- 1-15是系统异常
-
16及以上是外部中断(IRQ0对应异常16)
-
保留位置:
- 某些位置保留供将来使用
-
必须填充为0或默认处理函数
-
外设中断:
- 不同芯片的外设中断数量不同
- STM32F103有60个外设中断
- STM32F4系列可能有90+个中断
启动文件中的向量表定义¶
在启动文件(startup_stm32f1xx.s)中,向量表是这样定义的:
; 向量表定义
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD __initial_sp ; 栈顶地址
DCD Reset_Handler ; 复位向量
DCD NMI_Handler ; NMI处理函数
DCD HardFault_Handler ; 硬件错误处理
DCD MemManage_Handler ; 内存管理错误
DCD BusFault_Handler ; 总线错误
DCD UsageFault_Handler ; 用法错误
DCD 0 ; 保留
DCD 0 ; 保留
DCD 0 ; 保留
DCD 0 ; 保留
DCD SVC_Handler ; SVC处理函数
DCD DebugMon_Handler ; 调试监视器
DCD 0 ; 保留
DCD PendSV_Handler ; PendSV处理函数
DCD SysTick_Handler ; SysTick处理函数
; 外部中断
DCD WWDG_IRQHandler ; 窗口看门狗
DCD PVD_IRQHandler ; PVD通过EXTI检测
DCD TAMPER_IRQHandler ; 侵入检测
DCD RTC_IRQHandler ; RTC全局中断
DCD FLASH_IRQHandler ; Flash全局中断
DCD RCC_IRQHandler ; RCC全局中断
DCD EXTI0_IRQHandler ; 外部中断线0
DCD EXTI1_IRQHandler ; 外部中断线1
; ... 更多中断向量
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
AREA |.text|, CODE, READONLY
代码说明:
AREA RESET, DATA, READONLY:定义只读数据区域DCD:定义一个32位常量(Define Constant Data)__initial_sp:栈顶地址,由链接脚本定义EXPORT:导出符号,使其他文件可以引用EQU:定义常量,计算向量表大小
向量表重定位¶
默认情况下,向量表位于Flash的起始地址(0x08000000),但可以通过VTOR(Vector Table Offset Register)寄存器将其重定位到其他位置。
VTOR寄存器:
/**
* @brief 向量表偏移寄存器 (VTOR)
* @note 位于SCB (System Control Block)
*/
#define SCB_VTOR (*((volatile uint32_t*)0xE000ED08))
// VTOR寄存器位定义
// bit 31-7: TBLOFF - 向量表偏移地址(必须256字节对齐)
// bit 6-0: 保留,必须为0
重定位向量表示例:
/**
* @brief 重定位向量表到SRAM
* @note 用于在SRAM中运行代码或实现动态向量表
*/
void Relocate_Vector_Table_To_SRAM(void)
{
extern uint32_t __Vectors[]; // 向量表起始地址
uint32_t *src = __Vectors;
uint32_t *dst = (uint32_t*)0x20000000; // SRAM起始地址
uint32_t vector_count = 16 + 60; // 系统异常 + 外设中断
/* 1. 复制向量表到SRAM */
for (uint32_t i = 0; i < vector_count; i++) {
dst[i] = src[i];
}
/* 2. 设置VTOR指向新位置 */
SCB->VTOR = 0x20000000;
/* 3. 指令同步屏障 */
__DSB();
__ISB();
printf("向量表已重定位到SRAM: 0x%08X\n", SCB->VTOR);
}
/**
* @brief 重定位向量表到Flash的其他位置
* @param offset: 偏移地址(必须256字节对齐)
*/
void Relocate_Vector_Table(uint32_t offset)
{
/* 检查对齐 */
if (offset & 0xFF) {
printf("错误:向量表地址必须256字节对齐\n");
return;
}
/* 设置VTOR */
SCB->VTOR = FLASH_BASE + offset;
__DSB();
__ISB();
printf("向量表已重定位到: 0x%08X\n", SCB->VTOR);
}
重定位的应用场景:
- Bootloader应用:
- Bootloader使用默认向量表
- 应用程序使用重定位的向量表
-
实现固件升级功能
-
在SRAM中运行:
- 提高中断响应速度
- 实现动态修改向量表
-
用于特殊调试场景
-
多应用程序:
- 不同应用使用不同向量表
- 实现应用隔离
- 支持应用切换
第二部分:异常类型详解¶
系统异常¶
系统异常是由处理器内部产生的异常,用于处理系统级事件和错误。
1. Reset (异常1)¶
触发条件: - 上电复位 - 外部复位引脚 - 看门狗复位 - 软件复位
处理函数:
/**
* @brief 复位处理函数
* @note 系统启动后执行的第一个函数
*/
void Reset_Handler(void)
{
// 调用系统初始化
SystemInit();
// 跳转到main函数
__main();
}
2. NMI - 不可屏蔽中断 (异常2)¶
特点: - 不能被禁用或屏蔽 - 优先级固定为-2(仅次于Reset) - 用于处理关键错误
典型应用:
/**
* @brief NMI处理函数
* @note 处理关键系统错误
*/
void NMI_Handler(void)
{
/* 检查RCC时钟安全系统 */
if (RCC->CIR & RCC_CIR_CSSF) {
printf("时钟安全系统检测到HSE故障\n");
/* 清除标志 */
RCC->CIR |= RCC_CIR_CSSC;
/* 切换到HSI时钟 */
RCC->CR |= RCC_CR_HSION;
while(!(RCC->CR & RCC_CR_HSIRDY));
/* 切换系统时钟源 */
RCC->CFGR &= ~RCC_CFGR_SW;
RCC->CFGR |= RCC_CFGR_SW_HSI;
}
/* 其他NMI源处理 */
}
3. HardFault - 硬件错误 (异常3)¶
触发条件: - 访问无效内存地址 - 执行未定义指令 - 其他错误异常升级而来
处理函数:
/**
* @brief HardFault处理函数
* @note 捕获所有未处理的错误
*/
void HardFault_Handler(void)
{
printf("HardFault异常!\n");
/* 获取错误信息 */
uint32_t cfsr = SCB->CFSR; // 配置和状态寄存器
uint32_t hfsr = SCB->HFSR; // HardFault状态寄存器
uint32_t dfsr = SCB->DFSR; // 调试错误状态寄存器
printf("CFSR = 0x%08X\n", cfsr);
printf("HFSR = 0x%08X\n", hfsr);
printf("DFSR = 0x%08X\n", dfsr);
/* 检查是否由其他错误升级 */
if (hfsr & SCB_HFSR_FORCED_Msk) {
printf("由其他错误升级为HardFault\n");
}
/* 进入无限循环或复位 */
while(1);
}
4. MemManage - 内存管理错误 (异常4)¶
触发条件: - MPU检测到非法内存访问 - 访问未定义的内存区域 - 违反内存访问权限
处理函数:
/**
* @brief MemManage处理函数
* @note 需要先使能MemManage异常
*/
void MemManage_Handler(void)
{
printf("MemManage异常!\n");
/* 检查错误类型 */
uint32_t cfsr = SCB->CFSR;
if (cfsr & SCB_CFSR_IACCVIOL_Msk) {
printf("指令访问违规\n");
}
if (cfsr & SCB_CFSR_DACCVIOL_Msk) {
printf("数据访问违规\n");
/* 获取违规地址 */
if (cfsr & SCB_CFSR_MMARVALID_Msk) {
uint32_t mmfar = SCB->MMFAR;
printf("违规地址: 0x%08X\n", mmfar);
}
}
if (cfsr & SCB_CFSR_MSTKERR_Msk) {
printf("入栈错误\n");
}
if (cfsr & SCB_CFSR_MUNSTKERR_Msk) {
printf("出栈错误\n");
}
/* 清除错误标志 */
SCB->CFSR = cfsr;
while(1);
}
/**
* @brief 使能MemManage异常
*/
void Enable_MemManage_Fault(void)
{
SCB->SHCSR |= SCB_SHCSR_MEMFAULTENA_Msk;
}
5. BusFault - 总线错误 (异常5)¶
触发条件: - 访问不存在的外设 - 总线访问超时 - 预取指令错误
处理函数:
/**
* @brief BusFault处理函数
*/
void BusFault_Handler(void)
{
printf("BusFault异常!\n");
uint32_t cfsr = SCB->CFSR;
if (cfsr & SCB_CFSR_IBUSERR_Msk) {
printf("指令总线错误\n");
}
if (cfsr & SCB_CFSR_PRECISERR_Msk) {
printf("精确数据总线错误\n");
/* 获取错误地址 */
if (cfsr & SCB_CFSR_BFARVALID_Msk) {
uint32_t bfar = SCB->BFAR;
printf("错误地址: 0x%08X\n", bfar);
}
}
if (cfsr & SCB_CFSR_IMPRECISERR_Msk) {
printf("非精确数据总线错误\n");
}
if (cfsr & SCB_CFSR_UNSTKERR_Msk) {
printf("出栈总线错误\n");
}
if (cfsr & SCB_CFSR_STKERR_Msk) {
printf("入栈总线错误\n");
}
SCB->CFSR = cfsr;
while(1);
}
6. UsageFault - 用法错误 (异常6)¶
触发条件: - 执行未定义指令 - 未对齐的内存访问 - 除零错误(需使能) - 协处理器访问错误
处理函数:
/**
* @brief UsageFault处理函数
*/
void UsageFault_Handler(void)
{
printf("UsageFault异常!\n");
uint32_t cfsr = SCB->CFSR;
if (cfsr & SCB_CFSR_UNDEFINSTR_Msk) {
printf("未定义指令\n");
}
if (cfsr & SCB_CFSR_INVSTATE_Msk) {
printf("无效状态\n");
}
if (cfsr & SCB_CFSR_INVPC_Msk) {
printf("无效PC值\n");
}
if (cfsr & SCB_CFSR_NOCP_Msk) {
printf("无协处理器\n");
}
if (cfsr & SCB_CFSR_UNALIGNED_Msk) {
printf("未对齐访问\n");
}
if (cfsr & SCB_CFSR_DIVBYZERO_Msk) {
printf("除零错误\n");
}
SCB->CFSR = cfsr;
while(1);
}
/**
* @brief 使能UsageFault异常
*/
void Enable_UsageFault(void)
{
/* 使能UsageFault */
SCB->SHCSR |= SCB_SHCSR_USGFAULTENA_Msk;
/* 使能除零检测 */
SCB->CCR |= SCB_CCR_DIV_0_TRP_Msk;
/* 使能未对齐访问检测 */
SCB->CCR |= SCB_CCR_UNALIGN_TRP_Msk;
}
7. SVC - 系统服务调用 (异常11)¶
用途: - 用户程序请求系统服务 - 实现系统调用接口 - RTOS任务切换
使用示例:
/**
* @brief 触发SVC异常
* @param svc_num: SVC号
*/
void Trigger_SVC(uint8_t svc_num)
{
__asm volatile ("SVC %0" : : "I" (svc_num));
}
/**
* @brief SVC处理函数
*/
void SVC_Handler(void)
{
/* 获取栈帧 */
uint32_t *stack_frame;
if (__get_CONTROL() & CONTROL_SPSEL_Msk) {
stack_frame = (uint32_t*)__get_PSP();
} else {
stack_frame = (uint32_t*)__get_MSP();
}
/* 获取返回地址 */
uint32_t pc = stack_frame[6];
/* 获取SVC号 */
uint8_t svc_number = ((uint8_t*)pc)[-2];
printf("SVC #%d 被调用\n", svc_number);
/* 根据SVC号执行相应服务 */
switch(svc_number) {
case 0:
// 系统服务0
break;
case 1:
// 系统服务1
break;
default:
printf("未知的SVC号\n");
break;
}
}
8. PendSV - 可挂起的系统调用 (异常14)¶
特点: - 可以被挂起和延迟执行 - 优先级可配置(通常设为最低) - RTOS中用于任务切换
应用示例:
/**
* @brief 触发PendSV异常
*/
void Trigger_PendSV(void)
{
SCB->ICSR |= SCB_ICSR_PENDSVSET_Msk;
}
/**
* @brief PendSV处理函数
* @note RTOS中用于任务切换
*/
void PendSV_Handler(void)
{
/* 保存当前任务上下文 */
__asm volatile (
"MRS R0, PSP\n" // 获取PSP
"STMDB R0!, {R4-R11}\n" // 保存R4-R11到栈
"STR R0, [%0]\n" // 保存新的栈指针
:
: "r" (¤t_task_sp)
: "r0"
);
/* 选择下一个任务 */
// next_task = scheduler();
/* 恢复新任务上下文 */
__asm volatile (
"LDR R0, [%0]\n" // 加载新任务栈指针
"LDMIA R0!, {R4-R11}\n" // 恢复R4-R11
"MSR PSP, R0\n" // 设置PSP
"BX LR\n" // 返回
:
: "r" (&next_task_sp)
: "r0"
);
}
9. SysTick - 系统滴答定时器 (异常15)¶
用途: - 提供系统时基 - RTOS时间片调度 - 延时函数实现
配置和使用:
/**
* @brief 配置SysTick定时器
* @param ticks: 定时器重载值
* @retval 0: 成功, 1: 失败
*/
uint32_t SysTick_Config(uint32_t ticks)
{
/* 检查重载值范围 */
if ((ticks - 1) > SysTick_LOAD_RELOAD_Msk) {
return 1; // 超出范围
}
/* 设置重载值 */
SysTick->LOAD = ticks - 1;
/* 设置优先级 */
NVIC_SetPriority(SysTick_IRQn, (1UL << __NVIC_PRIO_BITS) - 1UL);
/* 清除当前值 */
SysTick->VAL = 0;
/* 使能SysTick */
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | // 使用处理器时钟
SysTick_CTRL_TICKINT_Msk | // 使能中断
SysTick_CTRL_ENABLE_Msk; // 使能定时器
return 0;
}
/**
* @brief SysTick中断处理函数
*/
void SysTick_Handler(void)
{
/* 更新系统滴答计数 */
HAL_IncTick();
/* RTOS时间片调度 */
// OS_TimeSlice();
}
/**
* @brief 配置1ms的SysTick
*/
void SysTick_Init_1ms(void)
{
/* 假设系统时钟72MHz */
SysTick_Config(72000); // 72MHz / 72000 = 1kHz (1ms)
}
外部中断¶
外部中断由外设产生,用于通知处理器外部事件的发生。
外部中断的特点:
- 数量众多:
- STM32F1系列:60个外部中断
- STM32F4系列:90+个外部中断
-
不同芯片型号数量不同
-
可配置优先级:
- 每个中断都可以配置优先级
- 支持抢占优先级和子优先级
-
通过NVIC进行管理
-
可以使能/禁用:
- 通过NVIC控制中断使能
- 可以单独使能或禁用每个中断
- 支持挂起和清除中断
常见外设中断示例:
/**
* @brief EXTI0外部中断处理函数
* @note 处理GPIO PA0的外部中断
*/
void EXTI0_IRQHandler(void)
{
/* 检查中断标志 */
if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET) {
/* 清除中断标志 */
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
/* 处理中断事件 */
printf("EXTI0中断触发\n");
Handle_Button_Press();
}
}
/**
* @brief USART1中断处理函数
*/
void USART1_IRQHandler(void)
{
/* 接收中断 */
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE)) {
uint8_t data = (uint8_t)(huart1.Instance->DR & 0xFF);
UART_Receive_Callback(data);
}
/* 发送完成中断 */
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC)) {
__HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_TC);
UART_Transmit_Complete_Callback();
}
}
/**
* @brief TIM2定时器中断处理函数
*/
void TIM2_IRQHandler(void)
{
/* 更新中断 */
if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE)) {
__HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE);
/* 定时器溢出处理 */
Timer_Overflow_Callback();
}
}
第三部分:NVIC配置¶
NVIC简介¶
NVIC(Nested Vectored Interrupt Controller,嵌套向量中断控制器)是ARM Cortex-M处理器的中断管理单元,负责管理所有外部中断和部分系统异常。
NVIC的主要功能:
- 中断使能/禁用:
- 控制每个中断的使能状态
-
支持批量操作
-
优先级管理:
- 配置中断优先级
-
支持抢占和嵌套
-
挂起管理:
- 设置和清除中断挂起状态
-
查询中断状态
-
活动状态:
- 查询中断是否正在执行
- 支持中断嵌套
NVIC寄存器¶
NVIC提供了一组寄存器用于中断管理:
/**
* @brief NVIC寄存器结构
*/
typedef struct
{
__IOM uint32_t ISER[8U]; // 中断使能寄存器
uint32_t RESERVED0[24U];
__IOM uint32_t ICER[8U]; // 中断清除使能寄存器
uint32_t RESERVED1[24U];
__IOM uint32_t ISPR[8U]; // 中断挂起寄存器
uint32_t RESERVED2[24U];
__IOM uint32_t ICPR[8U]; // 中断清除挂起寄存器
uint32_t RESERVED3[24U];
__IOM uint32_t IABR[8U]; // 中断活动位寄存器
uint32_t RESERVED4[56U];
__IOM uint8_t IP[240U]; // 中断优先级寄存器
uint32_t RESERVED5[644U];
__OM uint32_t STIR; // 软件触发中断寄存器
} NVIC_Type;
#define NVIC ((NVIC_Type*)0xE000E100UL)
寄存器说明:
- ISER (Interrupt Set-Enable Registers):
- 使能中断
- 写1使能对应中断
-
读取返回使能状态
-
ICER (Interrupt Clear-Enable Registers):
- 禁用中断
-
写1禁用对应中断
-
ISPR (Interrupt Set-Pending Registers):
- 设置中断挂起状态
-
写1设置挂起
-
ICPR (Interrupt Clear-Pending Registers):
- 清除中断挂起状态
-
写1清除挂起
-
IABR (Interrupt Active Bit Registers):
- 只读,显示中断活动状态
-
1表示中断正在执行
-
IP (Interrupt Priority Registers):
- 配置中断优先级
- 每个中断占8位
NVIC基本操作¶
使能和禁用中断:
/**
* @brief 使能中断
* @param IRQn: 中断号
*/
void NVIC_EnableIRQ(IRQn_Type IRQn)
{
if ((int32_t)(IRQn) >= 0) {
NVIC->ISER[(((uint32_t)IRQn) >> 5UL)] =
(uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
}
}
/**
* @brief 禁用中断
* @param IRQn: 中断号
*/
void NVIC_DisableIRQ(IRQn_Type IRQn)
{
if ((int32_t)(IRQn) >= 0) {
NVIC->ICER[(((uint32_t)IRQn) >> 5UL)] =
(uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
}
}
/**
* @brief 获取中断使能状态
* @param IRQn: 中断号
* @retval 0: 禁用, 1: 使能
*/
uint32_t NVIC_GetEnableIRQ(IRQn_Type IRQn)
{
if ((int32_t)(IRQn) >= 0) {
return ((uint32_t)(((NVIC->ISER[(((uint32_t)IRQn) >> 5UL)] &
(1UL << (((uint32_t)IRQn) & 0x1FUL))) != 0UL) ? 1UL : 0UL));
}
return 0;
}
挂起和清除中断:
/**
* @brief 设置中断挂起
* @param IRQn: 中断号
*/
void NVIC_SetPendingIRQ(IRQn_Type IRQn)
{
if ((int32_t)(IRQn) >= 0) {
NVIC->ISPR[(((uint32_t)IRQn) >> 5UL)] =
(uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
}
}
/**
* @brief 清除中断挂起
* @param IRQn: 中断号
*/
void NVIC_ClearPendingIRQ(IRQn_Type IRQn)
{
if ((int32_t)(IRQn) >= 0) {
NVIC->ICPR[(((uint32_t)IRQn) >> 5UL)] =
(uint32_t)(1UL << (((uint32_t)IRQn) & 0x1FUL));
}
}
/**
* @brief 获取中断挂起状态
* @param IRQn: 中断号
* @retval 0: 未挂起, 1: 挂起
*/
uint32_t NVIC_GetPendingIRQ(IRQn_Type IRQn)
{
if ((int32_t)(IRQn) >= 0) {
return ((uint32_t)(((NVIC->ISPR[(((uint32_t)IRQn) >> 5UL)] &
(1UL << (((uint32_t)IRQn) & 0x1FUL))) != 0UL) ? 1UL : 0UL));
}
return 0;
}
/**
* @brief 获取中断活动状态
* @param IRQn: 中断号
* @retval 0: 未活动, 1: 活动
*/
uint32_t NVIC_GetActive(IRQn_Type IRQn)
{
if ((int32_t)(IRQn) >= 0) {
return ((uint32_t)(((NVIC->IABR[(((uint32_t)IRQn) >> 5UL)] &
(1UL << (((uint32_t)IRQn) & 0x1FUL))) != 0UL) ? 1UL : 0UL));
}
return 0;
}
配置中断优先级:
/**
* @brief 设置中断优先级
* @param IRQn: 中断号
* @param priority: 优先级值(0-15,值越小优先级越高)
*/
void NVIC_SetPriority(IRQn_Type IRQn, uint32_t priority)
{
if ((int32_t)(IRQn) >= 0) {
NVIC->IP[((uint32_t)IRQn)] =
(uint8_t)((priority << (8U - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
} else {
SCB->SHP[(((uint32_t)IRQn) & 0xFUL)-4UL] =
(uint8_t)((priority << (8U - __NVIC_PRIO_BITS)) & (uint32_t)0xFFUL);
}
}
/**
* @brief 获取中断优先级
* @param IRQn: 中断号
* @retval 优先级值
*/
uint32_t NVIC_GetPriority(IRQn_Type IRQn)
{
if ((int32_t)(IRQn) >= 0) {
return (uint32_t)(NVIC->IP[((uint32_t)IRQn)] >> (8U - __NVIC_PRIO_BITS));
} else {
return (uint32_t)(SCB->SHP[(((uint32_t)IRQn) & 0xFUL)-4UL] >>
(8U - __NVIC_PRIO_BITS));
}
}
优先级分组¶
ARM Cortex-M支持将优先级分为抢占优先级和子优先级两部分。
优先级分组配置:
/**
* @brief 配置优先级分组
* @param PriorityGroup: 优先级分组
* @arg NVIC_PRIORITYGROUP_0: 0位抢占优先级,4位子优先级
* @arg NVIC_PRIORITYGROUP_1: 1位抢占优先级,3位子优先级
* @arg NVIC_PRIORITYGROUP_2: 2位抢占优先级,2位子优先级
* @arg NVIC_PRIORITYGROUP_3: 3位抢占优先级,1位子优先级
* @arg NVIC_PRIORITYGROUP_4: 4位抢占优先级,0位子优先级
*/
void NVIC_SetPriorityGrouping(uint32_t PriorityGroup)
{
uint32_t reg_value;
uint32_t PriorityGroupTmp = (PriorityGroup & (uint32_t)0x07UL);
reg_value = SCB->AIRCR;
reg_value &= ~((uint32_t)(SCB_AIRCR_VECTKEY_Msk | SCB_AIRCR_PRIGROUP_Msk));
reg_value = (reg_value |
((uint32_t)0x5FAUL << SCB_AIRCR_VECTKEY_Pos) |
(PriorityGroupTmp << SCB_AIRCR_PRIGROUP_Pos));
SCB->AIRCR = reg_value;
}
/**
* @brief 获取优先级分组
* @retval 优先级分组值
*/
uint32_t NVIC_GetPriorityGrouping(void)
{
return ((uint32_t)((SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) >>
SCB_AIRCR_PRIGROUP_Pos));
}
/**
* @brief 编码优先级
* @param PriorityGroup: 优先级分组
* @param PreemptPriority: 抢占优先级
* @param SubPriority: 子优先级
* @retval 编码后的优先级值
*/
uint32_t NVIC_EncodePriority(uint32_t PriorityGroup,
uint32_t PreemptPriority,
uint32_t SubPriority)
{
uint32_t PriorityGroupTmp = (PriorityGroup & (uint32_t)0x07UL);
uint32_t PreemptPriorityBits;
uint32_t SubPriorityBits;
PreemptPriorityBits = ((7UL - PriorityGroupTmp) > (uint32_t)(__NVIC_PRIO_BITS)) ?
(uint32_t)(__NVIC_PRIO_BITS) : (uint32_t)(7UL - PriorityGroupTmp);
SubPriorityBits = ((PriorityGroupTmp + (uint32_t)(__NVIC_PRIO_BITS)) < (uint32_t)7UL) ?
(uint32_t)0UL : (uint32_t)((PriorityGroupTmp - 7UL) + (uint32_t)(__NVIC_PRIO_BITS));
return (
((PreemptPriority & (uint32_t)((1UL << (PreemptPriorityBits)) - 1UL)) << SubPriorityBits) |
((SubPriority & (uint32_t)((1UL << (SubPriorityBits)) - 1UL)))
);
}
优先级分组示例:
/**
* @brief 配置中断优先级示例
*/
void Interrupt_Priority_Config(void)
{
/* 配置优先级分组为2:2 */
NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);
/* 配置EXTI0中断:抢占优先级2,子优先级1 */
uint32_t priority = NVIC_EncodePriority(NVIC_PRIORITYGROUP_2, 2, 1);
NVIC_SetPriority(EXTI0_IRQn, priority);
NVIC_EnableIRQ(EXTI0_IRQn);
/* 配置USART1中断:抢占优先级1,子优先级0 */
priority = NVIC_EncodePriority(NVIC_PRIORITYGROUP_2, 1, 0);
NVIC_SetPriority(USART1_IRQn, priority);
NVIC_EnableIRQ(USART1_IRQn);
/* 配置TIM2中断:抢占优先级2,子优先级2 */
priority = NVIC_EncodePriority(NVIC_PRIORITYGROUP_2, 2, 2);
NVIC_SetPriority(TIM2_IRQn, priority);
NVIC_EnableIRQ(TIM2_IRQn);
}
第四部分:异常处理流程¶
异常响应过程¶
当异常发生时,处理器会自动执行以下步骤:
1. 挂起当前指令执行
↓
2. 保存上下文到栈
- xPSR, PC, LR, R12, R3-R0
↓
3. 更新LR为特殊值
- 0xFFFFFFF9: 返回线程模式,使用MSP
- 0xFFFFFFFD: 返回线程模式,使用PSP
- 0xFFFFFFF1: 返回处理器模式,使用MSP
↓
4. 切换到处理器模式
- 自动切换到特权级别
- 使用MSP
↓
5. 从向量表读取处理函数地址
↓
6. 跳转到异常处理函数
↓
7. 执行异常处理代码
↓
8. 异常返回(BX LR)
↓
9. 自动恢复上下文
↓
10. 返回被打断的代码
栈帧结构:
异常发生时自动压栈的寄存器:
高地址
┌─────────┐
│ xPSR │ ← SP + 28
├─────────┤
│ PC │ ← SP + 24 (返回地址)
├─────────┤
│ LR │ ← SP + 20
├─────────┤
│ R12 │ ← SP + 16
├─────────┤
│ R3 │ ← SP + 12
├─────────┤
│ R2 │ ← SP + 8
├─────────┤
│ R1 │ ← SP + 4
├─────────┤
│ R0 │ ← SP (栈指针)
└─────────┘
低地址
获取栈帧信息:
/**
* @brief 获取异常栈帧
* @retval 栈帧指针
*/
uint32_t* Get_Exception_Stack_Frame(void)
{
uint32_t *stack_frame;
/* 检查使用的栈指针 */
if (__get_CONTROL() & CONTROL_SPSEL_Msk) {
stack_frame = (uint32_t*)__get_PSP();
} else {
stack_frame = (uint32_t*)__get_MSP();
}
return stack_frame;
}
/**
* @brief 打印异常栈帧信息
*/
void Print_Exception_Stack_Frame(void)
{
uint32_t *stack_frame = Get_Exception_Stack_Frame();
printf("=== 异常栈帧 ===\n");
printf("R0 = 0x%08X\n", stack_frame[0]);
printf("R1 = 0x%08X\n", stack_frame[1]);
printf("R2 = 0x%08X\n", stack_frame[2]);
printf("R3 = 0x%08X\n", stack_frame[3]);
printf("R12 = 0x%08X\n", stack_frame[4]);
printf("LR = 0x%08X\n", stack_frame[5]);
printf("PC = 0x%08X\n", stack_frame[6]);
printf("xPSR= 0x%08X\n", stack_frame[7]);
}
异常返回机制¶
异常返回通过执行BX LR指令实现,LR寄存器包含特殊的返回值(EXC_RETURN)。
EXC_RETURN值:
0xFFFFFFF1: 返回处理器模式,使用MSP
0xFFFFFFF9: 返回线程模式,使用MSP
0xFFFFFFFD: 返回线程模式,使用PSP
0xFFFFFFE1: 返回处理器模式,使用MSP,浮点上下文
0xFFFFFFE9: 返回线程模式,使用MSP,浮点上下文
0xFFFFFFED: 返回线程模式,使用PSP,浮点上下文
异常返回示例:
/**
* @brief 中断处理函数示例
*/
void EXTI0_IRQHandler(void)
{
/* 处理中断 */
if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0)) {
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
/* 中断处理代码 */
Handle_Interrupt();
}
/* 函数返回时自动执行异常返回 */
/* 编译器会生成 BX LR 指令 */
}
中断嵌套¶
ARM Cortex-M支持中断嵌套,高优先级中断可以打断低优先级中断。
嵌套规则:
- 抢占优先级决定嵌套:
- 只有抢占优先级更高的中断才能嵌套
-
子优先级不影响嵌套
-
自动保存和恢复:
- 硬件自动保存被打断中断的上下文
-
返回时自动恢复
-
尾链优化:
- 连续的中断可以优化上下文切换
- 减少压栈和出栈开销
嵌套示例:
/**
* @brief 低优先级中断(抢占优先级3)
*/
void TIM2_IRQHandler(void)
{
printf("TIM2中断开始\n");
/* 长时间处理 */
for (volatile int i = 0; i < 1000000; i++);
printf("TIM2中断结束\n");
}
/**
* @brief 高优先级中断(抢占优先级1)
*/
void EXTI0_IRQHandler(void)
{
printf(" EXTI0中断开始(嵌套)\n");
/* 快速处理 */
Handle_Critical_Event();
printf(" EXTI0中断结束\n");
}
/**
* @brief 配置中断嵌套
*/
void Configure_Nested_Interrupts(void)
{
/* 配置优先级分组 */
NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);
/* TIM2: 抢占优先级3,子优先级0 */
NVIC_SetPriority(TIM2_IRQn, NVIC_EncodePriority(NVIC_PRIORITYGROUP_2, 3, 0));
NVIC_EnableIRQ(TIM2_IRQn);
/* EXTI0: 抢占优先级1,子优先级0 */
NVIC_SetPriority(EXTI0_IRQn, NVIC_EncodePriority(NVIC_PRIORITYGROUP_2, 1, 0));
NVIC_EnableIRQ(EXTI0_IRQn);
}
执行流程:
主程序运行
↓
TIM2中断触发 → 进入TIM2_IRQHandler
↓
正在执行TIM2处理代码
↓
EXTI0中断触发(优先级更高)
↓
保存TIM2上下文 → 进入EXTI0_IRQHandler
↓
执行EXTI0处理代码
↓
EXTI0返回 → 恢复TIM2上下文
↓
继续执行TIM2处理代码
↓
TIM2返回 → 返回主程序
第五部分:中断编程实践¶
完整的中断配置流程¶
以配置外部中断为例,展示完整的中断配置过程:
/**
* @brief 配置EXTI0外部中断
* @note PA0引脚,下降沿触发
*/
void EXTI0_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 1. 使能GPIO时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE();
/* 2. 配置GPIO */
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; // 下降沿触发中断
GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* 3. 配置NVIC */
/* 设置优先级分组 */
NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);
/* 设置中断优先级:抢占优先级2,子优先级1 */
uint32_t priority = NVIC_EncodePriority(NVIC_PRIORITYGROUP_2, 2, 1);
NVIC_SetPriority(EXTI0_IRQn, priority);
/* 使能中断 */
NVIC_EnableIRQ(EXTI0_IRQn);
printf("EXTI0中断已配置\n");
}
/**
* @brief EXTI0中断处理函数
*/
void EXTI0_IRQHandler(void)
{
/* 检查中断标志 */
if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) != RESET) {
/* 清除中断标志 */
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
/* 处理中断事件 */
printf("按键按下\n");
/* 翻转LED */
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}
}
定时器中断配置¶
/**
* @brief 配置TIM2定时器中断
* @note 1ms定时中断
*/
void TIM2_Config(void)
{
TIM_HandleTypeDef htim2;
/* 1. 使能TIM2时钟 */
__HAL_RCC_TIM2_CLK_ENABLE();
/* 2. 配置定时器参数 */
htim2.Instance = TIM2;
htim2.Init.Prescaler = 7200 - 1; // 预分频:72MHz/7200 = 10kHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 10 - 1; // 周期:10kHz/10 = 1kHz (1ms)
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim2) != HAL_OK) {
Error_Handler();
}
/* 3. 配置NVIC */
NVIC_SetPriority(TIM2_IRQn, NVIC_EncodePriority(NVIC_PRIORITYGROUP_2, 2, 0));
NVIC_EnableIRQ(TIM2_IRQn);
/* 4. 启动定时器中断 */
HAL_TIM_Base_Start_IT(&htim2);
printf("TIM2定时器中断已配置\n");
}
/**
* @brief TIM2中断处理函数
*/
void TIM2_IRQHandler(void)
{
/* 检查更新中断标志 */
if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE) != RESET) {
if (__HAL_TIM_GET_IT_SOURCE(&htim2, TIM_IT_UPDATE) != RESET) {
/* 清除中断标志 */
__HAL_TIM_CLEAR_IT(&htim2, TIM_IT_UPDATE);
/* 定时器溢出处理 */
static uint32_t counter = 0;
counter++;
if (counter >= 1000) { // 1秒
counter = 0;
printf("1秒定时到\n");
}
}
}
}
UART中断配置¶
/**
* @brief 配置USART1中断
*/
void USART1_Config(void)
{
UART_HandleTypeDef huart1;
/* 1. 使能时钟 */
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/* 2. 配置GPIO */
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10; // TX, RX
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* 3. 配置UART参数 */
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK) {
Error_Handler();
}
/* 4. 配置NVIC */
NVIC_SetPriority(USART1_IRQn, NVIC_EncodePriority(NVIC_PRIORITYGROUP_2, 1, 0));
NVIC_EnableIRQ(USART1_IRQn);
/* 5. 使能接收中断 */
__HAL_UART_ENABLE_IT(&huart1, UART_IT_RXNE);
printf("USART1中断已配置\n");
}
/**
* @brief USART1中断处理函数
*/
void USART1_IRQHandler(void)
{
/* 接收中断 */
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE) != RESET) {
if (__HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_RXNE) != RESET) {
/* 读取接收到的数据 */
uint8_t data = (uint8_t)(huart1.Instance->DR & 0xFF);
/* 处理接收数据 */
UART_Receive_Handler(data);
}
}
/* 发送完成中断 */
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_TC) != RESET) {
if (__HAL_UART_GET_IT_SOURCE(&huart1, UART_IT_TC) != RESET) {
__HAL_UART_CLEAR_FLAG(&huart1, UART_FLAG_TC);
/* 发送完成处理 */
UART_Transmit_Complete_Handler();
}
}
}
/**
* @brief UART接收数据处理
*/
void UART_Receive_Handler(uint8_t data)
{
static uint8_t rx_buffer[256];
static uint8_t rx_index = 0;
/* 存储接收数据 */
rx_buffer[rx_index++] = data;
/* 检查是否接收完整帧 */
if (data == '\n' || rx_index >= 256) {
/* 处理接收到的数据 */
Process_Received_Data(rx_buffer, rx_index);
rx_index = 0;
}
}
中断优先级管理¶
/**
* @brief 系统中断优先级配置
* @note 统一管理所有中断的优先级
*/
void System_Interrupt_Priority_Config(void)
{
/* 配置优先级分组:2位抢占,2位子优先级 */
NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);
/* 关键中断:抢占优先级0(最高) */
NVIC_SetPriority(NMI_IRQn, 0);
/* 高优先级中断:抢占优先级1 */
NVIC_SetPriority(USART1_IRQn, NVIC_EncodePriority(NVIC_PRIORITYGROUP_2, 1, 0));
NVIC_SetPriority(EXTI0_IRQn, NVIC_EncodePriority(NVIC_PRIORITYGROUP_2, 1, 1));
/* 中等优先级中断:抢占优先级2 */
NVIC_SetPriority(TIM2_IRQn, NVIC_EncodePriority(NVIC_PRIORITYGROUP_2, 2, 0));
NVIC_SetPriority(ADC1_IRQn, NVIC_EncodePriority(NVIC_PRIORITYGROUP_2, 2, 1));
/* 低优先级中断:抢占优先级3 */
NVIC_SetPriority(DMA1_Channel1_IRQn, NVIC_EncodePriority(NVIC_PRIORITYGROUP_2, 3, 0));
/* 系统异常优先级 */
NVIC_SetPriority(SysTick_IRQn, NVIC_EncodePriority(NVIC_PRIORITYGROUP_2, 3, 0));
NVIC_SetPriority(PendSV_IRQn, NVIC_EncodePriority(NVIC_PRIORITYGROUP_2, 3, 3));
}
第六部分:中断编程最佳实践¶
中断服务函数设计原则¶
- 快速执行:
- 中断处理应尽可能快
- 避免长时间占用CPU
-
复杂处理放到主循环
-
最小化操作:
- 只做必要的处理
- 设置标志位,主循环处理
-
避免复杂计算
-
避免阻塞:
- 不使用延时函数
- 不等待外设就绪
-
不使用printf(调试除外)
-
保护共享资源:
- 使用临界区保护
- 避免竞态条件
- 使用原子操作
良好的中断处理示例:
/* 全局标志和数据 */
volatile uint8_t uart_rx_flag = 0;
volatile uint8_t uart_rx_data = 0;
/**
* @brief UART中断处理函数(推荐)
* @note 快速处理,设置标志
*/
void USART1_IRQHandler(void)
{
if (__HAL_UART_GET_FLAG(&huart1, UART_FLAG_RXNE)) {
/* 读取数据 */
uart_rx_data = (uint8_t)(huart1.Instance->DR & 0xFF);
/* 设置标志 */
uart_rx_flag = 1;
/* 快速返回 */
}
}
/**
* @brief 主循环处理
*/
void main_loop(void)
{
while(1) {
/* 检查标志 */
if (uart_rx_flag) {
uart_rx_flag = 0;
/* 在主循环中处理数据 */
Process_UART_Data(uart_rx_data);
}
/* 其他任务 */
}
}
不推荐的中断处理:
/**
* @brief 不推荐的中断处理函数
* @note 包含延时和复杂处理
*/
void EXTI0_IRQHandler(void)
{
if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0)) {
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
/* ❌ 不推荐:延时 */
HAL_Delay(100);
/* ❌ 不推荐:复杂计算 */
float result = Complex_Calculation();
/* ❌ 不推荐:printf */
printf("Result: %f\n", result);
/* ❌ 不推荐:等待外设 */
while(!UART_Ready());
Send_Data(result);
}
}
临界区保护¶
/**
* @brief 进入临界区
* @retval 之前的中断状态
*/
uint32_t Enter_Critical_Section(void)
{
uint32_t primask = __get_PRIMASK();
__disable_irq();
return primask;
}
/**
* @brief 退出临界区
* @param primask: 之前的中断状态
*/
void Exit_Critical_Section(uint32_t primask)
{
__set_PRIMASK(primask);
}
/**
* @brief 使用临界区保护共享变量
*/
void Update_Shared_Variable(void)
{
/* 进入临界区 */
uint32_t primask = Enter_Critical_Section();
/* 访问共享变量 */
shared_counter++;
shared_buffer[index] = data;
/* 退出临界区 */
Exit_Critical_Section(primask);
}
/**
* @brief 使用宏简化临界区
*/
#define CRITICAL_SECTION_BEGIN() uint32_t __primask = __get_PRIMASK(); __disable_irq()
#define CRITICAL_SECTION_END() __set_PRIMASK(__primask)
void Example_Function(void)
{
CRITICAL_SECTION_BEGIN();
/* 临界区代码 */
shared_data = new_value;
CRITICAL_SECTION_END();
}
中断调试技巧¶
/**
* @brief 中断统计信息
*/
typedef struct {
uint32_t count; // 中断次数
uint32_t max_duration; // 最大执行时间
uint32_t total_duration; // 总执行时间
} IRQ_Stats_t;
IRQ_Stats_t irq_stats[60]; // 假设60个外设中断
/**
* @brief 中断性能监控
*/
void IRQ_Performance_Monitor_Start(IRQn_Type IRQn)
{
/* 启动DWT计数器 */
DWT->CYCCNT = 0;
}
void IRQ_Performance_Monitor_End(IRQn_Type IRQn)
{
/* 读取执行周期数 */
uint32_t cycles = DWT->CYCCNT;
/* 更新统计信息 */
irq_stats[IRQn].count++;
irq_stats[IRQn].total_duration += cycles;
if (cycles > irq_stats[IRQn].max_duration) {
irq_stats[IRQn].max_duration = cycles;
}
}
/**
* @brief 带性能监控的中断处理函数
*/
void EXTI0_IRQHandler(void)
{
IRQ_Performance_Monitor_Start(EXTI0_IRQn);
/* 中断处理代码 */
if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0)) {
__HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0);
Handle_Interrupt();
}
IRQ_Performance_Monitor_End(EXTI0_IRQn);
}
/**
* @brief 打印中断统计信息
*/
void Print_IRQ_Statistics(void)
{
printf("=== 中断统计信息 ===\n");
for (int i = 0; i < 60; i++) {
if (irq_stats[i].count > 0) {
uint32_t avg_cycles = irq_stats[i].total_duration / irq_stats[i].count;
uint32_t avg_us = avg_cycles / (SystemCoreClock / 1000000);
uint32_t max_us = irq_stats[i].max_duration / (SystemCoreClock / 1000000);
printf("IRQ %d: 次数=%d, 平均=%dus, 最大=%dus\n",
i, irq_stats[i].count, avg_us, max_us);
}
}
}
中断丢失检测¶
/**
* @brief 中断丢失检测
*/
typedef struct {
uint32_t expected_count; // 期望的中断次数
uint32_t actual_count; // 实际的中断次数
uint32_t lost_count; // 丢失的中断次数
} IRQ_Monitor_t;
IRQ_Monitor_t irq_monitor;
/**
* @brief 定时器中断(用于检测)
*/
void TIM2_IRQHandler(void)
{
if (__HAL_TIM_GET_FLAG(&htim2, TIM_FLAG_UPDATE)) {
__HAL_TIM_CLEAR_FLAG(&htim2, TIM_FLAG_UPDATE);
/* 增加期望计数 */
irq_monitor.expected_count++;
/* 增加实际计数 */
irq_monitor.actual_count++;
}
}
/**
* @brief 检查中断丢失
*/
void Check_IRQ_Lost(void)
{
if (irq_monitor.actual_count < irq_monitor.expected_count) {
irq_monitor.lost_count = irq_monitor.expected_count - irq_monitor.actual_count;
printf("警告:检测到%d个中断丢失\n", irq_monitor.lost_count);
}
}
总结¶
本文详细介绍了ARM Cortex-M处理器的中断向量表和异常处理机制,主要内容包括:
核心要点¶
- 中断向量表:
- 包含所有异常处理函数地址的数组
- 位于内存固定位置,可通过VTOR重定位
-
第一个条目是栈指针,后续是异常处理函数地址
-
异常类型:
- 系统异常:Reset, NMI, HardFault, MemManage, BusFault, UsageFault, SVC, PendSV, SysTick
-
外部中断:由外设产生,数量因芯片而异
-
NVIC控制器:
- 管理所有外部中断和部分系统异常
- 支持中断使能/禁用、优先级配置、挂起管理
-
提供灵活的优先级分组机制
-
异常处理流程:
- 自动保存上下文到栈
- 切换到处理器模式和特权级别
- 执行异常处理函数
-
自动恢复上下文并返回
-
中断嵌套:
- 支持高优先级中断打断低优先级中断
- 抢占优先级决定嵌套能力
- 硬件自动管理上下文切换
最佳实践¶
- 中断处理函数:
- 保持简短快速
- 避免延时和阻塞操作
-
使用标志位通知主循环
-
优先级配置:
- 合理分配优先级
- 关键中断使用高优先级
-
统一管理优先级分组
-
共享资源保护:
- 使用临界区保护
- 避免竞态条件
-
使用原子操作
-
调试和监控:
- 添加性能监控
- 检测中断丢失
- 记录统计信息
延伸阅读¶
推荐进一步学习的内容:
- 相关主题:
- 处理器工作模式与特权级别
- STM32启动过程深度分析
-
高级主题:
- RTOS中的中断管理
- 中断延迟优化
-
实时性能分析
-
实践项目:
- 多中断协同处理
- 中断驱动的通信系统
- 实时数据采集系统
参考资料¶
- ARM Cortex-M3权威指南 - Joseph Yiu
- ARM Cortex-M Programming Guide to Memory Barrier Instructions
- STM32 Cortex-M3编程手册 - ST Microelectronics
- The Definitive Guide to ARM Cortex-M3 and Cortex-M4 Processors
练习题:
-
解释中断向量表的结构,说明为什么第一个条目是栈指针而不是处理函数地址。
-
编写代码实现以下功能:
- 配置EXTI0外部中断(下降沿触发)
- 配置TIM2定时器中断(1ms周期)
-
设置合理的优先级,使EXTI0可以打断TIM2
-
分析以下代码的问题并改正:
-
设计一个中断优先级方案,要求:
- 系统有UART、定时器、ADC、按键四种中断源
- UART数据不能丢失
- 定时器需要精确计时
- ADC采样可以延迟
-
按键响应要求不高
-
解释什么是中断嵌套,并说明在什么情况下会发生中断嵌套。
下一步:建议学习 芯片数据手册阅读指南,掌握如何从数据手册中获取中断相关信息。