一、中断函数必须 "短小精悍",避免耗时操作
中断的响应和处理要越快越好,否则会阻塞其他中断(尤其是高优先级中断)或主程序。
-
严禁在中断中执行的操作
- 延时函数(如
delay_ms()):会占用大量 CPU 时间,导致系统失去实时性。 - 复杂运算(如浮点运算、循环遍历数组):中断服务函数(ISR)应只做 "必要的事"。
- 阻塞式 I/O 操作(如串口轮询收发、SPI 忙等等待):会让中断长时间占用 CPU。
- 函数嵌套调用:中断函数本身的栈空间有限,嵌套容易导致栈溢出。
- 延时函数(如
-
正确的做法 中断函数只做 "标记 + 极简数据处理",具体的业务逻辑交给主程序处理。示例(STM32 串口接收中断):
c
运行
// 全局标志位和缓冲区(需加volatile防止编译器优化) volatile uint8_t uart_rx_flag = 0; volatile uint8_t uart_rx_data = 0; // 串口1中断服务函数 void USART1_IRQHandler(void) { if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) { uart_rx_data = USART_ReceiveData(USART1); // 读取数据(1-2个时钟周期) uart_rx_flag = 1; // 置位标志位(核心操作) USART_ClearITPendingBit(USART1, USART_IT_RXNE); // 清除中断标志 } } // 主程序处理逻辑 int main(void) { while(1) { if(uart_rx_flag) { // 处理数据、打印、控制外设等耗时操作 uart_rx_flag = 0; // 清除标志位 } } }
二、正确使用 volatile 关键字
中断函数和主程序共享的变量(如标志位、数据缓冲区),必须用 volatile 修饰,防止编译器优化导致数据错乱。
- 原理:编译器会对普通变量做 "缓存优化"(将变量值暂存寄存器),主程序可能无法及时读取到中断修改后的变量值。
volatile的作用:告诉编译器 "该变量会被意外修改(如中断)",每次访问都必须从内存中读取,而非寄存器。
三、严格管理中断优先级,避免优先级反转
-
优先级分配原则
- 对实时性要求高的中断(如定时器精确定时、外部紧急信号)分配 高优先级。
- 对实时性要求低的中断(如串口打印、按键消抖)分配 低优先级。
- STM32 中通过
NVIC_PriorityGroupConfig()配置优先级分组,区分 抢占优先级 (可打断低优先级中断)和 响应优先级(同抢占优先级时的响应顺序)。
-
解决优先级反转问题当低优先级中断正在执行,高优先级中断无法抢占时,会导致优先级反转。
- 解决方案:使用 中断嵌套 (STM32 支持),高优先级中断可以打断低优先级中断;或通过 关中断保护临界区(需谨慎)。
四、谨慎使用 "关中断 / 开中断" 操作
在处理临界区代码(如共享数据的读写)时,可能需要临时关闭中断,防止中断打断导致数据不一致。
-
STM32 中关 / 开中断的常用函数
c
运行
__disable_irq(); // 关闭全局中断 // 临界区代码(如修改共享数组、全局变量) __enable_irq(); // 开启全局中断 -
注意事项
- 临界区代码必须 极短,否则会导致所有中断无法响应,降低系统实时性。
- 避免嵌套关中断:若在关中断期间再次关中断,可能导致开中断后无法恢复到正确状态。
五、清除中断标志位,防止重复进入中断
-
必须手动清除的中断标志 STM32 中部分中断标志位(如串口
RXNE、定时器UPDATE)需要 手动清除,否则中断会被反复触发。- 清除方式:通过库函数(如
USART_ClearITPendingBit())或直接操作寄存器。 - 错误示例:未清除中断标志 → 中断函数执行完毕后,立即再次进入 → 系统卡死。
- 清除方式:通过库函数(如
-
自动清除的中断标志 少数中断标志(如外部中断
EXTI的边沿触发标志)会在读取寄存器或响应中断后 自动清除,无需手动操作。
六、避免在中断中使用全局变量的副作用
-
共享数据的保护 若中断和主程序同时操作一个全局变量(如计数器、缓冲区),需加 互斥保护:
- 方法 1:关中断保护(适合简单变量)。
- 方法 2:使用环形缓冲区(适合大量数据传输,如串口批量收发)。
-
禁止在中断中修改复杂全局结构体结构体的成员可能分布在不同内存地址,中断中修改时若被主程序打断,会导致结构体数据不完整。
七、中断函数的命名和声明要符合编译器规范
不同单片机 / 编译器对中断服务函数的 命名有严格要求,必须和启动文件中的中断向量表一致,否则中断无法响应。
- 示例:STM32 的启动文件
startup_stm32f10x_md.s中,串口 1 的中断函数名固定为USART1_IRQHandler,不能自定义为uart1_isr()等。 - 中断函数无需声明 ,直接在
.c文件中定义即可(编译器会自动关联中断向量表)。
八、调试中断的注意事项
- 禁止在中断中使用调试打印函数 如
printf()本质是串口轮询发送,耗时较长,会导致中断阻塞。若需调试,可通过 置位标志位 + 主程序打印 的方式。 - 使用示波器 / 逻辑分析仪观察中断触发时机可在中断函数中翻转一个 GPIO 引脚电平,通过示波器观察引脚波形,判断中断是否被正确触发、执行时间是否过长。