中断服务程序编写规范指南
·
在嵌入式系统中,中断服务程序(ISR)是响应硬件事件的核心机制,直接决定系统的实时性和可靠性。
ISR的本质
ISR与普通函数区别
普通函数
显式调用
执行可预测
无严格时间限制
使用任务栈
ISR
硬件自动触发
异步不可预测
执行时间极短
可能使用独立中断栈
中断嵌套风险特性 | 普通函数 | ISR |
| 调用方式 | 显式函数调用 | 触发调用 |
| 执行时机 | 程序流程决定 | 异步、不可预测 |
| 进入/退出 | 简单跳转/返回 | 保存/恢复完整上下文 |
| 执行时间 | 无严格限制 | 必须极短 |
| 栈使用 | 使用当前任务栈 | 可能使用独立中断栈 |
| 可重入性 | 可重入设计 | 通常不可重入,需特别处理 |
| 资源访问 | 常规方式 | 需考虑与主程序/其他ISR的竞争 |
设计原则
// ❌ 错误示例:ISR中做太多事情
voidUART_IRQHandler(void) {
char buffer[256];
int i;
// 接收数据
for (i = 0; i < 256; i++) {
while (!(USART1->SR & USART_SR_RXNE));
buffer[i] = USART1->DR;
}
// 复杂的数据处理
process_large_data(buffer, 256);
// 发送响应
send_response_over_uart(buffer);
}
// ✅ 正确示例:ISR只做最必要的工作
volatileuint8_t uart_rx_buffer[256];
volatileuint16_t uart_rx_index = 0;
volatileuint8_t uart_data_ready = 0;
voidUART_IRQHandler(void) {
if (USART1->SR & USART_SR_RXNE) {
uint8_t data = USART1->DR;
if (uart_rx_index < 255) {
uart_rx_buffer[uart_rx_index++] = data;
}
if (data == '\n') {
uart_data_ready = 1;
}
}
}
// 在主循环中处理数据
voidmain_loop(void) {
while (1) {
if (uart_data_ready) {
process_large_data(uart_rx_buffer, uart_rx_index);
send_response_over_uart(uart_rx_buffer);
uart_rx_index = 0;
uart_data_ready = 0;
}
}
}特点
1. 精简高效:ISR执行时间应尽量短(通常<10% CPU周期)
2. 避免阻塞:禁止使用delay()等阻塞式函数
3. 减少复杂度:避免在ISR中进行复杂计算、大量数据处理
4. 任务分离:ISR只做数据接收/发送,复杂处理交给主循环或任务
可重入性
什么是可重入性
可重入函数是指在被调用过程中再次被中断并重新进入,仍能正确执行的函数。
是
否
函数执行
被高优先级中断打断
重新进入同一函数
是否可重入?
正确执行
数据破坏/系统崩溃可重入函数的条件
1. 不使用全局或静态变量
2. 不修改自身代码
3. 只调用可重入函数
// ❌ 不可重入函数:使用静态变量
intbad_counter(void) {
staticint count = 0; // 静态变量,不可重入
count++;
return count;
}
// ✅ 可重入函数:使用参数传递状态
intgood_counter(int *count) {
(*count)++; // 使用外部传入的指针
return *count;
}
// ❌ 不可重入:返回指向静态数据的指针
char *bad_format_time(time_t t) {
staticchar buffer[32]; // 静态缓冲区
strftime(buffer, sizeof(buffer), "%Y-%m-%d", localtime(&t));
return buffer; // 多次调用会覆盖同一缓冲区
}
// ✅ 可重入:调用者提供缓冲区
voidgood_format_time(time_t t, char *buffer, size_t size) {
strftime(buffer, size, "%Y-%m-%d", localtime(&t));
}ISR中的可重入问题
// ❌ 危险示例:ISR和主程序共享全局变量
uint32_t system_tick = 0;
voidSysTick_Handler(void) {
system_tick++; // 读-改-写操作,可能被打断
}
uint32_tget_tick(void) {
return system_tick; // 32位读取在8位/16位MCU上不是原子操作
}
// ✅ 安全示例1:使用volatile + 临界区保护
volatileuint32_t system_tick = 0;
voidSysTick_Handler(void) {
system_tick++;
}
uint32_tget_tick(void) {
uint32_t tick;
__disable_irq();
tick = system_tick;
__enable_irq();
return tick;
}
// ✅ 安全示例2:使用原子操作(C11/C17)
#include <stdatomic.h>
atomic_uint_fast32_t system_tick = ATOMIC_VAR_INIT(0);
voidSysTick_Handler(void) {
atomic_fetch_add(&system_tick, 1);
}
uint32_tget_tick(void) {
returnatomic_load(&system_tick);
}浮点运算的特殊处理
在ISR中使用浮点数时,需要特别注意浮点寄存器的保存和恢复。
// ❌ 危险:在ISR中使用浮点运算(某些架构)
float float_value = 0.0f;
voidTIM_IRQHandler(void) {
float_value += 0.1f; // 可能破坏主程序的浮点上下文
}
// ✅ 安全:编译器特定指令保存浮点上下文
#pragma SAVE
#pragma FPU
voidTIM_IRQHandler(void) {
float_value += 0.1f;
}
#pragma RESTORE
// ✅ 更好:避免在ISR中使用浮点数
volatileuint32_t integer_counter = 0;
voidTIM_IRQHandler(void) {
integer_counter++; // 使用整数,主循环中再转换
}
voidmain_loop(void) {
while (1) {
float float_value = integer_counter * 0.1f; // 在主循环计算
}
}临界区保护
什么是临界区
临界区是指访问共享资源的代码段,在同一时间只能有一个执行流进入。
读取
修改
主程序
共享变量
ISR临界区保护的常用方法
关中断(最简单但影响大)
// 基础实现
voidcritical_section_example(void) {
__disable_irq(); // 关中断
// 临界区代码
shared_data++;
__enable_irq(); // 开中断
}
// ✅ 更安全:保存中断状态
voidsafe_critical_section(void) {
uint32_t primask = __get_PRIMASK(); // 保存当前中断状态
__disable_irq();
// 临界区代码
shared_data++;
__set_PRIMASK(primask); // 恢复之前的状态
}
// ✅ 封装成宏(推荐)
#define CRITICAL_SECTION_ENTER() \
uint32_t __primask = __get_PRIMASK(); \
__disable_irq()
#define CRITICAL_SECTION_EXIT() \
__set_PRIMASK(__primask)
voidusage_example(void) {
CRITICAL_SECTION_ENTER();
shared_data++;
CRITICAL_SECTION_EXIT();
}使用基元屏蔽(只屏蔽相关中断)
// ✅ 更好:只屏蔽相关中断,不影响其他中断
void uart_specific_protection(void) {
NVIC_DisableIRQ(UART1_IRQn); // 只屏蔽UART1中断
// 临界区:只访问UART相关的共享数据
uart_rx_buffer[uart_rx_index++] = data;
NVIC_EnableIRQ(UART1_IRQn); // 恢复UART1中断
}使用互斥锁(RTOS环境)
// ✅ 在RTOS中使用互斥锁
#include "FreeRTOS.h"
#include "semphr.h"
SemaphoreHandle_t xMutex;
void rtos_critical_section(void) {
if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
// 临界区代码
shared_data++;
xSemaphoreGive(xMutex);
}
}中断嵌套处理
什么是中断嵌套
中断嵌套是指高优先级中断打断正在执行的低优先级中断。
高优先级ISR低优先级ISR主程序高优先级ISR低优先级ISR主程序低优先级中断触发高优先级中断触发正常执行进入低优先级ISR执行中...进入高优先级ISR执行完成返回低优先级ISR继续执行返回主程序Cortex-M中的优先级配置
#include "stm32f4xx.h"
voidNVIC_Configuration(void) {
// 设置优先级分组:抢占优先级2位,子优先级2位
// 抢占优先级:决定是否可以打断其他中断
// 子优先级:决定同优先级中断的响应顺序
NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_2);
// 配置高优先级中断(抢占优先级0)
NVIC_SetPriority(EXTI0_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 0, 0));
NVIC_EnableIRQ(EXTI0_IRQn);
// 配置中优先级中断(抢占优先级1)
NVIC_SetPriority(TIM2_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 1, 0));
NVIC_EnableIRQ(TIM2_IRQn);
// 配置低优先级中断(抢占优先级2)
NVIC_SetPriority(UART1_IRQn, NVIC_EncodePriority(NVIC_GetPriorityGrouping(), 2, 0));
NVIC_EnableIRQ(UART1_IRQn);
}常见问题处理
忘清除中断标志
// ❌ 错误:忘清除中断标志,导致ISR反复触发
void Bad_TIM_IRQHandler(void) {
g_counter++;
// 忘记清除TIM中断标志!
}
// ✅ 正确:及时清除中断标志
void Good_TIM_IRQHandler(void) {
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 先清除标志
g_counter++;
}
}ISR中调用非重入函数
// ❌ 危险:在ISR中调用malloc()
voidBad_ISR(void) {
char *buffer = malloc(256); // malloc可能不可重入
// 使用buffer
free(buffer);
}
// ✅ 正确:预先分配缓冲区
staticchar isr_buffer[256]; // 静态缓冲区
voidGood_ISR(void) {
// 使用预分配的缓冲区
isr_buffer[0] = data;
}volatile使用不当
// ❌ 错误:共享变量未用volatile声明
uint8_t flag = 0; // 编译器可能优化掉读取操作
voidISR(void) {
flag = 1;
}
voidmain_loop(void) {
while (!flag); // 编译器可能优化成死循环!
process_data();
}
// ✅ 正确:使用volatile
volatileuint8_t flag = 0; // 告诉编译器变量可能被意外修改
voidISR(void) {
flag = 1;
}
voidmain_loop(void) {
while (!flag); // 正确读取
process_data();
}printf调试ISR
// ❌ 错误:在ISR中使用printf
void Bad_ISR(void) {
printf("ISR triggered\r\n"); // printf很慢,可能导致问题
}
// ✅ 正确:使用简单的标志或GPIO
volatile uint8_t isr_triggered = 0;
void Good_ISR(void) {
isr_triggered = 1;
GPIOA->BSRR = GPIO_PIN_0; // 翻转GPIO(最快)
}总结
中断服务程序是嵌入式系统中最关键也最容易出错的部分,遵循这些规范,便于写出安全、高效、可靠的中断服务程序。
‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧ END ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧
关注我的微信公众号,回复“星球”加入知识星球,有问必答。点击“阅读原文”查看知识星球详情,欢迎点分享、收藏、点赞、在看。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐


所有评论(0)