处理器:Intel Celeron® Dual-Core CPU 2.10GHz
操作系统:Windows7 专业版 x86
阅读书籍:《30天自制操作系统》—川合秀实[2015.04.15-04.16]
工具:../toolset/
键盘输入处理的流程似鼠标输入处理流程,需要提前配置好GDT和IDT,并初始化好PIC。
int keydata0; struct FIFO32 *keyfifo; void wait_KBC_sendready(void) { //等待键盘控制电路准备完善(查看键盘控制电路手册) for (;;) { if ((io_in8(PORT_KEYSTA) & KEYSTA_SEND_NOTREADY) == 0) { break; } } return; } /* 初始化键盘控制电路 * fifo为管理缓冲区的结构体 * 键盘缓冲区加上data0的值后再写入缓冲区,跟其它数据得以区分 */ void init_keyboard(struct FIFO32 *fifo, int data0) { //管理键盘缓冲区结构体赋值, keyfifo = fifo; keydata0 = data0; //向键盘控制电路发送命令开启键盘 wait_KBC_sendready(); io_out8(PORT_KEYCMD, KEYCMD_WRITE_MODE); wait_KBC_sendready(); io_out8(PORT_KEYDAT, KBC_MODE); return; }
初始化键盘控制电路,让键盘控制电路接收键盘按下时向它发送的数据。
键盘中断信号为为PIC上的IRQ1,所以需要开启PIC对IRQ1中断的监听。
io_out8(PIC0_IMR, 0xf8);// 主PIC端口0x0021,11111000:IRQ0-2中断允许
允许监听某IRQ0-2后,如果PICIRQ0-2有中断来临,则PIC向CPU汇报这些中断。
在初始化PIC时,设定INT0x20 ~ 0x2f接收中断信号IRQ0~ 15。形象的将键盘中断函数名称为inthander21。
void inthandler21(int *esp) { int data; io_out8(PIC0_OCW2, 0x61); //收到键盘信息后通知PIC继续监听IRQ1 data = io_in8(PORT_KEYDAT); fifo32_put(keyfifo, data + keydata0);//键盘数据加上keydata0后写入缓冲区 return; }
键盘数据保存端口60h中,需要程序自动读取。
1. GLOBAL _asm_inthandler21
2. EXTERN _inthandler21
3. _asm_inthandler21:
4. PUSH ES
5. PUSH DS
6. PUSHAD
7. MOV EAX,ESP
8. PUSH EAX
9. MOV AX,SS
10. MOV DS,AX
11. MOV ES,AX
12. CALL _inthandler21
13. POP EAX
14. POPAD
15. POP DS
16. POP ES
17. IRETD//设定GDT和IDT void init_gdtidt(void) { //IDT的设定 set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);//定时器 set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);//键盘 set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);//鼠标 return; }
当键盘被按下或者松开时,键盘向键盘控制电路的60h端口发送键盘数据,此时PIC向CPU汇报IRQ1的中断信息。CPU接收到PIC的这个汇报后,(执行完当前指令后)根据中断信息的中断号(IRQ1的中断号为21h)转去执行IDT+ 21h(地址)中asm_inthandler21地址处的代码。(经IRETD指令后,CPU又返回到原程序中执行后续指令)。
在键盘上按下一个键时,键盘向端口60h发送这个按键的扫描码(通码);当松开这个按键时,键盘向端口60h发送这个按键的断码(通码+ 80h)。“书”14.4章节有键盘各个按键的通码表,其中’A’按键的通码值为1EH。
在主函数HariMain()中包含处理键盘输入的各步骤。
…… void HariMain(void) { char str[40]; struct FIFO32 fifo; int fifobuf[128]; …… init_gdtidt(); init_pic(); io_sti();//PIC初始化后立即恢复CPU中断机制 …… fifo32_init(&fifo, 128, fifobuf); init_keyboard(&fifo, 256);//初始化键盘 io_out8(PIC0_IMR, 0xf8);//PIC检测键盘中断IRQ1 …… for (;;) { …… if (fifo32_status(&fifo) == 0) { io_sti(); } else { i = fifo32_get(&fifo); io_sti(); if (256 <= i && i <= 511) { //键盘数据 sprintf(str, "%02X", i - 256); if (i == 0x1e + 256) { //按下的键为’A’ putfonts8_asc_sht(sht_win, 40, 28, COL8_000000, COL8_C6C6C6, "A", 1); } }…… …… } }
打开“书”中的“!cons_nt.bat”,整个程序通过编译后,运行“makerun”命令在QEMU中运行程序。
键盘上有很多个需要显示的字符,将它们组织在一个数据结构里面供键盘显示。
按照键盘上每个字符编码的规律,用一个数组来存储键盘的各按键字符。
static char keytable[0x54] = { 0, 0, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '^', 0, 0, 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '@', '[', 0, 0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ';', ':', 0, 0, ']', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', ',', '.', '/', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '7', '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '0', '.' };
keytable[0x1e]对应字符’A’,’A’的通码刚好为1eh。
最好是能根据键盘输入就能够对应键盘输入按键在数据结构的对应的字符。
i = fifo32_get(&fifo); io_sti(); if (256 <= i && i <= 511) { //键盘数据 sprintf(str, "%02X", i - 256); putfonts8_asc_sht(sht_back, 0, 16, COL8_FFFFFF, COL8_008484, str, 2); if (i < 256 + 0x54) { //在keytable编码范围内 if (keytable[i - 256] != 0) { str[0] = keytable[i - 256]; str [1] = 0; putfonts8_asc_sht(sht_win, 40, 28, COL8_000000, COL8_C6C6C6, str, 1);//显示按键上的字符 } } }
按照keytable[]数组中编排按键字符以及往缓冲区写入键盘数据的方式,键盘通码减去256刚好是键盘字符在keytable[]数组中的下标。
文字的输入背景就是用绘制矩形的函数绘制一些矩形。
/* 制作文字输入的背景 * sht为文字输入所在的图层, * x0,y0为文字输入背景的起始位置, * sx,sy为在文字输入背景在x,y方向上的长度 * c为文字输入背景边框的颜色 */ void make_textbox8(struct SHEET *sht, int x0, int y0, int sx, int sy, int c) { int x1 = x0 + sx, y1 = y0 + sy; boxfill8(sht->buf, sht->bxsize, COL8_848484, x0 - 2, y0 - 3, x1 + 1, y0 - 3); boxfill8(sht->buf, sht->bxsize, COL8_848484, x0 - 3, y0 - 3, x0 - 3, y1 + 1); boxfill8(sht->buf, sht->bxsize, COL8_FFFFFF, x0 - 3, y1 + 2, x1 + 1, y1 + 2); boxfill8(sht->buf, sht->bxsize, COL8_FFFFFF, x1 + 2, y0 - 3, x1 + 2, y1 + 2); boxfill8(sht->buf, sht->bxsize, COL8_000000, x0 - 1, y0 - 2, x1 + 0, y0 - 2); boxfill8(sht->buf, sht->bxsize, COL8_000000, x0 - 2, y0 - 2, x0 - 2, y1 + 0); boxfill8(sht->buf, sht->bxsize, COL8_C6C6C6, x0 - 2, y1 + 1, x1 + 0, y1 + 1); boxfill8(sht->buf, sht->bxsize, COL8_C6C6C6, x1 + 1, y0 - 2, x1 + 1, y1 + 1); boxfill8(sht->buf, sht->bxsize, c, x0 - 1, y0 - 1, x1 + 0, y1 + 0); return; }
字符输入图标根据键盘输入的变化而变化,将光标的坐标变换写到HariMain()主函数中。
int cursor_x, cursor_c; …… make_textbox8(sht_win, 8, 28, 144, 16, COL8_FFFFFF); cursor_x = 8; cursor_c = COL8_FFFFFF; …… for (;;) { io_cli(); if (fifo32_status(&fifo) == 0) { io_stihlt(); } else { i = fifo32_get(&fifo); io_sti(); if (256 <= i && i <= 511) { //键盘数据 sprintf(s, "%02X", i - 256); putfonts8_asc_sht(sht_back, 0, 16, COL8_FFFFFF, COL8_008484, s, 2); if (i < 0x54 + 256) { if (keytable[i - 256] != 0 && cursor_x < 144) { //一般字符 //显示一个字符就前移一个光标 s[0] = keytable[i - 256]; s[1] = 0; putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, s, 1); cursor_x += 8; } } if (i == 256 + 0x0e && cursor_x > 8) { //退格键 //用空格把光标消去后,后移一次光标 putfonts8_asc_sht(sht_win, cursor_x, 28, COL8_000000, COL8_FFFFFF, " ", 1); cursor_x -= 8; } //显示光标 boxfill8(sht_win->buf, sht_win->bxsize, cursor_c, cursor_x, 28, cursor_x + 7, 43); sheet_refresh(sht_win, cursor_x, 28, cursor_x + 8, 44); } else if (512 <= i && i <= 767) { //鼠标数据 …… } else if (i == 10) { //10秒定时 putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484, "10[sec]", 7); } else if (i == 3) { //3秒定时 putfonts8_asc_sht(sht_back, 0, 80, COL8_FFFFFF, COL8_008484, "3[sec]", 6); } else if (i <= 1) { //光标使用定时 if (i != 0) { timer_init(timer3, &fifo, 0); //设定光标显示黑色 cursor_c = COL8_000000; } else { timer_init(timer3, &fifo, 1); //设定光标显示白色 cursor_c = COL8_FFFFFF; } timer_settime(timer3, 50); boxfill8(sht_win->buf, sht_win->bxsize, cursor_c, cursor_x, 28, cursor_x + 7, 43); sheet_refresh(sht_win, cursor_x, 28, cursor_x + 8, 44); } } }
程序通过编译后,在真机中运行。
Figure1.接收键盘输入 及 键盘输入的背景显示
在HariMain()程序中实现窗口的移动。
for (;;) { io_cli(); if (fifo32_status(&fifo) == 0) { io_stihlt(); } else { i = fifo32_get(&fifo); io_sti(); if (256 <= i && i <= 511) { //键盘数据 …… } else if (512 <= i && i <= 767) { //鼠标数据 if (mouse_decode(&mdec, i - 512) != 0) { …… sheet_slide(sht_mouse, mx, my); if ((mdec.btn & 0x01) != 0) { //按下左键移动窗口 sheet_slide(sht_win, mx - 80, my - 8); } } } else if (i == 10) { //10秒定时 putfonts8_asc_sht(sht_back, 0, 64, COL8_FFFFFF, COL8_008484, "10[sec]", 7); } else if (i == 3) { //3秒定时 putfonts8_asc_sht(sht_back, 0, 80, COL8_FFFFFF, COL8_008484, "3[sec]", 6); } else if (i <= 1) { //光标定时 if (i != 0) { timer_init(timer3, &fifo, 0); //0设定光标显示黑色 cursor_c = COL8_000000; } else { timer_init(timer3, &fifo, 1); //1设定光标显示白色 cursor_c = COL8_FFFFFF; } timer_settime(timer3, 50); boxfill8(sht_win->buf, sht_win->bxsize, cursor_c, cursor_x, 28, cursor_x + 7, 43); sheet_refresh(sht_win, cursor_x, 28, cursor_x + 8, 44); } } }
整个程序编译通过后,在真机中运行。
Figure2. 窗口的移动
初始化外部中断硬件模块到调用中断程序的过程。
图标的移动。
[x86OS] Note Over.
[2015.04.17]