《ORANGE-s-一个操作系统实现》输入输出系统(5-1)

键盘

操作系统需要交互,首先就是键盘

从中断开始:键盘的初体验

因为8259A的IRQ1就是键盘,现在我们写一个键盘的处理程序:

1
2
3
4
PUBLIC void keyboard_handler(int irq)
{
disp_str("*");
}

这个结果就是每按一次键盘,就会打印一个星号。
然后添加中断处理程序,并打开键盘中断:

1
2
3
4
5
PUBLIC void init_keyboard()
{
put_irq_handler(KEYBOARD_IRD,keyboard_handler);/*设定键盘中断程序*/
enable_irq(KEYBOARD_IRQ);/*开键盘中断*/
}

然后在proto.h中声明init_keyboard()并调用它。
make之后,运行结果如下。
mark
然后问题出现了,按了一次以后就没法再按出星号了。

AT、PS/2键盘

下图左边是PS/2键盘的接口,右边是AT键盘的接口,现在主流的都是USB的了。
mark

键盘敲击的过程

在键盘中存在一个叫做键盘编码器的芯片,它通常是Intel 8048以及兼容芯片,作用是监视键盘的输入,并把适当的数据传送给计算机。另外在计算机主板上还有一个键盘控制器,用来接受和解码来自键盘的数据,并与8259A以及软件等进行通信。如下图:
mark
敲击键盘有两个方面的含义:动作和内容。动作有三类:按下、保持按下以及放开;内容则是键盘上的不同的键.8048既要反应“哪个”安检产生动作,还要反映产生了什么内容。
敲击键盘产生的编码被称为扫描码,当一个键被按下或者保持按下时,将会产生Make Code,当弹起时,产生Break Code。当8048检测到一个键的动作,会把相应的扫描码发送给8042,8042会把它转换成相应的扫描码,并将其放置在缓冲区,然后8042告诉8259A产生中断(IRQ1)。如果此时键盘又有新的键被按下,8042将不再接受,一直到缓冲区被清空。
所以我们只能按下一次就没有反应了,因为缓冲区的内容没有被取走。
为了了解如何从缓冲区中读取扫描码,我们要学习8042:
mark
对于输入和输出缓冲区,可以用in和out指令来进行相应的读取操作。如下,在keyboard_handler中添加:

1
in_byte(0x60);

运行结果如下:
mark
我们发现结果是16进制数,所以我们用如下表格建立对应关系,即可看到想要的结果了:
mark
mark

用数组表示扫描码

扫描码是一些数字,我们建立一个数组,以扫描码为下标,对应的元素就是相应的字符。数组是3个值一组,三个值是单独按某键、Shift+某键和有0xE0前缀扫描码对应的字符。Esc、Enter被定义成不冲突的宏即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
u32 keymap[NR_SCAN_CODES * MAP_COLS] = {

/* scan-code !Shift Shift E0 XX */
/* ==================================================================== */
/* 0x00 - none */ 0, 0, 0,
/* 0x01 - ESC */ ESC, ESC, 0,
/* 0x02 - '1' */ '1', '!', 0,
/* 0x03 - '2' */ '2', '@', 0,
/* 0x04 - '3' */ '3', '#', 0,
/* 0x05 - '4' */ '4', '$', 0,
/* 0x06 - '5' */ '5', '%', 0,
/* 0x07 - '6' */ '6', '^', 0,
/* 0x08 - '7' */ '7', '&', 0,
/* 0x09 - '8' */ '8', '*', 0,
/* 0x0A - '9' */ '9', '(', 0,
/* 0x0B - '0' */ '0', ')', 0,
/* 0x0C - '-' */ '-', '_', 0,
/* 0x0D - '=' */ '=', '+', 0,
/* 0x0E - BS */ BACKSPACE, BACKSPACE, 0,
/* 0x0F - TAB */ TAB, TAB, 0,
/* 0x10 - 'q' */ 'q', 'Q', 0,
/* 0x11 - 'w' */ 'w', 'W', 0,
/* 0x12 - 'e' */ 'e', 'E', 0,
/* 0x13 - 'r' */ 'r', 'R', 0,
/* 0x14 - 't' */ 't', 'T', 0,
/* 0x15 - 'y' */ 'y', 'Y', 0,
/* 0x16 - 'u' */ 'u', 'U', 0,
/* 0x17 - 'i' */ 'i', 'I', 0,
/* 0x18 - 'o' */ 'o', 'O', 0,
/* 0x19 - 'p' */ 'p', 'P', 0,
/* 0x1A - '[' */ '[', '{', 0,
/* 0x1B - ']' */ ']', '}', 0,
/* 0x1C - CR/LF */ ENTER, ENTER, PAD_ENTER,
/* 0x1D - l. Ctrl */ CTRL_L, CTRL_L, CTRL_R,
/* 0x1E - 'a' */ 'a', 'A', 0,
/* 0x1F - 's' */ 's', 'S', 0,
/* 0x20 - 'd' */ 'd', 'D', 0,
/* 0x21 - 'f' */ 'f', 'F', 0,
/* 0x22 - 'g' */ 'g', 'G', 0,
/* 0x23 - 'h' */ 'h', 'H', 0,
/* 0x24 - 'j' */ 'j', 'J', 0,
/* 0x25 - 'k' */ 'k', 'K', 0,
/* 0x26 - 'l' */ 'l', 'L', 0,
/* 0x27 - ';' */ ';', ':', 0,
/* 0x28 - '\'' */ '\'', '"', 0,
/* 0x29 - '`' */ '`', '~', 0,
/* 0x2A - l. SHIFT */ SHIFT_L, SHIFT_L, 0,
/* 0x2B - '\' */ '\\', '|', 0,
/* 0x2C - 'z' */ 'z', 'Z', 0,
/* 0x2D - 'x' */ 'x', 'X', 0,
/* 0x2E - 'c' */ 'c', 'C', 0,
/* 0x2F - 'v' */ 'v', 'V', 0,
/* 0x30 - 'b' */ 'b', 'B', 0,
/* 0x31 - 'n' */ 'n', 'N', 0,
/* 0x32 - 'm' */ 'm', 'M', 0,
/* 0x33 - ',' */ ',', '<', 0,
/* 0x34 - '.' */ '.', '>', 0,
/* 0x35 - '/' */ '/', '?', PAD_SLASH,
/* 0x36 - r. SHIFT */ SHIFT_R, SHIFT_R, 0,
/* 0x37 - '*' */ '*', '*', 0,
/* 0x38 - ALT */ ALT_L, ALT_L, ALT_R,
/* 0x39 - ' ' */ ' ', ' ', 0,
/* 0x3A - CapsLock */ CAPS_LOCK, CAPS_LOCK, 0,
/* 0x3B - F1 */ F1, F1, 0,
/* 0x3C - F2 */ F2, F2, 0,
/* 0x3D - F3 */ F3, F3, 0,
/* 0x3E - F4 */ F4, F4, 0,
/* 0x3F - F5 */ F5, F5, 0,
/* 0x40 - F6 */ F6, F6, 0,
/* 0x41 - F7 */ F7, F7, 0,
/* 0x42 - F8 */ F8, F8, 0,
/* 0x43 - F9 */ F9, F9, 0,
/* 0x44 - F10 */ F10, F10, 0,
/* 0x45 - NumLock */ NUM_LOCK, NUM_LOCK, 0,
/* 0x46 - ScrLock */ SCROLL_LOCK, SCROLL_LOCK, 0,
/* 0x47 - Home */ PAD_HOME, '7', HOME,
/* 0x48 - CurUp */ PAD_UP, '8', UP,
/* 0x49 - PgUp */ PAD_PAGEUP, '9', PAGEUP,
/* 0x4A - '-' */ PAD_MINUS, '-', 0,
/* 0x4B - Left */ PAD_LEFT, '4', LEFT,
/* 0x4C - MID */ PAD_MID, '5', 0,
/* 0x4D - Right */ PAD_RIGHT, '6', RIGHT,
/* 0x4E - '+' */ PAD_PLUS, '+', 0,
/* 0x4F - End */ PAD_END, '1', END,
/* 0x50 - Down */ PAD_DOWN, '2', DOWN,
/* 0x51 - PgDown */ PAD_PAGEDOWN, '3', PAGEDOWN,
/* 0x52 - Insert */ PAD_INS, '0', INSERT,
/* 0x53 - Delete */ PAD_DOT, '.', DELETE,
/* 0x54 - Enter */ 0, 0, 0,
/* 0x55 - ??? */ 0, 0, 0,
/* 0x56 - ??? */ 0, 0, 0,
/* 0x57 - F11 */ F11, F11, 0,
/* 0x58 - F12 */ F12, F12, 0,
/* 0x59 - ??? */ 0, 0, 0,
/* 0x5A - ??? */ 0, 0, 0,
/* 0x5B - ??? */ 0, 0, GUI_L,
/* 0x5C - ??? */ 0, 0, GUI_R,
/* 0x5D - ??? */ 0, 0, APPS,
/* 0x5E - ??? */ 0, 0, 0,
/* 0x5F - ??? */ 0, 0, 0,
/* 0x60 - ??? */ 0, 0, 0,
/* 0x61 - ??? */ 0, 0, 0,
/* 0x62 - ??? */ 0, 0, 0,
/* 0x63 - ??? */ 0, 0, 0,
/* 0x64 - ??? */ 0, 0, 0,
/* 0x65 - ??? */ 0, 0, 0,
/* 0x66 - ??? */ 0, 0, 0,
/* 0x67 - ??? */ 0, 0, 0,
/* 0x68 - ??? */ 0, 0, 0,
/* 0x69 - ??? */ 0, 0, 0,
/* 0x6A - ??? */ 0, 0, 0,
/* 0x6B - ??? */ 0, 0, 0,
/* 0x6C - ??? */ 0, 0, 0,
/* 0x6D - ??? */ 0, 0, 0,
/* 0x6E - ??? */ 0, 0, 0,
/* 0x6F - ??? */ 0, 0, 0,
/* 0x70 - ??? */ 0, 0, 0,
/* 0x71 - ??? */ 0, 0, 0,
/* 0x72 - ??? */ 0, 0, 0,
/* 0x73 - ??? */ 0, 0, 0,
/* 0x74 - ??? */ 0, 0, 0,
/* 0x75 - ??? */ 0, 0, 0,
/* 0x76 - ??? */ 0, 0, 0,
/* 0x77 - ??? */ 0, 0, 0,
/* 0x78 - ??? */ 0, 0, 0,
/* 0x78 - ??? */ 0, 0, 0,
/* 0x7A - ??? */ 0, 0, 0,
/* 0x7B - ??? */ 0, 0, 0,
/* 0x7C - ??? */ 0, 0, 0,
/* 0x7D - ??? */ 0, 0, 0,
/* 0x7E - ??? */ 0, 0, 0,
/* 0x7F - ??? */ 0, 0, 0
};

键盘输入缓冲区

这个是用来放置中断例程接受到的扫描码。为了解决当扫描码不止一个字符时的问题。
它的用法如图所示:
mark
白色框表示空闲字节,灰色框表示已用字节。

用新加的任务处理键盘操作

终端任务是要处理屏幕输出等内容的,为了简化我们现在只是不停的调用keyboard_read():

1
2
3
4
5
6
PUBLIC void task_tty()
{
while(1){
keyboard_read();
}
}

keyboard_read()首先判断kb_in.count是否为0,如果不为0表明缓冲区中有扫描码,就开始读取。

解析扫描码

显示字符

因为键盘的键的功能是不一样的,有的是一种功能不是一个ASCII。我们先处理可以打印的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
PUBLIC void keyboard_read()
{
u8 scan_code;
char output[2];
int make; /* TRUE: make; FALSE: break. */

memset(output, 0, 2);

if(kb_in.count > 0){
disable_int();
scan_code = *(kb_in.p_tail);
kb_in.p_tail++;
if (kb_in.p_tail == kb_in.buf + KB_IN_BYTES) {
kb_in.p_tail = kb_in.buf;
}
kb_in.count--;
enable_int();

/* 下面开始解析扫描码 */
if (scan_code == 0xE1) {
/* 暂时不做任何操作 */
}
else if (scan_code == 0xE0) {
/* 暂时不做任何操作 */
}
else { /* 下面处理可打印字符 */

/* 首先判断Make Code 还是 Break Code */
make = (scan_code & FLAG_BREAK ? FALSE : TRUE);

/* 如果是Make Code 就打印,是 Break Code 则不做处理 */
if(make) {
output[0] = keymap[(scan_code&0x7F)*MAP_COLS];
disp_str(output);
}
}
/* disp_int(scan_code); */
}
}

上面的总体思想是0xE0和0xE1单独处理,其余都是单字节的,运行结果如下:
mark

处理shift、alt、ctrl

先对这三个按键的状态进行判断,因为有左右之分,所以有6个键,当按下shift_l时,相应的变量就变为TRUE,如果立即释放则变为FALSE。如果if(shift_l||shift_r)成立,则表示左shift被按下且未被释放,此时colum值变为1。
运行一下:
mark
对于其他功能键,我们统一放在in_process()中,并在不可打印的字符的定义中,都加一个FLAG_EXT。运行结果如下:
mark

您的支持将鼓励我继续创作!