键盘驱动程序设计(上)

问题

计算机已经接入了键盘,为啥按键没反应啊?

因为键盘是外设,当按键被按下时会向处理器发送中断,而我们并没有编写相应的中断处理函数。

键盘的本质

键盘是一种计算机外部设备

键盘与计算机的通信 (数据交互) 需要借助中断完成

键盘的中断引脚接在 主8259A 的 IRQ1 引脚上。 

键盘驱动步骤

1. 使能 8259A 引脚 IRQ1 (启用键盘中断)

2. 编写中断服务程序,并注册到中断向量表 (0x21号中断)

键盘中断

interrupt.c

void IntModInit()
{
	SetIntHandler(AddrOff(gIdtInfo.entry, 0x0D), (uint)SegmentFaultHandlerEntry);
	SetIntHandler(AddrOff(gIdtInfo.entry, 0x0E), (uint)PageFaultHandlerEntry);
    SetIntHandler(AddrOff(gIdtInfo.entry, 0x20), (uint)TimerHandlerEntry);
    SetIntHandler(AddrOff(gIdtInfo.entry, 0x21), (uint)KeyboardHandlerEntry);
    SetIntHandler(AddrOff(gIdtInfo.entry, 0x80), (uint)SysCallHandlerEntry);
    
    InitInterrupt();
}

loader.asm

RunTask:

    push ebp
    mov ebp, esp
    
    mov esp, [ebp + 8]

    lldt word [esp + 96]
    ltr word [esp + 98]

    pop gs
    pop fs
    pop es
    pop ds

    popad

    add esp, 4  

    mov dx, MASTER_IMR_PORT
    in ax, dx  

    %rep 5
    nop
    %endrep 

    and ax, 0xFC    ; Enable Timer and Keyboard Interrupt
    out dx, al  

    %rep 5
    nop
    %endrep
    
    mov eax, PageDirBase    ; Open Page Mode
	mov cr3, eax
	mov eax, cr0
	or  eax, 0x80000000
	mov cr0, eax    

    iret

kentry.asm

;
;
KeyboardHandlerEntry:
BeginISR
	call KeyboardHandler
EndISR

ihandler.c

void KeyboardHandler()
{
	PrintChar('*');

	SendEOI(MASTER_EOI_PORT);
}

我们在中断向量中注册了 0x21 号中断 (键盘中断),并将中断屏蔽寄存器的值设置为 0xFD,时钟中断和键盘中断分别为 IRQ0 和 IRQ1,这两个中断现在可以被处理器接收。

在键盘中断服务程序中,我们打印 '*' 字符。

当第一次按下键盘的一个按键时,屏幕上显示了 '*' 字符,但之后在按下键盘时,屏幕上就不会再显示 '*' 字符了。

问题

为什么键盘中断只发生一次?如何获取键盘按键的值?

键盘工作原理简介

当我们按下字符 ‘a’ 时,8048把按键信息发送给8042,8042把键位信息存储在缓冲区中,发送中断请求;然后 8259A 通知处理器来处理8042的中断。当处理器读取8042的缓冲区时,8042的缓冲区会被清空;而我们的中断处理函数并没有读取8042缓冲区的信息,所以第二次按下按键的时候,8042就接收不了8048的键位信息了。

键盘扫描码 (键位信息)

处理器接收到按键中断后,从 0x60 端口读取键盘扫描码

扫描码指的是硬件电路对键位的编码

  • Make Code - 按下键时产生的扫描码
  • Break Code - 释放键时产生的扫描码
  • Break Code = Make Code + 0x80

小贴士

C语言中的函数是怎么返回值的?

汇编程序可以通过将该函数的返回值写入 AX  寄存器,AX  寄存器会作为这个函数的返回值。

端口数据读取

读取键盘扫描码

kentry.asm

;
; byte ReadPort(ushort port)
;
ReadPort:
	push ebp
	mov ebp, esp
	
	xor eax, eax
	
	mov dx, [esp + 8]
	in al, dx
	
	nop
	nop
	nop
	
	leave
	ret

;
; void WritePort(ushort port, byte value)
;
WritePort:
	push ebp
	mov ebp, esp
	
	xor eax, eax
	
	mov al, [esp + 8]
	mov dx, [esp + 12]
	out dx, al
	
	nop
	nop
	nop
	
	leave
	ret
void KeyboardHandler()
{
	byte kc = ReadPort(0x60);
	
	PrintIntHex(kc);
	PrintString("  ");

	SendEOI(MASTER_EOI_PORT);
}

在键盘中断服务程序中,我们读取 0x60 端口上的键盘扫描码,以便可以接收到下一次按键中断。

当按下一个按键时,屏幕上会显示两个数字,一个是按下键时产生的扫描码,另一个是松开键时产生的扫描码,二者相差 0x80。并且我们每按下一次按键,都会读到两个按键信息。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值