首页 » 互联网 » 带你理解键盘驱动轨范_键盘_按下

带你理解键盘驱动轨范_键盘_按下

萌界大人物 2024-09-18 21:45:04 0

扫一扫用手机浏览

文章目录 [+]

与键盘干系的芯片有两个,一个是键盘编码器 i8048,另一个是键盘掌握器 i8042,分别来看。

键盘编码器

键盘编码器位于键盘,它的浸染紧张是监测键的按下和弹起,然后将两种状态编码,发送给键盘掌握器。

带你理解键盘驱动轨范_键盘_按下 带你理解键盘驱动轨范_键盘_按下 互联网

上陈说的码叫做键盘扫描码,编码办法一共有三种,相应的也就有三套键盘扫描码,各套键盘扫描码详细怎么编码的就不说了,见后面的链接。
现今的键盘大多数都是用的第二套键盘扫描码,但也不用除利用第一套和第三套的,所以为了兼容,键盘掌握器会统统地转换为第一套扫描码。
当然这是默认的情形,详细利用哪一套扫描码,掌握器是否转化,还是要看硬件是否支持与详细怎么设置,有兴趣的详见文末链接。

带你理解键盘驱动轨范_键盘_按下 带你理解键盘驱动轨范_键盘_按下 互联网
(图片来自网络侵删)

因此第一套键盘扫描码还是得说道说道,一个键有按下就会有弹起,以是每个键会有两个状态,即每个键将会对应两个扫描码,键被按下时的编码叫做通码(),弹起时的编码叫做断码()。

大部分键的通码和断码都是 8 位 1 字节,但有些操作掌握键如 Ctrl、Alt,附加键如Insert,小键盘区如/ ,方向键等是 2 字节乃至多个字节。
有多个字节的扫描码常日都因此 开头。
只有 一个键因此 开头。

断码与通码的关系:断码通码。
二进制表示为 ,以是对付断码和通码可以这样理解,它们由 8 位比特组成,最高位第 7 位表示按键状态,1 表示按下,0 表示弹起。

键盘掌握器

键盘掌握器(i8042),不在键盘内部,被集成在南桥芯片上。
紧张吸收键盘编码器发来的键盘扫描码,做一些处理(比如第二套扫描码转第一套),然后触发中断关照 CPU 来读取扫描码。

键盘掌握器有 4 个 8 bits 寄存器,Status Register 和 Control Register,两者共用一个端口 0x64,读的时候是状态寄存器,写的时候是掌握寄存器。
Input Buffer 和 Output Buffer,两者共用一个端口 0x60,读的时候是输出缓冲器,写的时候是输入缓冲器。

状态寄存器:

bit0:1 表示输出缓存器满,CPU 读取后清零。
从编码器发过来的扫描码就放在这里。

bit1:1 表示输入缓存器满,掌握器读取后清零。

掌握寄存器:

通过写 0x64 端口来向掌握器发送命令,把稳是向掌握器本身发命令而不是向硬件设备键盘发命令,对付键盘的掌握便是通过掌握器来间接掌握,以是只须要操作键盘便是了。

命令掌握器便是将命令字节写入 0x64 端口,一样平常命令便是一字节,如果有两字节,则将第二个字节写入 0x60 端口。
由于要写 0x60 端口表示的缓存区,以是要先判断该缓存区是否为空。

比如进入保护模式设置 时,先判断输入缓存区是否为空,空的话表示掌握器已取走数据,可以连续进行,否则不空的话循环等待:

inb $0x64,%al # Wait for not busy 等待i8042缓冲区为空testb $0x2,%aljnz seta20.1

再向 0x64 端口写入命令 ,表示准备写 Output 端口,随后写入 0x60 端口的字节将放入 Output 端口。

inb $0x64,%al # Wait for not busy 同上testb $0x2,%aljnz seta20.2movb $0xdf,%al # 0xdf -> port 0x60 向端口0x60写入0xdf,打开A20outb %al,$0x60

同样的先判断输入缓存区是否为空,然后写入命令第二字节 ,这个字节会被送到 Output 端口,这个端口也是一个掌握端口,bit2 掌握着 的开关,以是如果是命令字节 表示关闭 。

关于键盘掌握器就说这么多,只讲述了与 xv6 干系的部分,其他部分同样的感兴趣的见文末的链接。

XV6

驱动程序便是硬件物理接口的封装,键盘驱动程序也是如此,它的紧张功能便是将读取扫描码转换成打算机所须要的信息,比如说转换成字符,旗子暗记等等。
xv6 在这方面实现的比较大略,只实现了字符转化,一些功能掌握键,我们来看看。

首先在 头文件中定义了端口号,掌握键如 Ctrl,分外键如 UP,以及最主要的映射表,来看个普通情形下的映射表:

static uchar normalmap[256] ={ NO, 0x1B, '1', '2', '3', '4', '5', '6', // 0x00 '7', '8', '9', '0', '-', '=', '\b', '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', // 0x10 'o', 'p', '[', ']', '\n', NO, 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', // 0x20 '\'', '`', NO, '\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', NO, '', // 0x30 NO, ' ', NO, NO, NO, NO, NO, NO, NO, NO, NO, NO, NO, NO, NO, '7', // 0x40 '8', '9', '-', '4', '5', '6', '+', '1', '2', '3', '0', '.', NO, NO, NO, NO, // 0x50 [0x9C] '\n', // KP_Enter [0xB5] '/', // KP_Div [0xC8] KEY_UP, [0xD0] KEY_DN, [0xC9] KEY_PGUP, [0xD1] KEY_PGDN, [0xCB] KEY_LF, [0xCD] KEY_RT, [0x97] KEY_HOME, [0xCF] KEY_END, [0xD2] KEY_INS, [0xD3] KEY_DEL};

键盘扫描码是一个键的代表,但不是我们想要的,我们想要的是这个键表示的意义,比如数字键 1 的通码是 , 显然不是我们想要的,我们想要的是数字 1,以是须要一个映射关系来转换,将所有键的映射关系凑集在一起便是上述的映射表。
是个大数组,下标便是这个键的扫描码,内容便是所表达的意思。

上述是一样平常情形,那当然还有非一样平常的情形,比如有按下 Shift,CapsLock,Ctrl 等掌握键,当按下这些掌握键后,其他键按下之后表达的意义就不一样了,以是还须要其余的映射表,这里就不列出来了,太多了,可以直接参考代码。
举个例子,当按下 Shift 键之后再按下数字键 1,通码 则该当映射成 !
而不是1。

有了这些理解之后来看 里面的源码:

int kbdgetc(void){ static uint shift; //shift用bit来记录掌握键,比如shift,ctrl static uchar charcode[4] = { normalmap, shiftmap, ctlmap, ctlmap }; //映射表 uint st, data, c; st = inb(KBSTATP); if((st & KBS_DIB) == 0) //输出缓冲区未满,没法用指令in读取 return -1; data = inb(KBDATAP); //从输出缓冲区读数据 if(data == 0xE0){ //通码以e0开头的键 shift |= E0ESC; //记录e0 return 0; } else if(data & 0x80){ //断码,表键弹起 // Key released data = (shift & E0ESC ? data : data & 0x7F); shift &= ~(shiftcode[data] | E0ESC); return 0; } else if(shift & E0ESC){ //紧接着0xE0后的扫描码 // Last character was an E0 escape; or with 0x80 data |= 0x80; shift &= ~E0ESC; } shift |= shiftcode[data]; //记录掌握键状态,如Shift,Ctrl,Alt shift ^= togglecode[data]; //记录掌握键状态,如CapsLock,NumLock,ScrollLock c = charcode[shift & (CTL | SHIFT)][data]; //获取映射表的内容,也便是该键表示的意义 if(shift & CAPSLOCK){ if('a' <= c && c <= 'z') c += 'A' - 'a'; else if('A' <= c && c <= 'Z') c += 'a' - 'A'; } return c;}

这个程序就可以看作极简的键盘驱动程序,也是键盘中断的做事程序的主体,完成键盘扫描码到所需信息的转化。
下面就来仔细剖析剖析:

前面说过有多张映射表多种映射办法,那怎么知道用哪张?用哪张得看看有没有相应的掌握键按下,以是得有个东西来记录掌握键的按下与否,这个东西便是变量 ,虽然变量名是 ,但不代表只记录 Shift 键的状态,记录的信息有:

#define SHIFT (1<<0)#define CTL (1<<1)#define ALT (1<<2)#define CAPSLOCK (1<<3)#define NUMLOCK (1<<4)#define SCROLLLOCK (1<<5)#define E0ESC (1<<6) //通码断码以E0开头

从这种定义掌握键的办法就可以看出利用 来记录掌握键的办法该当是利用位运算。

表示一个二维数组,可以看作是映射表的凑集,根据 的记录信息来选择映射表,后面用到的时候就明白了。

st = inb(KBSTATP);if((st & KBS_DIB) == 0) //输出缓冲区为空,没法用指令in读取 return -1;data = inb(KBDATAP); //从输出缓冲区读数据

这几句用来读取键盘扫描码,从键盘发过来的扫描码就放在输出缓冲区中。
要读取扫描码首先从状态寄存器读取当前状态到 ,再做与运算取出第 0 位,表示输出缓冲区的状态,如果为 0 表示输出缓冲区寄存器为空,没法读取返回 -1。
如果为 1 表示输出缓冲区寄存器已满有内容,可以读取,以是接着从端口 0x60 输出缓冲区读出扫描码到 。

if(data == 0xE0){ //通码以e0开头的键 shift |= E0ESC; //记录e0 return 0;}

如果这个扫描码为 0xE0,解释按下的键是分外键,扫描码不止 8 字节,这种情形在 变量中做好标记就可以直接返回了,等待下一个数据的到来再做详细处理

else if(data & 0x80){ //断码,表键弹起 // Key released data = (shift & E0ESC ? data : data & 0x7F); shift &= ~(shiftcode[data] | E0ESC); return 0;}

为 1 的话,表示第 7 位为 1,解释此数据为断码,收到断码不须要额外的做什么事,但如果这个断码是某个掌握键的断码,则该当将该掌握键在 里面的记录信息给打消掉。

以是得知道读出的 表示哪一个掌握键,以是有了 映射:

static uchar shiftcode[256] ={ [0x1D] CTL, [0x2A] SHIFT, [0x36] SHIFT, [0x38] ALT, [0x9D] CTL, [0xB8] ALT,};

私以为这个定义办法很不对头啊,实在不太明白一些掌握键用通码,一些用断码,这也就导致了那条利用了条件表达式的 赋值语句必须存在,由于 中映射 Shift 键的时候没有用断码,以是得转换成通码。
私以为这么映射很混乱,导致后面 中有些语句意义也不太明确,要么就该当将映射关系给补全,然后可以省却那句 赋值语句,使后面的语句书写变得更明确一点。
当然这不是重点,能理解这过程意思就行,总而言之如果 是个断码,不须要干其他的事,如果是掌握键的断码,将记录在 中的掌握键信息给打消掉就行。

else if(shift & E0ESC){ // Last character was an E0 escape; or with 0x80 data |= 0x80; shift &= ~E0ESC;}

这种情形对应的是紧接着 后面的键盘扫描码,键盘扫描码有多个字节的,都是成对存在也便是 这种形式,每次收到 ,都要将 键中记录的 信息给打消掉。
至于前面还有一句 还是与 xv6 设计的映射表有关,键盘上有着许多相同意义的键,xv6 将一些键的映射关系用断码来映射,比如除号键 /。

shift |= shiftcode[data]; //记录掌握键状态,如Shift,Ctrl,Altshift ^= togglecode[data]; //记录掌握键状态,如CapsLock,NumLock,ScrollLock

这两句来记录掌握键的状态,分了两种情形,两种运算办法。
该当能看出它们之间的差异吧,实现组合键的时候,Shift,Ctrl,Alt 须要按住不放才能生效,弹起后不再生效。
而像 CapsLock 之类的掌握键,只须要按下一次即可,即便弹起之后同样生效。
以是一个利用或运算,一个利用异或运算,自己仿照一下过程该当很随意马虎明白。

c = charcode[shift & (CTL | SHIFT)][data]; //获取映射表的内容,也便是该键表示的意义if(shift & CAPSLOCK){ //如果有 CapsLock 存在 if('a' <= c && c <= 'z') //小写变大写 c += 'A' - 'a'; else if('A' <= c && c <= 'Z') //大写变小写 c += 'a' - 'A';}

根据 中记录的掌握键信息,来选取映射表,根据 去获取该键盘扫描码所表示的意义。
由于 CapsLock 和 Shift 键的功能有相同之处,以是如果 c 便是个普通 26 个英笔墨母字符的话,须要额外判断大小写。

关于 xv6 的键盘驱动程序差不多便是这么多,当然还有一些功能没说,比如 Ctrl 组合键功能,键盘的缓冲区等等,这在另一个文件里面涉及到了其余的知识,咱们放在后面再详述吧。

在此再聊聊常见的一些问题,在第一篇键盘里也说过,再来看看:

利用组合键时须要先按下掌握键。
键盘的中断程序为这些掌握键设置了标识()。
先按下掌握键,程序为掌握键设置好按下状态,再处理后到来的键时会检讨这些标识,是否有掌握键按下,以便做出不同的操作。

组合键按键时有顺序,但弹起无顺序哀求。
由上面的键处理程序可知,只有通码的键处理程序在干事,而断码的键处理程序除了掌握键的标识位须要复位之外其他键都是直接返回的。
以是利用键盘掌握输入时主要的是按键,而不是键弹起,以是只要按键对了,若何弹起并不主要。

一贯按着某个键时会一贯触发键盘中断,若是普通的字符键,电脑屏幕可能会涌现一贯打印某个字符的征象。
若是一些掌握键,则驱动程序可能会一直地将这个键设为按下状态。
当然,驱动程序是否记录上次按键取决于详细实现,大多是不记录的,xv6 也是如此,触发一次键盘中断就处理一个扫描码。

末了总结一番,键盘驱动程序同样的是封装键盘的物理接口利用,比如读取状态,读取扫描码等等。
键盘本身利用的是键盘扫描码,每个键都有自己的键盘扫描码,一个是通码表按下,一个表断码表弹起。
这个键盘扫描码只是唯一标识一个键,可以将键盘扫描码看作是一个键的物理意义,但这不是我们想要的,我们想要的是这个键代表的逻辑意义。
以是物理意义和逻辑之间须要一个转化,这便是映射表存在的意义。

键盘上有各种各样的键,还能组合利用,它们所代表的意义、具有的功能很多也很杂,xv6 只实现了个中一部分,但也足以让我们明白个中的实质。
不论要通过按键实现什么功能,还是就只是大略的利用一个键所代表的逻辑意义,都是要先获取键的扫描码,再通过映射表转化成所须要的信息,后续什么功能再在其上做文章。

好了本文就到这里结束,有什么缺点还请批评示正,也欢迎大家来同我谈论互换一起学习进步。

参考:

https://www.win.tue.nl/~aeb/linux/kbd/scancodes-11.html

https://wiki.osdev.org/"8042"_PS/2_Controller#Command_Register

标签:

相关文章

旗子暗记质量测试规范_暗记_旗子

择要:本规范详细解释了单板旗子暗记质量测试的方法。个中包括各种旗子暗记波形参数的定义,进行旗子暗记质量测试的条件,覆盖范围,合格标...

互联网 2025-01-11 阅读0 评论0