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
相关产品推荐
相关产品推荐

