鸿 网 互 联 www.68idc.cn

关于键盘(总论8042)

来源:互联网 作者:佚名 时间:2015-10-09 05:39
说到 键盘 就不得不先说“ 键盘 控制器”。只有明确了 键盘 的工作原理和其的组成之后,才能进一步开始用代码的形式来用信息控制的方式表达。单片微处理器8042,大多集成在南桥中,控制整个 键盘 的工作。包括加电自检、 键盘 扫描码的缓冲以及与主板的通讯

说到键盘就不得不先说“键盘控制器”。只有明确了键盘的工作原理和其的组成之后,才能进一步开始用代码的形式来用信息控制的方式表达。单片微处理器8042,大多集成在南桥中,控制整个键盘的工作。包括加电自检、键盘扫描码的缓冲以及与主板的通讯。8042分输入缓冲和输出缓冲,数据传输在I/O口的60h和64h进行,前者是数据口,后者是命令和状态口。

键盘输入到cpu读取,这中间要经过很多的过程,但是具体可以分为两大块:外设+内部组件。外设指的就是键盘键盘是有自己的芯片的(8048)。而内部组件则是cpu与8042。8048和8042则是联通内外的桥梁,也就是说,cpu是通过8042与键盘沟通的,8048从外界得到信息输入,通过8042告诉cpu,同样cpu也将直接8042发命令,8042再给8048传递。8048的主要工作就是检索来自于key matrix的外界输入,所产生的扫描码,将其存放于键盘自身的内部缓冲,而扫描码也因为厂商版本的差异而各自不同。8042的作用还有一个就是用来控制A20 GATE以决定cpu是否可以访问以MB为单位的偶数内存,以及想系统发送reset信号,让主机重新启动,8042还同样支持ps/2类型的鼠标。

而同样,键盘和cpu之间有接口,人与键盘也有接口,那就是键盘上的按键。在设计过程中,因为用户使用键盘的动作无非两个:按下(Press key),松开(Release key)。而在这两个动作之间的时间段,称为:Press key delay。以上才是一个完整的击键过程。而除了pause键之外,当按下和松开两个动作发生的时候,都会产生两个扫描码:Make Code和Break Code。在这两个动作之间的时间段里,会按照一定的频率产生Repeat code。当击键速度快的时候不会产生Repeat code。键盘有一个Repeat code的“产生延迟”设置,这个“产生延迟”指的是两次产生“Repeat code”之间的时间间隔。设定这个的目的是用来扫描按键的。比如设定一个延迟长度,当你按键大于这个时间段的时候,键盘系统会产生一个针对此键的repeat code.当一次按下很多键的时候,键盘系统总是对最后发生按下动作的键产生repeat code.比如,你现在按下了"A"键,产生了一个Press key的动作,键盘系统会为之产生一个Make code;随后,你保持按着"A"键不放开,键盘系统将会按照一定的频率产生针对"A"键的Repeat code。这个时候,你由按下了"B"键,键盘随即停止产生"A"键的Repeat code,然后产生一个"B"键的Make code。从此以后,在"A"键的当前“击键过程”中,"A"的Repeat code再也不会产生,即使"B"在"A"被Release之前松开也是这样。而之后如果“B”被保持按着的话,则键盘系统会按照一定的频率产生"B"的 Repeat code,直到"B"被松开,或又有一个键被Press为止。但无论"A"或"B"在任何时候被松开,都必然会产生一个Break code。

也就是说,只要发生了按下,就会有一个make code。只要发生了松开,就会产生break code.而对于Repeat Code,则有两个条件,一是,到当前的时刻为止,最后被按下的键;二是,这个最后被按下的键,在被松开之前被按的时间超过键盘所设置的Repeat code“产生延迟”。

键盘系统中,由两根线,Data line和Clock line,用来控制对Scan code的检索和传递。如果Data line和Clock line都处于高电平状态,则每次8048每次检索到一个Scan code,就会立即将其发送给8042芯片。如果Data line为高电平,而Clock line为低电平,则每次8048每次检索到一个Scan code,不会立即将其发送给8042芯片,而是先将其存放在键盘的内部缓冲中,等Clock line变成高电平后,再将缓冲中的Scan code发送给8042。如果Data line为低电平,则8048停止对Scan code的检索,转而等待接收来自于8042的命令,这种情况下,如果Clock line为高电平,8048则会将接到的命令的回复数据发送给8042,否则,则无法回复这些命令。所以在8042需要向8048发送命令时,必须保证 Clock line为高电平状态。

如果8042芯片收到一个来自于8048芯片的Scan code或者命令回复字节,经过处理后(可能存在的解码操作),会将其放入8042的Output buffer中,8042芯片会首先将状态寄存器(Status Register)的OBF(Output Buffer Full)标志设置,随后将Output port的IBF(bit-4)设为1,表示将产生一个IRQ1,然后将Clock line置为低电平,以禁止8042进一步接收8048的数据;然后发送一个IRQ1给Intel 8059A可编程中断控制器,由它将中断提交给CPU,CPU收到此IRQ后,将调用此IRQ对应的ISR(中断服务程序,这就是我们键盘Driver的一个重要部分)。随后此ISR可以从8042的数据端口60H中将Output buffer数据读取出来,并进行进一步的处理。当Output buffer中的数据被读取出来之后,8042会将状态寄存器的OBF标志清0,然后将Clock line置为高电平,以允许进一步接收8048发送来的数据。

8042自身有少量的RAM,以及一些ROM,这些内存与我们常说的内存(称为系统内存)没有任何关系,它们和系统内存不使用相同的地址空间。这些RAM和ROM是8042芯片处理器自身运算的需要。8042还有3个内部端口:Input port, Output port和Test Port。Output port有System Reset和A20 Gate两个与键盘无关的重要的控制位,其它的位都是向8048芯片输出,让8048芯片参考的控制位。程序员最好不要动这些除了System Reset和A20 Gate之外的控制位。IBM AT和IBM PS/2的这3个端口的格式有少许不同,主要因为在IBM PS/2上,8048同时支持PS/2鼠标。

Input Port:

Pin    Name    PS/2 Function            AT Function
0    P10    Keyboard Data             Undefined
1    P11    Mouse Data            Undefined
2    P12    Undefined             Undefined
3    P13    Undefined             Undefined
4    P14    External RAM             External RAM 
        1: Enable external RAM         1: Enable external RAM
        0: Disable external RAM        0: Disable external RAM
5    P15    Manufacturing Setting        Manufacturing Setting
        1: Setting enabled        1: Setting enabled
        0: Setting disabled        0: Setting disabled
6    P16    Display Type Switch        Display Type Switch
        1: Color display        1: Color display
        0: Monochrome            0: Monochrome
7    P17    Keyboard Inhibit Switch        Keyboard Inhibit Switch
        1: Keyboard enabled        1: Keyboard enabled
        0: Keyboard inhibited        0: Keyboard inhibited

Output Port:

Pin    Name    PS/2 Function            AT Function
0    P20    System Reset            System Reset
        1: Normal            1: Normal
        0: Reset computer        0: Reset computer
1    P21    A20 Gate            A20 Gate
        1: Enable            1: Enable
        0: Disable            0: Disable
2    P22    Mouse Data:            Undefined
        1: Pull Data low
        0: High-Z    
3    P23    Mouse Clock:            Undefined
        1: Pull Clock low
        0: High-Z    
4    P24    Keyboard IBF interrupt:        Output Buffer Full
        1: Assert IRQ 1
        0: De-assert IRQ 1    
5    P25    Mouse IBF interrupt:        Input Buffer Empty
        1: Assert IRQ 12
        0: De-assert IRQ 12    
6    P26    Keyboard Clock            Keyboard Clock
        1: Pull Clock low        1: Pull Clock low
        0: High-Z            0: High-Z
7    P27    Keyboard Data:            Keyboard Data:
        1: Pull Data low        1: Pull Data low
        0: High-Z            0: High-Z

Test Port:

Pin    Name    Function
0    T0    Keyboard Clock
1    T1    Mouse Clock
2    --    Undefined
3    --    Undefined
4    --    Undefined
5    --    Undefined
6    --    Undefined
7    --    Undefined 

8042本身就是一个小的处理器,有4个8bit的寄存器。status register(状态寄存器Read-Only),output buffer(输出缓冲寄存器Read-Only),input buffer(输入缓冲寄存器Write-Only),control register(控制寄存器Read/Write).

Status Register(状态寄存器):状态寄存器是一个8位只读寄存器,任何时刻均可被cpu读取,其作用也如名称一样,是随时用来判断状况的。其各位定义如下:

bit     Meaning
0    output register (60h) has data for system
1    input register (60h/64h) has data for 8042
2    system flag (set to 0 after power on reset)
3    data in input register is command (1) or data (0)
4    1=keyboard enabled, 0=keyboard disabled (via switch)
5    1=transmit timeout (data transmit not complete)
6    1=receive timeout (data transmit not complete)
7    1=even parity rec'd, 0=odd parity rec'd (should be odd)

Bit7: PARITY-EVEN(P_E): 从键盘获得的数据奇偶校验错误
Bit6: RCV-TMOUT(R_T): 接收超时,置1
Bit5: TRANS_TMOUT(T_T): 发送超时,置1
Bit4: KYBD_INH(K_I): 为1,键盘没有被禁止。为0,键盘被禁止。
Bit3: CMD_DATA(C_D): 为1,输入缓冲器中的内容为命令,为0,输入缓冲器中的内容为数据。
Bit2: SYS_FLAG(S_F): 系统标志,加电启动置0,自检通过后置1
Bit1: INPUT_BUF_FULL(I_B_F): 输入缓冲器满置1,i8042 取走后置0
BitO: OUT_BUF_FULL(O_B_F): 输出缓冲器满置1,CPU读取后置0

Control Register(控制寄存器):是一个可读写的8bit寄存器,作用也如名称,是用来控制的。在后面会用代码的形式给出用法。其各位定义如下:
Command Byte不能直接通过60h和64端口读取,若要访问它,必须首先通过64h端口向8042发布相应命令(20h/read,60h/write),然后再通过60h存取。

bit    Meaning
0    1=enable output register full interrupt
1    should be 0
2    1=set status register system, 0=clear
3    1=override keyboard inhibit, 0=allow inhibit
4    disable keyboard I/O by driving clock line low
5    disable auxiliary device, drives clock line low
6    IBM scancode translation 0=AT, 1=PC/XT
7    reserved, should be 0

Bit7: 保留,应该为0
Bit6: 将第二套扫描码翻译为第一套
Bit5: 置1,禁止鼠标
Bit4: 置1,禁止键盘
Bit3: 置1,忽略状态寄存器中的 Bit4
Bit2: 设置状态寄存器中的 Bit2
Bit1: 置1,enable 鼠标中断
BitO: 置1,enable 键盘中断

output buffer:输出缓冲器是一个8位只读寄存器。驱动从这些个寄存器中读取数据。这些数据包括:扫描码,送往8042命令的响应。间接的发往8048命令的响应。output buffer被存放可以通过60h端口读取的数据。这些数据分为2大类:一类是那些通过64h端口发送的,被用来控制8042芯片的命令的返回结果;另一类则是8048芯片所发过来的数据。后者的数据又分为Scan code,和对那些通过60h端口发送给8048的命令的回复结果。

input buffer:输入缓冲器是一个8位只写寄存器。缓冲驱动发来的内容发往8042的命令,通过i8042间接发往8048的命令,以及作为命令参数的数据。Input buffer被用来向8042芯片发送命令与数据,以控制8042芯片和8048芯片。Input buffer可以通过60h端口和64h端口写入,其中通过64h端口写入的是用来控制8042芯片的命令,而通过60h写入的数据有两种:一种是通过 64h端口发送的被用来控制8042芯片的命令所需要的进一步数据;另一种则是直接发给键盘用来控制8048芯片的命令。

 

这是8042的初始化函数。先是要申请一个端口。前面谈到了0x60,0x64.在这里的定义为:

#define I8042_COMMAND_REG       0x64
#define I8042_STATUS_REG        0x64
#define I8042_DATA_REG          0x60

通过8042芯片发布命令是通过64h,然后通过60h读取命令的返回结果。或者通过60h端口写入命令所需要的数据。可以看到2个数据分成了三个宏。其中的64h就是分为读与写状态的。也就是说,当需要读取status寄存器的时候,就要从0x64读,也就是I8042_STATUS_REG.写入command寄存器的时候,要使用I8042_COMMAND_REG。这样做是为了清楚不同情况下自己的动作,归根结底,两个都是0x64,只是状态的区别。而向8048发布命令,则需要通过60h.读取来自于Keyboard的数据(通过60h)。这些数据包括Scan Code(由按键和释放键引起的),对8048发送的命令的确认字节(ACK)及回复数据。Command分为发送给8042芯片的命令和发送给8048的命令。它们是不相同的,并且使用的端口也是不相同的(分别为64h和60h)。

关于几个端口的读写函数,就是将inb,outb两个函数封装到了一个新的函数中,这两个函数都是嵌入了汇编语言,用以和端口通信,通过这四个函数,也可以看出读写时端口的区别:

static inline int i8042_read_data(void)
{
        return inb(I8042_DATA_REG);
}
static inline int i8042_read_status(void)
{
        return inb(I8042_STATUS_REG);
}
static inline void i8042_write_data(int val)
{
        outb(val, I8042_DATA_REG);
}
static inline void i8042_write_command(int val)
{
        outb(val, I8042_COMMAND_REG);
}

在linux2.6.31中。申请函数主要的作用函数就是这一句:request_region(I8042_DATA_REG, 16, "i8042")。在数据口申请了16个bit的端口资源。处理完端口资源申请后,就是要检查i8042的接口是否正常了。在check中,就可以看到一个具体操作的例子。

读取状态寄存器的数值,并判断当前状态:(i8042_read_status() & I8042_STR_OBF)这一句就是如此。

#define I8042_STR_IBF           0x02
#define I8042_STR_OBF           0x01
#define I8042_BUFFER_SIZE       16

读取0x64的数值,第0位为1则表示有输入数据,此时需要延长下,用短延时函数:udelay(50)来延长50微妙。然后再从0x60中将数值读走。关于如何从60h端口读数据也是需要注意的地方。60h端口(读操作)对60h端口进行读操作,将会读取Output Register的内容。Output Register的内容可能是:来自于8048的数据。这些数据包括Scan Code,对8048发送的命令的确认字节(ACK)及回复数据。或者通过64h端口对8042发布的命令的返回结果。在向60h端口读取数据之前必须确保Output Register中有数据(通过判断Status Register的Bit-0是否为1)。当然,也并不是可以无限制的永久读取。而是设定为16次。(i < I8042_BUFFER_SIZE)(i++)完整的写来就是:

while (((str = i8042_read_status()) & I8042_STR_OBF) && (i < I8042_BUFFER_SIZE)) 
{
                udelay(50);
                data = i8042_read_data();
                i++;
}
显示下data的数值,你会发现是一个'_',用16进制来表示是14.然后再来对比i此时的数值,也就是跳出循环的数值,如果是16,证明接口有问题。如果不是,则可以正常使用。这步完结。剩下的就是 platform总线上的操作。

向60h端口写入的字节,有两种可能:如果之前通过64h端口向8042芯片发布的命令需要进一步的数据,则此时写入的字节就被认为是数据;否则,此字节被认为是发送给8048的命令。在向60h端口写数据之前,必须确保Input Register是空的(通过判断Status Register的Bit-1是否为0)。

向64h端口写入的字节,被认为是对8042芯片发布的命令(Command):写入的字节将会被存放在Input Register中;同时会引起Status Register的Bit-3自动被设置为1,表示现在放在Input Register中的数据是一个Command,而不是一个Data;在向64h端口写某些命令之前必须确保键盘是被禁止的,因为这些被写入的命令的返回结果将会放到Output Register中,而键盘如果不被禁止,则也会将数据放入到Output Register中,会引起相互之间的数据覆盖;在向64h端口写数据之前必须确保Input Register是空的(通过判断Status Register的Bit-1是否为0)。

而通过上面的例子,可以举出很多读写命令。8042,8048都有一些专门的命令和返回

1)发给8042的命令
20h:准备读取8042芯片的Command Byte;其行为是将当前8042 Command Byte的内容放置于Output Register中,下一个从60H端口的读操作将会将其读取出来。
60h:准备写入8042芯片的Command Byte;下一个通过60h写入的字节将会被放入Command Byte。
A4h:测试一下键盘密码是否被设置;测试结果放置在Output Register,然后可以通过60h读取出来。测试结果可以有两种值:FAh=密码被设置;F1h=没有密码。
A5h:设置键盘密码。其结果被按照顺序通过60h端口一个一个被放置在Input Register中。密码的最后是一个空字节(内容为0)。
A6h:让密码生效。在发布这个命令之前,必须首先使用A5h命令设置密码。
AAh:自检。诊断结果放置在Output Register中,可以通过60h读取。55h=OK。
ADh:禁止键盘接口。Command Byte的bit-4被设置。当此命令被发布后,Keyboard将被禁止发送数据到Output Register。
AEh:打开键盘接口。Command Byte的bit-4被清除。当此命令被发布后,Keyboard将被允许发送数据到Output Register。
C0h:准备读取Input Port。Input Port的内容被放置于Output Register中,随后可以通过60h端口读取。
D0h:准备读取Outport端口。结果被放在Output Register中,随后通过60h端口读取出来。
D1h:准备写Output端口。随后通过60h端口写入的字节,会被放置在Output Port中。
D2h:准备写数据到Output Register中。随后通过60h写入到Input Register的字节会被放入到Output Register中,此功能被用来模拟来自于Keyboard发送的数据。如果中断被允许,则会触发一个中断。

#define I8042_CMD_CTL_RCTR      0x0120
#define I8042_CMD_CTL_WCTR      0x1060
#define I8042_CMD_CTL_TEST      0x01aa
#define I8042_CMD_KBD_DISABLE   0x00ad
#define I8042_CMD_KBD_ENABLE    0x00ae
#define I8042_CMD_KBD_TEST      0x01ab
#define I8042_CMD_KBD_LOOP      0x11d2

在探测的时候,前三个宏会用到。也就是0x0120,0x1060,0x01aa.也就是读写8042,和自检。三个宏在函数中的应用需要 & 0xff.然后将结果写入command寄存器。使用i8042_write_command函数。

这里还牵扯到一个等待函数:

#define I8042_CTL_TIMEOUT       10000
int i = 0;
while ((i8042_read_status() & I8042_STR_IBF) && (i < I8042_CTL_TIMEOUT)) 
{
    udelay(50);
    i++;
}
return -(i == I8042_CTL_TIMEOUT)

以此来判断。当返回的为1的时候,错误。然后就可以来读取了。自然也有等待读取。

static int i8042_wait_read(void)
{
         int i = 0;
         while ((~i8042_read_status() & I8042_STR_OBF) && (i < I8042_CTL_TIMEOUT)) {
                udelay(50);
                i++;
         }
         return -(i == I8042_CTL_TIMEOUT);
}

param[i] = i8042_read_data();

这就是其中一个完整流程。接下来就是判断,如果是键盘,则用0x60写命令来试验下,如果正常就可以了,然后定义中断为1.

网友评论
<