键盘中断也是一个非常重要的硬中断。当键盘中的一个按钮被按下或抬起时,将通过8359A芯片向CPU发送一个键盘中断的消息,这时CPU将转入键盘中断处理程序。键盘上的每个按键都对应一个扫描码,当有键盘中断时,这个扫描码被送入0x60端口。CPU通过读取0x60端口中的扫描码就可以得知是键盘中的哪个键盘被按下或抬起了。按键盘的扫描码的大小顺序定义的按键如下:
#define KEY_ESC 0X01 // ESC #define KEY_1 0X02 // 1 #define KEY_2 0X03 // 2 #define KEY_3 0X04 // 3 #define KEY_4 0X05 // 4 #define KEY_5 0X06 // 5 #define KEY_6 0X07 // 6 #define KEY_7 0X08 // 7 #define KEY_8 0X09 // 8 #define KEY_9 0X0A // 9 #define KEY_0 0X0B // 0 #define KEY_DASH 0X0C // - #define KEY_EQUAL 0X0D // = #define KEY_BACKSPACE 0X0E // BACKSPACE #define KEY_TAB 0X0F // TAB #define KEY_Q 0X10 // Q #define KEY_W 0X11 // W #define KEY_E 0X12 // E #define KEY_R 0X13 // R #define KEY_T 0X14 // T #define KEY_Y 0X15 // Y #define KEY_U 0X16 // U #define KEY_I 0X17 // I #define KEY_O 0X18 // O #define KEY_P 0X19 // P #define KEY_LBRACKET 0X1A // [ #define KEY_RBRACKET 0X1B // ] #define KEY_ENTER 0X1C // ENTER #define KEY_CTRL 0X1D // CTRL #define KEY_A 0X1E // A #define KEY_S 0X1F // S #define KEY_D 0X20 // D #define KEY_F 0X21 // F #define KEY_G 0X22 // G #define KEY_H 0X23 // H #define KEY_J 0X24 // J #define KEY_K 0X25 // K #define KEY_L 0X26 // L #define KEY_SEMICOLON 0X27 // ; #define KEY_RQUOTE 0X28 // ' #define KEY_LQUOTE 0X29 // ` #define KEY_LEFT_SHIFT 0X2A // LEFT SHIFT #define KEY_BACKSLASH 0X2B // '\' #define KEY_Z 0X2C // Z #define KEY_X 0X2D // X #define KEY_C 0X2E // C #define KEY_V 0X2F // V #define KEY_B 0X30 // B #define KEY_N 0X31 // N #define KEY_M 0X32 // M #define KEY_COMMA 0X33 // , #define KEY_PERIOD 0X34 // . #define KEY_SLASH 0X35 // / #define KEY_RIGHT_SHIFT 0X36 // RIGHT SHIFT #define KEY_PRTSC 0X37 // PRINT SCREEN #define KEY_ALT 0X38 // ALT #define KEY_SPACE 0X39 // SPACE #define KEY_CAPS_LOCK 0X3A // CAPS LOCK #define KEY_F1 0X3B // F1 #define KEY_F2 0X3C // F2 #define KEY_F3 0X3D // F3 #define KEY_F4 0X3E // F4 #define KEY_F5 0X3F // F5 #define KEY_F6 0X40 // F6 #define KEY_F7 0X41 // F7 #define KEY_F8 0X42 // F8 #define KEY_F9 0X43 // F9 #define KEY_F10 0X44 // F10 #define KEY_NUM_LOCK 0X45 // NUM LOCK #define KEY_SCROLL_LOCK 0X46 // SCROLL LOCK #define KEY_HOME 0X47 // HOME #define KEY_UP 0X48 // UP #define KEY_PAGE_UP 0X49 // PAGE UP #define KEY_SUB 0X4A // SUB #define KEY_LEFT 0X4B // LEFT #define KEY_CENTER 0X4C // CENTER #define KEY_RIGHT 0X4D // RIGHT #define KEY_ADD 0X4E // ADD #define KEY_END 0X4F // END #define KEY_DOWN 0X50 // DOWN #define KEY_PAGE_DOWN 0X51 // PAGE DOWN #define KEY_INSERT 0X52 // INSERT #define KEY_DEL 0X53 // DEL
另外,当一个键被按下时,我们希望在显示器上显示出这个字符,所以还要定义可显字符与扫描码的对应关系。比如当CPU得到一个扫描码为0x1e的按键,可以通过这个对应关系找到这个按键为字符'a',于是调用putchar函数在显示器上显示'a'。这个对应关系被定义为一个二维的字符数组,这个字符数组的行下标为扫描码,列下标为当shift按键按下时的下标。如下:
u8 keys[0x53][2] = { { 0x0, 0x0 }, // ESC { '1', '!' }, // 1 { '2', '@' }, // 2 { '3', '#' }, // 3 { '4', '$' }, // 4 { '5', '%' }, // 5 { '6', '^' }, // 6 { '7', '&' }, // 7 { '8', '*' }, // 8 { '9', '(' }, // 9 { '0', ')' }, // 0 { '-', '_' }, // - { '=', '+' }, // = { 0x0, 0x0 }, // BACKSPACE { 0x0, 0x0 }, // TAB { 'q', 'Q' }, // Q { 'w', 'W' }, // W { 'e', 'E' }, // E { 'r', 'R' }, // R { 't', 'T' }, // T { 'y', 'Y' }, // Y { 'u', 'U' }, // U { 'i', 'I' }, // I { 'o', 'O' }, // O { 'p', 'P' }, // P { '[', '{' }, // [ { ']', '}' }, // ] { '\n', 0x0 }, // ENTER { 0x0, 0x0 }, // CTRL { 'a', 'A' }, // A { 's', 'S' }, // S { 'd', 'D' }, // D { 'f', 'F' }, // F { 'g', 'G' }, // G { 'h', 'H' }, // H { 'j', 'J' }, // J { 'k', 'K' }, // K { 'l', 'L' }, // L { ';', ':' }, // ; { '\'', '"' }, // ' { '`', '~' }, // ` { 0x0, 0x0 }, // LEFTSHIFT { '\\', '|' }, // '\' { 'a', 'Z' }, // Z { 'x', 'X' }, // X { 'c', 'C' }, // C { 'v', 'V' }, // V { 'b', 'B' }, // B { 'n', 'N' }, // N { 'm', 'M' }, // M { ',', '<' }, // , { '.', '>' }, // . { '/', '?' }, // / { 0x0, 0x0 }, // RIGHTSHIFT { 0x0, 0x0 }, // PRINTSCREEN { 0x0, 0x0 }, // ALT { 0x0, 0x0 }, // SPACE { 0x0, 0x0 }, // CAPSLOCK { 0x0, 0x0 }, // F1 { 0x0, 0x0 }, // F2 { 0x0, 0x0 }, // F3 { 0x0, 0x0 }, // F4 { 0x0, 0x0 }, // F5 { 0x0, 0x0 }, // F6 { 0x0, 0x0 }, // F7 { 0x0, 0x0 }, // F8 { 0x0, 0x0 }, // F9 { 0x0, 0x0 }, // F10 { 0x0, 0x0 }, // NUMLOCK { 0x0, 0x0 }, // SCROLLLOCK { 0x0, 0x0 }, // HOME { 0x0, 0x0 }, // UP { 0x0, 0x0 }, // PAGEUP { 0x0, 0x0 }, // SUB { 0x0, 0x0 }, // LEFT { 0x0, 0x0 }, // CENTER { 0x0, 0x0 }, // RIGHT { 0x0, 0x0 }, // ADD { 0x0, 0x0 }, // END { 0x0, 0x0 }, // DOWN { 0x0, 0x0 }, // PAGEDOWN { 0x0, 0x0 }, // INSERT { 0x0, 0x0 } // DEL };
可以看到可显字符只有0-9、a-z、A-Z以及一些符号,其中很多功能按键并不是可显字符,也就是说这些如Esc、Home、Left、ScrollLock、PrintScreen等这些按键并不是ascii中可显字符中的有效值。但是为了能够让可显字符的扫描码方便的作为keys行下标而使用,这里也保留了这些非可显字符的按键。接下来就可以使用这个扫描码了,通过打开8259A中断控制器的IRQ1脚来打开键盘中断:
//打开IRQ1的键盘中断 outb_p(inb_p(0x21) & 0xfd, 0x21);
另外,键盘存放按键扫描码的端口为0x60,还有一个键盘按键控制端口0x61,向它写入相应的控制字节,此字节每一位格式如下:
对于0-6暂时不去管它们,只需要将IRQ复位即可。如果IRQ不复位,键盘中断只会被8259A响应一次,之后就不再触发键盘中断了。
//清除键盘状态可以接受新按键 outb_p(0x7f, 0x61);
在int.S中处理键盘服务程序,调用int_keyborad函数来处理键盘按键:
//键盘中断 _int_0x21: cli pushal pushfl //调用键盘中断处理函数 call int_keyboard popfl popal sti iret
在int_keyborad函数中处理按下键盘的一个键时显示这个键所对应的字符:
/* * int_keyboard : 键盘中断 * return : void */ void int_keyboard() { //取得扫描码 u8 scan_code = inb_p(0x60); //取得按下、抬起状态 u8 status = scan_code >> 7; //扫描码的索引 u8 key_ind = scan_code & 0x7f; //shift按下 if ((key_ind == KEY_LEFT_SHIFT || key_ind == KEY_RIGHT_SHIFT) && status == 0) { kb_key_shift = 0x1; } //shift抬起 else if ((key_ind == KEY_LEFT_SHIFT || key_ind == KEY_RIGHT_SHIFT) && status == 1) { kb_key_shift = 0x0; } else if (status == 0) { //显示字符 putchar(keys[key_ind - 1][kb_key_shift]); } //清除键盘状态可以接受新按键 outb_p(scan_code & 0x7f, 0x61); //通知PIC1可以接受新中断 outb_p(0x20, 0x20); }
最后在start_kernel中加入安装键盘中断:
//安装键盘中断 install_kb();
在Makefile中加入key.c的编译代码,编译并运行结果:
运行结果中显示了一个叫作main的C语言主函数,千万不要被它吓到。这只是通过键盘输入在显示器上显示的一些字符罢了,并不是什么C的源代码和编译器。不过这已经说明我们的键盘中断已经可以正常工作了。
源代码的下载地址为:
https https://github.com/magicworldos/lidqos.git git git@github.com:magicworldos/lidqos.git subverion https://github.com/magicworldos/lidqos branch v0.13
Copyright © 2015-2023 问渠网 辽ICP备15013245号