键盘的发展趋势
就键盘的发展来看,键盘的键位是逐渐的增多(但不是无限制的增加毕竟键盘的面积是有限的),而且是向着多功能多媒体的方向发展。
从早期推出的电脑采用83键键盘,随后又推出了84键的设计标准,该标准将键盘分为三个区,即功能区、打字键区、负责光标控制和编辑的副键盘区。其中功能
键区的光标键与数字键作为双功能符号键使用,使用一个"Numlock"键来控制这两种功能的切换。虽然两种规格的键盘现在已经不多见了,但是键盘主要区
域的划分仍然沿用当时的标准,至今没有什么变化。直到1986年IBM公司推出了101键键盘,才在功能上实现了进一步的扩充,除了添加了F11、F12
两个功能键之外,还在键盘的中部多加了一组专用的光标控制和编辑的键,在微软推出WIN95操作系统之后,出现Windows启动键,时至今日大量带各种
附加功能键的键盘出现在我们的面前。例如Fn键、快捷键、带鼠标和手写板的键盘等等。
本人有必要说一下,常用的键盘的接口有AT接
口、PS/2接口和USB接口,现在绝大部分主板都是提供PS/2键盘接口,也称为"小口"。而兼容机尤其是较老的主板常常提供AT接口也被称为"大口
",所幸的是市场上有一种大小口键盘转换连接器,售价只有区区几元钱,它一举解决了两种接口键盘的兼容性问题。一些公司还推出了USB接口的键盘。根据最
新公布的 PC2001规范,以后所有通过ISA 总线工作的接口都会随着ISA总线的消亡而被USB取代。USB
允许同时将其他一些设备接入,相当于集成了一个HUB,比如可以将鼠标接入,这实际上节约了主板的COM或PS/2口。有的键盘甚至本身就集成了PS/2
转USB的电路,这样就更方便了。目前阻碍其普及的原因还是价格太高。集成USB
HUB的键盘,这类键盘大多采用USB接口,由于外设使用USB的机会增加,为了使用更多的USB设备,需要添加一种USB
HUB的装置扩展USB接口数量,但是专业的USB HUB价格比较昂贵,所以人们尝试将USB HUB集成到键盘或显示器中并得到成功。集成USB
HUB的键盘往往自身占用一个USB接口,用以保持键盘信号与主机的传输,同时提供2到4个USB接口供其他设备连结,简单地说是一进多出,价格上要比专
业的USB HUB便宜得多。
在单片机系统中,经常使用的键盘都是专用键盘。这类键盘都是单独设计制作的,成本高,连线多,且可靠性不高。这
些问题在那些要求键盘按键较多的应用系统中显得更加突出。与此相比,在PC系统中广泛使用的PS/2键盘具有价格低、通用可靠,且使用的连线少(仅使用2
根信号线)的特点,并可满足多数系统的要求。因此,在单片机系统中应用PS/2键盘是一种很好的选择。
本文在分析PS/2协议和PS/2键盘工作原理与特点的基础上,给出在AT89C51单片机上实现对PS/2键盘支持的硬件连接方法以及驱动程序的设计实现。
1PS/2协议
现在PC机广泛采用的PS/2接口为miniDIN 6引脚的连接器。其引脚如图1所示。
1—数据线(DATA);2—未用;3—电源地(GND);
4—电源(+5 V);5—时钟(CLK);6—未用。
图1PS/2
连接器PS/2设备有主从之分,主设备采用female插座,从设备采用male插座。现在广泛使用的PS/2键盘鼠标均工作在从设备方式下。PS/2接
口的时钟与数据线都是集电极开路结构的,必须外接上拉电阻。一般上拉电阻设置在主设备中。主从设备之间数据通信采用双向同步串行方式传输,时钟信号由从设
备产生。
(1) 从设备到主设备的通信
当从设备向主设备发送数据时,首先会检查时钟线,以确认时钟线是否是高电平。如果是高电平,从设备就可以开始传输数据;否则,从设备要等待获得总线的控制权,才能开始传输数据。传输的每一帧由11位组成,发送时序及每一位的含义如图2所示。
图2从设备到主设备的通信每一帧数据中开始位总是为0,数据校验采用奇校验方式,停止位始终为1。从设备到主设备通信时,从设备总是在时钟线为高时改变数据线状态,主设备在时钟下降沿读入数据线状态。
(2) 主设备到从设备的通信
主
设备与从设备进行通信时,主设备首先会把时钟线和数据线设置为“请求发送”状态。具体方式为:首先下拉时钟线至少100
μs来抑制通信,然后下拉数据线“请求发送”,最后释放时钟线。在此过程中,从设备在不超过10
μs的间隔内就要检查这个状态。当设备检测到这个状态时,将开始产生时钟信号。
此时数据传输的每一帧由12位构成,其时序和每一位含义如图3所示。
图3
主设备到从设备的通信与从设备到主设备通信相比,其每帧数据多了一个ACK位。这是从设备应答接收到的字节的应答位,由从设备通过拉低数据线产生,应答位
ACK总是为0。主设备到从设备通信过程中,主设备总是在时钟为低电平时改变数据线的状态,从设备在时钟的上升沿读入数据线状态。
2PS/2键盘的编码与命令集
(1) PS/2键盘的编码
现
在PC机使用的PS/2键盘都默认采用第二套扫描码集。该扫描码集可参考文献\[1\]。扫描码有两种不同的类型:通码(make
code)和断码(break
code)。当一个键被按下或持续按住时,键盘会将该键的通码发送给主机;而当一个键被释放时,键盘会将该键的断码发送给主机。
根据键盘按键扫描码的不同,在此可将按键分为如下几类:
第一类按键,通码为1字节,断码为0xF0+通码形式。如A键,其通码为0x1C,断码为0xF0 0x1C。
第二类按键,通码为2字节0xE0+0xXX形式,断码为0xE0+0xF0+0xXX形式。如right ctrl键,其通码为0xE0 0x14,断码为0xE0 0xF0 0x14。
第
三类特殊按键有两个,print screen键通码为0xE0 0x12 0xE0 0x7C,断码为0xE0 0xF0 0x7C 0xE0
0xF0 0x12; pause键通码为0x E1 0x14 0x77 0xE1 0xF0 0x14 0xF0 0x77,断码为空。
组合按键的扫描码发送按照按键发生的次序,如以下面顺序按左SHIFT+A键:1按下左SHIFT键,2按下A键,3释放A键,4释放左SHIFT键,那么计算机上接收到的一串数据为0x12 0x1C 0xF0 0x1C 0xF0 0x12。
在驱动程序设计中,就是根据这样的分类来对不同的按键进行不同处理的。
(2) PS/2键盘的命令集
主
机可以通过向PS/2键盘发送命令来对键盘进行设置或者获得键盘的状态等操作。每发送一个字节,主机都会从键盘获得一个应答0xFA(“重发
resend”和“回应echo”命令例外)。下面简要介绍驱动程序在键盘初始化过程中所用的指令(详细键盘命令集见参考文献\[1\]):
0xED主机在本命令后跟随发送一个参数字节,用于指示键盘上num lock, caps lock, scroll lock led的状态;
0xF3主机在这条命令后跟随发送一个字节参数来定义键盘机打的速率和延时;
0xF4用于在当主机发送0xF5禁止键盘后,重新使能键盘。
3PS/2键盘与单片机的连接电路
PS/2键盘与AT89C51单片机的连接方式如图4所示。P1.0接PS/2数据线,P3.2(INT0)接PS/2时钟线。因为单片机的P1、P3口内部是带上拉电阻的,所以PS/2的时钟线和数据线可以直接与单片机的P1、P3相连接。
4驱动程序设计
驱动程序使用Keil C51语言,Keil uVision2编程环境。PS/2 104键盘驱动程序的主要任务,是实现单片机与键盘间PS/2通信,以及将接收到的按键扫描码转换为该按键的键值KeyVal,提供给系统上层软件使用。
//============================================================
// 使用1602液晶显示和PS/2键盘的示例 沈阳 2007/12
// -------------------------------------------------
// http://www.ccnuc.cn http://bbs.ccnuc.cn
//=============================================================
//Keyboard接线
// PS/2--------51
// 1 DATA------P3.4
// 3 GND
// 4 VCC
// 5 CLK-------P3.2 接在51的外部中断,触发方式为低电平
//=============================================================
#include <at89x51.h>
#include <INTRINS.H>
#include "ps2_code.h"
#define uchar unsigned char
#define WAITFORKEYBOARDPULSE while(!scl); while(scl);
#define AddE aoe=0; ale=1;
#define AddNE aoe=0; ale=0;
#define DataE doe=0; dle=1;
#define DataNE doe=0; dle=0;
#define Key_Data P3_4 //定义Keyboard引脚
#define Key_CLK P3_2
#define Busy 0x80 //用于检测LCM状态字中的Busy标识
sfr ISP_CONTR=0xe7;
bit KeyBoardConnectFlag = 0;
sbit aoe=P2^3;
sbit ale=P2^4;
sbit doe=P2^6;
sbit dle=P2^7;
uchar bSelect = 0xf4;
uchar bit_enb = 0x10;
uchar seg_enb = 0x80;
uchar KeyCode; //键盘键值
uchar led_num=0x02;
void SetLight(bit IsLight); //背光灯控制
void LCDInit(void); //初始化1602函数
void DisplayChar(uchar X,uchar Y,uchar DData);//单个字符显示函数
void DisplayStr(uchar X,uchar Y,uchar *DData);//字符串显示函数
void DelayMs(uchar ms);
void Decode(unsigned char ScanCode);
void PS2_CMD(uchar sentchar);
uchar code str2[16] = {"char: code: H"};
uchar code str3[16] = {"To reboot?(y/n)"};
unsigned char code ccnuc_net[] = {"--www.ccnuc.cn--"};
unsigned char code email[] = {"shenyae86@163.com"};
unsigned char code Cls[] = {" "};
static uchar IntNum = 0; //中断次数计数
static uchar KeyV; //键值
static uchar DisNum = 0; //显示用指针
static uchar Key_UP=0, Shift = 0;//Key_UP是键松开标识,Shift是Shift键按下标识
static uchar BF = 0; //标识是否有字符被收到
void main(void)
{
unsigned char TempCyc;
DelayMs(250); //启动等待,等LCM讲入工作状态
LCDInit(); //LCM初始化
DelayMs(5); //延时片刻(可不要)
DisplayStr(0, 0, ccnuc_net);
DisplayStr(0, 1, email);
for (TempCyc=0; TempCyc<10; TempCyc++) DelayMs(250); //延时
DisplayStr(0, 1, Cls);
IT0 = 0; //设外部中断1为低电平触发
EA = 1;
EX0 = 1; //开中断
DelayMs(255);
PS2_CMD(0xed);
PS2_CMD(2);
do
{
if (BF) Decode(KeyV);
else EA = 1; //开中断
}
while(1);
}
/*********************************************************/
//函数:void PS2_CMD(uchar sentchar)
//功能:ps2主设备向从设备发送数据
//输入:uchar sentchar
//输出:修改全局变量键值
//描述:2007年12月6日
/*********************************************************/
void PS2_CMD(uchar sentchar)
{
uchar i= 0;
uchar sentchar_chk = 0;
EX0=0;
Key_CLK= 0; //将时钟线拉低并保持100 us
for(i=0;i<150;i++)_nop_();
Key_Data= 0; //起始位
_nop_();
Key_CLK = 1;
for(i=0;i<8;i++) //发送DATA0-7
{
while(Key_CLK) _nop_(); //等待时钟线变为低
Key_Data = (bit)(sentchar & 0x01); //发送数据
if(sentchar & 0x01 == 0x01) sentchar_chk++; //计算校验
while(!Key_CLK) _nop_(); //等待时钟线变高
sentchar>>=1; //待发送数据右移一位
}
while(Key_CLK) _nop_(); //等待时钟线变低
if(sentchar_chk%2==1) Key_Data = 0;
else Key_Data = 1; //发送校验位
while(!Key_CLK) _nop_(); //等待时钟线变高
while(Key_CLK) _nop_(); //等待时钟线变低
Key_Data =1; //发送停止位,停止位总为1
while(!Key_CLK) _nop_(); //等待时钟线变高
while(Key_CLK) _nop_(); //等待时钟线变低
//接收ACK
//if(sda) error();
//ACK信号由键盘发出,总为低电平
while(!Key_CLK) _nop_(); //等待时钟线变高
EX0=1;
}
void Keyboard_out(void) interrupt 1
{
if ((IntNum > 0) && (IntNum < 9))
{
KeyV = KeyV >> 1; //因键盘数据是低>>高,结合上一句所以右移一位
if (Key_Data) KeyV = KeyV | 0x80; //当键盘数据线为1时为1到最高位
}
IntNum++;
while (!Key_CLK); //等待PS/2CLK拉高
if (IntNum > 10)
{
IntNum = 0; //当中断11次后表示一帧数据收完,清变量准备下一次接收
BF = 1; //标识有字符输入完了
EA = 0; //关中断等显示完后再开中断 (注:如这里不用BF和关中断直接调Decode()则所Decode中所调用的所有函数要声明为再入函数)
}
}
void Decode(unsigned char ScanCode) //注意:如SHIFT+G为12H 34H F0H 34H F0H 12H,也就是说shift的通码+G的通码+shift的断码+G的断码
{
unsigned char TempCyc;
if (!Key_UP) //当键盘松开时
{
switch (ScanCode)
{
case 0xF0 :Key_UP = 1;break;// 当收到0xF0,Key_UP置1表示断码开始
case 0x12 : Shift = 1;break;// 左 SHIFT
case 0x59 : Shift = 1;break;// 右 SHIFT
default:
if (DisNum > 15)
{
DisplayStr(0, 1, Cls);//清LCD第二行
DisNum = 0;
}
if(((!Shift)&&((led_num & 0x04) == 0))||((Shift)&&((led_num & 0x04) == 4))) //如果SHIFT没按下
{
for (TempCyc = 0;(UnShifted[TempCyc][0]!=ScanCode)&&(TempCyc<59); TempCyc++); //查表显示
if (UnShifted[TempCyc][0] == ScanCode)
{
DisplayChar(DisNum, 1, UnShifted[TempCyc][1]);
DisNum++;
}
switch(ScanCode)
{
case 0x77://num
if((led_num&0x02)==0) led_num|=0x02;
else led_num&=0x05;
PS2_CMD(0xed);
PS2_CMD(led_num);
DelayMs(250);
break;
case 0x7e://scroll
if((led_num&0x01)==0) led_num|=0x01;
else led_num&=0x06;
PS2_CMD(0xed);
PS2_CMD(led_num);
DelayMs(250);
break;
case 0x58://A
if((led_num&0x04)==0) led_num|=0x04;
else led_num&=0x03;
PS2_CMD(0xed);
PS2_CMD(led_num);
DelayMs(250);
break;
case 0x66: DisplayChar(--DisNum, 1, ' ');break;
default:break;
}
}
else //按下SHIFT
{
for(TempCyc = 0; (Shifted[TempCyc][0]!=ScanCode)&&(TempCyc<59); TempCyc++); //查表显示
if (Shifted[TempCyc][0] == ScanCode) DisplayChar(DisNum, 1, Shifted[TempCyc][1]);
DisNum++;
}
break;
}
}
else
{
Key_UP = 0;
switch (ScanCode) //当键松开时不处理判码,如G 34H F0H 34H 那么第二个34H不会被处理
{
case 0x12 :Shift = 0;break; // 左 SHIFT
case 0x59 :Shift = 0;break; // 右 SHIFT
}
}
BF = 0; //标识字符处理完了
}
文章评论(0条评论)
登录后参与讨论