[Rx86OS-XV] 键盘输入处理

来源:互联网 发布:cnc钻孔程式手动编程 编辑:程序博客网 时间:2024/06/09 16:47

平台

处理器:Intel Celeron® Dual-Core CPU  2.10GHz

操作系统:Windows7 专业版 x86

阅读书籍:《30天自制操作系统》—川合秀实[2015.04.15-04.16]

工具:../toolset/


1 通路

键盘输入处理的流程似鼠标输入处理流程,需要提前配置好GDT和IDT,并初始化好PIC。

1.1 初始化键盘

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;}

初始化键盘控制电路,让键盘控制电路接收键盘按下时向它发送的数据。


1.2 开启PIC检测键盘中断IRQ1

键盘中断信号为为PIC上的IRQ1,所以需要开启PIC对IRQ1中断的监听。

io_out8(PIC0_IMR, 0xf8);// 主PIC端口0x0021,11111000:IRQ0-2中断允许

允许监听某IRQ0-2后,如果PICIRQ0-2有中断来临,则PIC向CPU汇报这些中断。


1.3 键盘中断程序

在初始化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中,需要程序自动读取。


中断程序的返回需要使用IRETD指令,C语句无与IRETD对应的语句,需要用编写汇编程序asm_inthandler21()来调用inthandler21()的方式来达到读取60h中的键盘信息,在IDT中注册键盘中断函数注册asm_inthandler21()即可。
 

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
 

1.4 注册键盘中断程序

//设定GDT和IDTvoid 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又返回到原程序中执行后续指令)。


1.5 键盘按键的编码

在键盘上按下一个键时,键盘向端口60h发送这个按键的扫描码(通码);当松开这个按键时,键盘向端口60h发送这个按键的断码(通码+ 80h)。“书”14.4章节有键盘各个按键的通码表,其中’A’按键的通码值为1EH。


1.6 显示键盘的’A’键

在主函数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中运行程序。

 

2 组织

键盘上有很多个需要显示的字符,将它们组织在一个数据结构里面供键盘显示。


2.1 键盘的数据结构

按照键盘上每个字符编码的规律,用一个数组来存储键盘的各按键字符。

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。


2.2 键盘输入显示

最好是能根据键盘输入就能够对应键盘输入按键在数据结构的对应的字符。

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[]数组中的下标。


3 提升(扩展)

3.1 字符输入背景字符输入提示图标

(1) 描绘文字输入背景

文字的输入背景就是用绘制矩形的函数绘制一些矩形。

/* 制作文字输入的背景 * 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;}

(2) 字符输入提示图标

字符输入图标根据键盘输入的变化而变化,将光标的坐标变换写到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.接收键盘输入 及 键盘输入的背景显示

3.2 窗口移动

在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. 窗口的移动

4 总结

初始化外部中断硬件模块到调用中断程序的过程。

图标的移动。


[x86OS] Note Over.

[2015.04.17]

0 0
原创粉丝点击