You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

COM1端口中断驱动I/O开发:键盘输入无法触发中断排查

串口接收中断无法触发问题排查

我正在学习简单操作系统概念,开发基于C语言的中断驱动串口驱动。目前串口写入功能正常,能按预期触发中断并打印完整消息,但串口接收(终端键盘输入)始终无法触发中断,导致无法进入ISR调试后续中断处理逻辑。

端口配置代码

int serial_open(device dev, int speed) {
    if (dev_pointers[serial_devno(dev)] != NULL) {
        return -103;
    }

    int valid = 0;
    for (int i = 0; i < 9; i++) {
        if (speed == valid_speeds[i]) {
            valid = 1;
            break;
        }
    }

    if (!valid) {
        return -102;
    }
    
    dcb* temp = sys_alloc_mem(sizeof(dcb));
    if (temp == NULL) {
        return -101;
    }

    temp->port = serial_devno(dev);
    temp->port_stat = 1;
    temp->alloc_stat = 0;
    temp->event_flag = 0;
    temp->current_op = 0;
    temp->head = 0;
    temp->tail = 0;
    temp->counter = 0;
    temp->buffer = NULL;
    temp->io_head = NULL;
    memset(temp->ring_buf, '\0', sizeof(temp->ring_buf));
    initialized[serial_devno(dev)] = 1;
    dev_pointers[serial_devno(dev)] = temp;

    if (dev == COM1) {
        idt_install(0x24, serial_isr);
    } else if (dev == COM2) {
        idt_install(0x23, serial_isr);
    } else if (dev == COM3) {
        idt_install(0x24, serial_isr);
    } else if (dev == COM4) {
        idt_install(0x23, serial_isr);
    } else {
        return 1;
    }

    int brd = 115200 / (long) speed;
    int rem = 115200 % (long) speed;
    outb(dev + IER, 0x00);
    outb(dev + LCR, 0x80);
    outb(dev + DLM, (uint8_t) rem);
    outb(dev + DLL, (uint8_t) brd);
    outb(dev + LCR, 0x03);

    cli();
    int mask = inb(0x21);
    if (dev == COM1 || dev == COM3) {
        mask = mask & ~0x10;
    } else if (dev == COM2 || dev == COM4) {
        mask &= 0x08;
    } else {
        return -1;
    }
    outb(0x21, mask);
    sti();

    outb(dev + MCR, 0x08);
    outb(dev + IER, 0x01);

    return 0;
}

寄存器枚举定义

enum uart_registers
{
    RBR = 0, // Receive Buffer
    THR = 0, // Transmitter Holding
    DLL = 0, // Divisor Latch LSB
    IER = 1, // Interrupt Enable
    DLM = 1, // Divisor Latch MSB
    IIR = 2, // Interrupt Identification
    FCR = 2, // FIFO Control
    LCR = 3, // Line Control
    MCR = 4, // Modem Control
    LSR = 5, // Line Status
    MSR = 6, // Modem Status
    SCR = 7, // Scratch
};

x86汇编ISR实现

bits 32
global serial_isr

extern serial_interrupt
serial_isr:
call serial_interrupt
iret

一级中断处理函数

void serial_input_interrupt(dcb* dcb) {
    char c = inb(dev_id(dcb->port) + RBR);
    outb(dev_id(dcb->port), c);
    if (dcb->current_op != READ) {
        if (dcb->head != dcb->tail) {
            dcb->ring_buf[dcb->head] = c;
            if (dcb->head + 1 >= 16) {
                dcb->head = 0;
            } else {
                dcb->head++;
            }
        } else {
            (void) c;
        }
    } else {
        if (dcb->io_head->op == dcb->current_op && dcb->io_head->buf_index < dcb->io_head->buf_size) {
            dcb->io_head->buf[dcb->io_head->buf_index] = c;
            if (dcb->counter > 0 && c != 10) {
                return;
            } else {
                dcb->event_flag = 1;
                dcb->current_op = IDLE;
            }
        }
    }
}

关键问题点与修复方案

1. PIC掩码配置错误

COM2/COM4对应的IRQ3,代码中mask &= 0x08逻辑错误,正确操作是清除该位以允许中断,改为:

} else if (dev == COM2 || dev == COM4) {
    mask = mask & ~0x08;
}

原操作会保留位3并清零其他所有位,导致除IRQ3外的所有中断被屏蔽,且逻辑上未正确打开IRQ3的中断允许。

2. UART波特率除数赋值颠倒

波特率除数的高8位(DLM)和低8位(DLL)赋值搞反了,正确计算方式:

uint16_t divisor = 115200 / (long)speed;
outb(dev + LCR, 0x80);
outb(dev + DLM, (uint8_t)(divisor >> 8));  // 高8位给DLM
outb(dev + DLL, (uint8_t)(divisor & 0xFF));// 低8位给DLL
outb(dev + LCR, 0x03);

错误的波特率会导致串口无法正确解析接收数据,自然不会触发接收中断。

3. MCR配置缺失中断允许位

UART的MCR寄存器位3(OUT2)需要置1,才能让UART向PIC发送中断请求。修改MCR配置:

outb(dev + MCR, 0x0B);  // 0x0B = DTR(0x01) + RTS(0x02) + OUT2(0x08)

原配置0x08仅启用OUT2,但缺少DTR和RTS,可能导致硬件握手异常,同时OUT2是中断信号传递的必要条件。

4. 未初始化FIFO

添加FCR初始化,启用FIFO并设置触发条件:

outb(dev + FCR, 0x07);  // 启用FIFO、清空收发FIFO、1字节触发中断

未启用FIFO可能导致接收数据无法正确触发中断。

5. ISR未发送EOI且未保存寄存器

修改汇编ISR,添加寄存器保存和PIC中断结束信号:

bits 32
global serial_isr

extern serial_interrupt
serial_isr:
    pusha                  ; 保存通用寄存器
    call serial_interrupt
    mov al, 0x20           ; 发送EOI给主PIC
    out 0x20, al
    popa                   ; 恢复寄存器
    iret

缺少EOI会导致PIC持续认为中断未处理,不再响应后续中断请求。

6. 验证接收缓冲区状态

可以在主循环中读取LSR寄存器,检查是否有接收数据:

uint8_t lsr = inb(dev + LSR);
if (lsr & 0x01) {
    // 接收缓冲区有数据,说明硬件能收到但中断没触发,排查中断配置
    char c = inb(dev + RBR);
}

内容的提问来源于stack exchange,提问作者mlb0066

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 19:18:10