4. 优化中断服务程序(ISR)

任务背景与优化目标

中断服务程序(ISR)的核心要求是“短而快”。若在ISR中执行耗时操作(如浮点运算、延时等待、复杂协议解析),会阻塞其他中断或导致主循环任务饥饿,破坏系统实时性。本案例以一个典型的串口接收中断为例,演示如何将ISR中的耗时解析逻辑移至主循环,使ISR仅完成数据搬运与标志置位。

优化前:ISR中直接处理数据

// 优化前:ISR内完成协议解析(耗时操作)
volatile uint8_t rx_buffer[64];
volatile uint8_t rx_index = 0;
volatile uint8_t frame_ready = 0;

void USART_IRQHandler(void) {
    uint8_t byte = USART->DR;  // 读取硬件数据寄存器
    
    // 以下解析逻辑耗时约50~100us(含浮点运算与循环)
    if (byte == 0xAA) {  // 帧头
        rx_index = 0;
        rx_buffer[rx_index++] = byte;
    } else if (rx_index > 0) {
        rx_buffer[rx_index++] = byte;
        if (rx_index >= 8) {  // 假设帧长8字节
            // 浮点运算:将4字节负载转换为float
            float value = *(float*)&rx_buffer[3];
            if (value > 100.0f) {  // 阈值判断
                // 执行耗时的滤波算法(约200us)
                value = value * 0.8f + 20.0f;
            }
            frame_ready = 1;
            rx_index = 0;
        }
    }
}

优化后:ISR仅做数据缓存

// 优化后:ISR只负责接收字节与置位标志
#define RX_FIFO_SIZE 128

volatile uint8_t rx_fifo[RX_FIFO_SIZE];
volatile uint8_t fifo_head = 0;
volatile uint8_t fifo_tail = 0;
volatile uint8_t data_available = 0;

void USART_IRQHandler(void) {
    uint8_t byte = USART->DR;
    uint8_t next = (fifo_head + 1) % RX_FIFO_SIZE;
    
    if (next != fifo_tail) {  // FIFO未满
        rx_fifo[fifo_head] = byte;
        fifo_head = next;
        data_available = 1;   // 通知主循环
    }
    // FIFO满时丢弃新字节(可在此置溢出标志)
}

// 主循环中处理协议解析(非中断上下文)
void main_loop_process(void) {
    static uint8_t parse_buffer[8];
    static uint8_t parse_index = 0;
    
    while (fifo_tail != fifo_head) {
        uint8_t byte = rx_fifo[fifo_tail];
        fifo_tail = (fifo_tail + 1) % RX_FIFO_SIZE;
        
        // 以下为原ISR中的耗时逻辑,现移至主循环
        if (byte == 0xAA) {
            parse_index = 0;
            parse_buffer[parse_index++] = byte;
        } else if (parse_index > 0) {
            parse_buffer[parse_index++] = byte;
            if (parse_index >= 8) {
                float value = *(float*)&parse_buffer[3];
                if (value > 100.0f) {
                    value = value * 0.8f + 20.0f;
                }
                // 此处可安全调用耗时算法或外设操作
                parse_index = 0;
            }
        }
    }
    
    if (fifo_head == fifo_tail) {
        data_available = 0;  // 所有数据已处理完
    }
}

关键优化点

  • ISR职责最小化:仅执行寄存器读取、FIFO写入和指针更新,耗时从数百微秒降至约1~2微秒(取决于总线时钟)。
  • 数据缓冲机制:使用环形FIFO(容量128字节)暂存原始数据,避免覆盖。主循环通过data_available标志或直接比较fifo_head与fifo_tail判断是否有待处理数据。
  • 上下文切换:耗时解析、浮点运算、算法调用全部移至主循环的main_loop_process()中执行,不阻塞中断响应。

验证方法

  1. 功能验证:向串口发送一帧数据 AA 01 02 00 00 C8 42 00(其中字节3~6为float小端序,值100.5),观察主循环中解析出的value是否等于100.5(经滤波后为100.4)。
  2. 实时性验证:在ISR入口和出口各置一个GPIO翻转,用示波器测量高电平持续时间。优化前应观察到明显脉冲(>50us),优化后脉冲宽度应小于3us(具体取决于MCU主频与指令周期)。
  3. 压力测试:以1ms间隔连续发送1000帧数据,确认frame_ready(或data_available)标志无丢失,且主循环能完整处理所有帧。

注意事项

  • 若主循环处理速度跟不上中断接收速率,FIFO会溢出。需根据最坏情况计算:FIFO大小 > 最大中断间隔内到达的字节数。
  • 当系统存在多个优先级中断时,应确保高优先级ISR不被本ISR阻塞——优化后本ISR已满足此要求。
  • 若使用RTOS,可将main_loop_process()放入独立任务,并通过信号量或消息队列与ISR通信,效果相同。