单片机系统中PS2键盘驱动程序设计
单片机系统中ps2键盘驱动程序的设计

在单片机系统中,经常使用的键盘都是专用键盘.此类键盘为单独设计制作的,成本高、使用硬件连接线多,且可靠性不高,这一状况在那些要求键盘按键较多的应用系统中更为突出.与此相比,在PC系统中广泛使用PS/2键盘具有价格低、通用可靠,且使用连接线少(仅使用2根信号线)的特点,并可满足多种系统的要求.因此在单片机系统中应用PS/2键盘是一种很好的选择.文中在介绍PS/2协议和PS/2键盘工作原理与特点的基础上,给出了一个在单片机上实现对PS/2键盘支持的硬件连接与驱动程序设计实现.该设计实现了在单片机系统中对PS/2标准104键盘按键输入的支持.使用Keil C51开发的驱动程序接口和库函数可以方便地移植到其他单片机或嵌入式系统中.所有程序在Keil uVision2上编译通过,在单片机AT89C51上测试通过.1 PS/2协议目前,PC机广泛采用的PS/2接口为mini-DIN 6pin的连接器,如图1所示.PS/2设备有主从之分,主设备采用Female插座,从设备采用Male插头.现在广泛使用的PS/2键盘鼠标均在从设备方式下工作.PS/2接口的时钟与数据线都是集电极开路结构,必须外接上拉电阻(一般上拉电阻设置在主设备中).主从设备之间数据通信采用双向同步串行方式传输,时钟信号由从设备产生.1.1 从设备到主设备的通信当从设备向主设备发送数据时,首先检查时钟线,以确认时钟线是否为高电平.如果是高电平,从设备就可以开始传输数据;反之,从设备要等待获得总线的控制权,才能开始传输数据.传输的每一帧由11位组成,发送时序及每一位的含义如图2所示.每一帧数据中开始位总是为0,数据校验采用奇校验方式,停止位始终为1.从设备到主设备通信时,从设备总是在时钟线为高时改变数据线状态,主设备在时钟下降沿读人数据线状态.1.2 主设备到从设备的通信主设备与从设备进行通信时,主设备首先将时钟线和数据线设置为“请求发送”状态,具体方式为:首先下拉时钟线至少100us抑制通信,然后下拉数据线“请求发送”,最后释放时钟线.在此过程中,从设备在不超过10us 的间隔内必须检查这个状态,当设备检测到这个状态时,它将开始产生时钟信号.此时数据传输的每一帧由12位构成,其时序和每一位含义如图3所示.与从设备到主设备通信相比,其每帧数据多了一个ACK位.这是从设备应答接收到字节的应答位,由从设备通过拉低数据线产生,应答位ACK总是为0.主设备到从设备通信过程中,主设备总是在时钟线为低电平时改变数据线的状态,从设备在时钟上升沿读人数据线状态.2 PS/2键盘的编码与命令集2.1 PS/2键盘的编码目前,PC机使用的PS/2键盘都默认采用第2套扫描码集.扫描码有两种不同的类型:“通码(make code)”和“断码(break code)”.当一个键被按下或持续按住时,键盘会将该键的通码发送给主机;而当一个键被释放时,键盘会将该键的断码发送给主机.根据键盘按键扫描码的不同,可将按键分为3类:第1类按键通码为一个字节,断码为0xF0+通码形式.如A键,其通码为0x1C;断码为0xF0 0x1C.第2类按键通码为两字节0xE0+0xXX形式,断码为0xE0+0xF0+0xXX形式.如Right Ctrl键,其通码为0xE0 0x14;断码为0xE0 0xF0 0x14.第3类特殊按键有两个,Print Screen键,其通码为0xE0 0x12 0xE0 0x7C;断码为0xE0 0xF0 0x7C 0xE0 0xF0 0x12.Pause键,其通码为0xE1 0x14 0x77 0xE1 0xF0 0xl4 0xF0 0x77;断码为空.组合按键扫描码的发送是按照按键发生的次序,如按下面顺序按左Shift十A键:①按下左Shift键;②按下A键;③释放A键;④释放左Shift键,那么计算机上接收到的一串数据为0x12 0x1C 0xF0 0x1C 0xF0 0x12.在文中的驱动程序设计中,就是根据按键的分类对其分别进行处理.2.2 PS/2键盘的命令集主机可通过向PS/2键盘发送命令对键盘进行设置或者获得键盘的状态等操作.每发送一个字节,主机都会从键盘获得一个应答0xFA(“重发resend”和“回应echo”命令例外).驱动程序在键盘初始化过程中所用的指令:0xED,主机在该命令后跟随发送一个参数字节,用于指示键盘上Num Lock,Caps Lock,Scroll Lock Led的状态;0xF3,主机在这条命令后跟随发送一个字节参数定义键盘机打的速率和延时;0xF4,用于当主机发送0xF5禁止键盘后,重新使能键盘.3 PS/2键盘与单片机的连接电路PS/2键盘与AT89C51单片机的连接方式如图4所示.P1.0接PS/2数据线;P3.2(INT0)接PS/2时钟线.因为单片机的P1,P3口内部是带上拉电阻的,所以PS/2的时钟线和数据线可以直接与单片机的P1,P3相连接.4 驱动程序设计驱动程序的开发使用Keil C51语言以及KeiluVision2编程环境.PS/2 104键盘驱动程序主要任务是实现单片机与键盘间PS/2通信,同时将接收到的按键扫描码转换为该按键的键值KeyVal,提供给系统上层软件使用.4.1 单片机与键盘间PS/2通信的程序设计在PS/2通信过程中,主设备(文中是单片机)是在时钟信号为低时发送和接收数据信号.因为单片机向键盘发送的是指令,需要键盘回应,所以这部分程序采用查询方式;而单片机接收键盘数据时,数据线上的信号在时钟为低时已经稳定,所以这部分程序采用中断方式,且不需要在程序中加入延时程序.单片机向PS/2键盘发送数据程序代码为:void ps2_sentchar(unsigned char sentchar){//ps2主设备向从设备发送数据unsigned char sentbit_cnt= 0x00;unsigned char sentchar_chk = 0x00;EX0=0; //关外部中断0//发起一个传送,发起始位PS2_SGN_CLOCK = 0; //将时钟线拉低并保持100 usdelay100us();PS2_SGN_DATA= 0; //起始位PS2_SGN_CLOCK = 1;//发送DATA0-7for(sentbit_cnt=0;sentbit_cnt< 8;sentbit_cnt++){while(PS2_SGN_CLOCK) _nop_(); //等待时钟线变为低PS2_SGN_DATA = sentchar& 0x01;//发送数据if(PS2_SGN_DATA) sentchar_chk++; //计算校验while(!PS2_SGN_CL0CK) _nop_(); //等待时钟线变高sentchar>>=1; //待发送数据右移一位}//发送校验位while(PS2_SGN_CLOCK) _nop_(); //等待时钟线变低switch(sentchar_chk){case 0:case 2:case 4:case 6:PS2_SGN_DATA =1;break;//奇校验case 1:case 3:case 5:case 7:PS2_SGN_DATA = 0;break;//奇校验default;break;)while(!PS2_SGN_CLOCK) _nop_(); //等待时钟线变高while(PS2_SGN_CLOCK) _nop_(); //等待时钟线变低PS2_SGN_DATA =1;//发送停止位,停止位总为1while(!PS2_SGN_CLOCK) _nop_(); //等待时钟线变高while(PS2_SGN_CLOCK) _nop_(); //等待时钟线变低//接收ACK//if(PS2_SGN_DATA) error();//ACK信号由键盘发出,总为低电平while(!PS2_SGN_CLOCK) _nop_(); //等待时钟线变高EX0= 1; //开外部中断0}单片机由PS/2键盘接收数据程序:外部中断0设置为下降沿触发void int0() interrupt 0 using 0 {//EX0=0; //关外部中断0switch(ps2_revchar_cnt){case 1:……case 8:mcu_revchar<<=1;if(PS2_SGN_DATA) mcu_revchar |= 0x01;ps2_revchar_cnt++;break;case 0:ps2_revchar_cnt++;break; //开始位,case 9:ps2_revchar_cnt++;break; //校验位,可添加校验程序case 10: _nop_();//停止位ps2_revchar_cnt= 0;revchar_flag=1;//置接收到数据标识位break;default:break;}EX0=1;//开外部中断0}4.2 键盘扫描码转换程序设计由于键盘扫描码无规律可循,因此由键盘扫描码获得相应按键的键值(字符键为其ASCII值,控制键如F1,Ctrl等为自定义值),只能通过查表的方式获得.由于按键的3种类型及部分按键对应着两个键值(如A键的键值根据Caps和Shift键状态有0x41(A)和0x61(a)两种),因此综合考虑查表转换速度和资源消耗,设计中使用4个键盘表:键盘扫描码转换基本集和切换集(kb_plain_map[NR_KEYS]与kb_shift_map[NR_KEYS]);包含E0前缀的键盘扫描码转换基本集和切换集(kbeO_plain_map[NR_KEYS]与kbe0_shiftmap[NR_KEYS]).PS/2 104键盘按键扫描码最大值为0x83,所以设置NR_KEYS为132.所有4个键盘表的定义均为如下形式:KB_MAP[MAKE CODE]=KEYVAL,如果扫描码对应的按键为空(如KB_MAP[0x00]),则定义相应键值为NULL_KEY(0x00).以下是键盘扫描码基本集的部分代码实例:kb_plain_map[NR_K EYS]={……NULL_KEY;0x2C;0x6B;0x69;0x6F;0x30;0x39;NULL_KEY; //扫描码0x40~0x47//对应按键空,逗号,K,I,O,0,9,空//对应键值0x00,',','k','i','o','O','9',0x00…… };如此设计键盘转换表的另一个好处在于,以后如需扩展支持有ACPI、Windows多媒体按键键盘时,只需要将键表中相应处修改即可,如ACPIPower按键通码为0xE0 0x37,修改kbe0_plain_map[0x37]=KB_ACPI_PWR即可.特殊按键Pause使用单独程序处理,如果接收到0xE1就转入这段程序.而Print Screen键则将其看作是两个通码分别为0xE0 0x12和0xE0 0x7C的“虚键”的组合键处理.在驱动程序中设定如下全局变量:led_status记录Scroll Lock Led,Num Lock Led和Caps Lock Led的状态(关为0,开为1);agcs_status记录左右Shift Ctrl Gui Alt状态,相应键按下则对应位为1,释放为0.E0_FLAG接到0xE0置1;E1_FLAG接收到0xE1置1;F0_FLAG接收到0xF0置1.按键键值通过KeyVal提供上层程序使用.PS/2键盘扫描码键值转换程序ps2_codetrans()流程框架如图5所示.第1类按键的扫描码键值转换程序代码。
C51程序PS2

}
}
void getkey() interrupt 1 //内部中断0 用来处理缓冲区里的数据
{
unsigned char i=0;
tr0=0;
th0=0;
tl0=0;
count=0; //中断记数则0
if((temp[0]==18 || temp[0]==89) && temp[1]!=0xf0 ) //shift被按下
{
key=noshift[i][1];
ie=0x83;
return;
}
}
}
for(i=0;i<5;i++)
{
temp[i]=0;
}
}
unsigned char count=0,num=9,temp[5],shu=0; //中数次数 中断控制变量 缓冲区数组 缓冲区指针
unsigned char key=0; //按键最终值
void zhongduan() interrupt 0 //外部中断0 用来接受键盘发来的数据
{
dat>>=1; //接受数据 低->高
tr0=1;
}
else
{
temp[shu]=dat1;temp[shu+1]=dat2; shu+=2; //如果shift键被按下则记录与它同时按下的那个键
count=0;
}
if((temp[0]==18 || temp[0]==89) && (temp[2]==18 || temp[2]==89) ) tr0=1; //如果缓冲区中有两个间隔的shift键则证明需要的铵键结束
用单片机来模拟ps2电脑键盘的程序

用单片机来模拟ps2电脑键盘的程序//本程序可实现用单片机来代替ps2键盘来给电脑输入数据.//您只需从单片机上引出2根线到电脑的ps/2接口就可实现//本程序在51hei-5开发板上测试通过//跳线设置:默认//晶振:6M#include<reg51.h> //包含单片机寄存器的头文件#include<intrins.h> //包含_nop_()函数定义的头文件//#include"51hei.h"/************************************************************** *****************以下是引脚定义*************************************************************** ****************/sbit PS2CLK=P3^5;sbit PS2DAT=P2^7;/*****************************************************函数功能:延时1ms(3j+2)*i=(3×33+2)×10=1010(微秒),可以认为是1毫秒***************************************************/void delay1ms(){unsigned char i,j;for(i=0;i<10;i++)for(j=0;j<33;j++);}//===================================== =================================//2051模拟PS2键盘和PC机通讯程序 6M下发送程序代码//程序的完整版本及hex文件下载地址://===================================== =================================PS2keytohost(unsigned char vale){unsigned char h = 0;unsigned char i = 8;unsigned char J;unsigned char t;bit bparity =0 ;ACC = vale;//获取字节的奇偶信息if(!P) //ACC中偶数时,P为0,但是PS2中时奇校验( 字节中的1的个数+校验位 = 奇数){ bparity = 1; }if(PS2CLK&&PS2DAT) //发送前检测PS2总线{for(J = 12 ;J > 0;J--) {;}//6M,大约延时40uS后再检测if(PS2CLK)//时钟线空闲{if(PS2DAT)//数据线空闲{//for(t = 10;t > 0;t--){;} //6M,大约延时20uSfor(J = 11;J > 0;J--) //1共11个数据{if(h == 0) // 送起始位{PS2DAT = 0;h++;}else if(h == 1)//送8位数据位{PS2DAT = vale & 0x01;//先LSB开始vale >>= 1;i--;if(i == 0) //发送完成{ h++; }}else if(h == 2)//送校验位{PS2DAT = bparity;h++;}else{ PS2DAT = 1; } //送停止位for(t = 12;t > 0;t--){;} //6M,大约延时40uS PS2CLK = 0;//拉低时钟线,主机接收for(t = 12;t > 0;t--){;} //6M,大约延时40uS PS2CLK = 1;//拉高时钟线,设备发送if(!PS2CLK) //检测到时钟线变低{ //主机不要这次通讯 (很罕见)return(0);//返回 0}for(t = 6;t > 0;t--){;} //6M,大约延时20uS}//for(J = 11;J > 0;J--)for(t = 12;t >0;t--){;}return(1);//返回 1}//if(PS2DAT)}//if(PS2CLK)}//if(PS2CLK&&PS2DAT)}//end/***************************************************** 函数功能:主函数***************************************************/ void main(void){PS2keytohost(0X1C);while(1){delay1ms(); }}。
51单片机控制PS2键盘头文件

51单片机控制PS2键盘头文件51单片机控制PS2键盘是DM51的一个有用接口,光盘中程序给出了调试过的,如果有用户丢失了头文件,请将下面文件存为ps2.h转载请注明出处。
//===========================ps2.h头文件=============================// #ifndef PS2_H#define PS2_Hsbit keydata=P1^7;sbit clk=P3^2;unsigned char times=0;unsigned char i=0;unsigned char keycode=0,ps2_key; //ps2_key用于存放接收到的键码static unsigned char BF=0; //标识是否有字符被收到unsigned char code noshift[80][2]={1 , 8,// { f9 }3 , 4,// { f5 }4 , 2,// { f3 }5 , 0,// { f1 }6 , 1,// { f2 }7 ,11,// { f12 }9 , 9,// { f10 }13 ,25,// { tab }20 ,27,// { ctrl }41 ,29,// { space } 31 ,30,// { win } 12 , 3,// { f4 }11 , 5,// { f6 }10 , 7,// { f8 }14 ,96,// { ` }22 ,49,// { 1 }28 ,97,// { a }30 ,50,// { 2 }33 ,99,// { c }38 ,51,// { 3 }37 ,52,// { 4 }46 ,53,// { 5 }47 ,31,// { winright} 54 ,54,// { 6 }61 ,55,// { 7 }62 ,56,// { 8 }50 ,98,// { b }35 ,100,// { d }36 ,101,// { e }43 ,102,// { f }52 ,103,// { g }51 ,104,// { h }59 ,106,// { j }58 ,109,// { m }49 ,110,// { n }21 ,113,// { q }45 ,114,// { r }27 ,115,// { s }60 ,117,// { u } 42 ,118,// { v }29 ,119,// { w } 34 ,120,// { x }53 ,121,// { y }26 ,122,// { z }65 ,44,// { , }66 ,107,// { k }67 ,105,// { i }68 ,111,// { o }69 ,48,// { 0 }70 ,57,// { 9 }73 ,46,// { . }74 ,47,// { / }75 ,108,// { l }76 ,59,// { ; }77 ,112,// { p }78 ,45,// { - }82 ,39,// { ' }85 ,61,// { = }84 ,91,// { [ }91 ,93,// { ] }88 ,26,// { caps } 93 ,92,// { \ }90 ,32,// { enter } 120,10,// { f11 } 102,12,// { back } 224,13,// { home } 105,14,// { end }125,15,// { pageup }122,16,// { pagedown }117,17,// { up }114,18,// { down }107,19,// { left }116,20,// { right }113,21,// { del }112,22,// { insert }225,23,// { pause }118,24,// { esc }131, 6,// { f7 }};unsigned char code addshift[47][2]= {14,126, // { ~ }22, 33, // { ! }30, 64, // { @ }38, 35, // { # }37, 36, // { $ }46, 37, // { % }54, 94, // { ^ }61, 38, // { & }62, 42, // { * }70, 40, // { ( }69, 41, // { ) }78, 95, // { _ }85, 43, // { + }93,124, // { | }84,123, // { { }76, 58, // { : } 82, 34, // { " } 65, 60, // { < } 73, 62, // { > } 74, 63, // { ? } 28 ,65,// { a } 50 ,66,// { b } 33 ,67,// { c }35 ,68,// { d }36 ,69,// { e } 43 ,70,// { f } 52 ,71,// { g } 51 ,72,// { h } 67 ,73,// { i } 59 ,74,// { j } 66 ,75,// { k } 75 ,76,// { l } 58 ,77,// { m } 49 ,78,// { n } 68 ,79,// { o } 77 ,80,// { p } 21 ,81,// { q } 45 ,82,// { r } 27 ,83,// { s } 44 ,84,// { t } 60 ,85,// { u } 42 ,86,// { v } 29 ,87,// { w } 34 ,88,// { x }26 ,90,// { z }};unsigned char getchar(unsigned char k) //转换键码为ASCII码{unsigned char j;if(!i)for(j=0;j<80;j++){if(noshift[j][0]==k){ps2_key=noshift[j][1];return 1;}}elsefor(j=0;j<47;j++){if(addshift[j][0]==k){ps2_key=addshift[j][1];return 1;}}return 0;}void Keyboard_out(void) interrupt 0{if(times<9){keycode=keycode>>1; //因键盘数据是低>>高,结合上一句所以右移一位if(keydata) keycode=keycode | 0x80; //当键盘数据线为1时为1到最高位 }times++;if(times>10){times=0;if(keycode==0xe0 || keycode==0xf0){}//return;}else if((keycode==18 || keycode==89) && i==0){i=1;}else if((keycode==18 || keycode==89) && i==1){i=0;}else {EX0=0;BF=1;} //关中断等显示完后再开中断//当中断11次后表示一帧数据收完,清变量准备下一次接收//(注:如这里不用BF和关中断直接调Decode()//则所Decode中所调用的所有函数要声明为再入函数)}// while(!clk); //等待PS/2CLK拉高}#endif。
单片机与PS2键盘接口程序

/*---------------------------------------------
2个放键事件处理(集中管理)
----------------------------------------------*/
PS2Buffers.PS2Keyco u* nt = 0;//脉冲计数归零
if (PS2DATA){//高电平是停止位
if (key == 0xe0){//本次是扩展键
PS2Buffers.PS2KeyExtFlage = 0xe0;//置扩展键标志(小键盘只有回车键)
}
else if (key == 0xf0){//本次是键断码,键释放
void int1proc() interrupt IE1_VECTOR using 1
{
unsigned ch* ar i, key;
code unsigned ch* ar PS2TAB[] = {//20键PS2小键盘键码表
0x70,//0
0x69,//1
0x72,//2
0x7a,//3
计数器清零
PS2CLOCK = 1;//释放PS2时钟总线
}
}
}
else PS2Buffers.PS2KeyPopError = 0xed;//置停止位错误号0xed
}
else PS2Buffers.PS2Keyco u* nt = 0;//PS2键盘出错
}
/*------------------------------------------------------------------
PS2键盘的设计---C51程序

PS2键盘的设计---C51程序+详细注释(转)PS2键盘的设计---C51程序#include <reg51.H>#define Frequence 11 //晶振频率单位是MHZ#define DELAY 10*Frequence/6 //发送程序延时#define SLEEP 8*Frequence/6 //发送程序延时sbit KBCLK="P3"^0; //键盘时钟线sbit KBDATA="P3"^1; //键盘数据线bit bat(void); //基本保证测试无错误返回0,有错返回1unsigned char buf_length(); //返回缓冲区数据个数bit command_s(); //键盘命令检查,有命令要接受返回1void clr_buf(void); //清键盘缓冲区void del_head(); //删除缓冲区头unsigned char exist(unsigned char);//检查键盘缓冲区中是否有与参数相等数,有则返回位置,无则返回255//bit emputy(); //检查键盘缓冲区是否空,是返回1unsigned char get_head(); //取键盘缓冲区头,头指针不变unsigned char get_head_f();//取缓冲区头对应标记,标记为0表示对应键已经松下bit insert(unsigned char,unsigned char);//插入缓冲区,并设置对应标记,成功则返回1void ini_timer01(); //定时器初始化void receive_process(); //接收键盘命令并处理void reset(); //软件复位unsigned char scankb(unsigned char); //扫描第N行,返回列直void send_buf(); //发送缓冲区扫描码bit send_code(unsigned char _KeyNo,bit flag);//发送按键扫描码,flag=0发送断开码,flag=1发送接通码bit send(unsigned char); //发送数据void set_default(); //设置缺省值void set_timer1(); //复位定时器1void set_scan_v(unsigned char); //设置扫描速度(拍发速率、延迟时间)void set_flag(unsigned char); //设置缓冲区对应标记void set_led(unsigned char); //设置LEDvoid secret(unsigned char);void scan(void);unsigned char get_end();bit emputy(void);//-----------------------函数声明,变量定义--------------------------------------------------------#include <reg51.h>#define KEY P1unsigned char key_code; //键值unsigned char key_buf[8]; //按键缓冲区unsigned char key_COUNT; //按键计数器unsigned char COUNT_TI; //定时中断计数//-----------------------变量声明---------------------------------------------------------------------void system_init(void ); //初始化,设置定时器0的工作方式,供主程序调用void TIMER0_SCANkey(); //定时器0中断处理函数bit judge_hitkey(); //判断是否有键按下,有返回1,没有返回0unsigned char scan_key(); //扫描键盘,返回键值(高四位代表行,低四位代表列) void key_manage(unsigned char keycode); //按键处理//...........每个按键对应一个处理程序//--------------------------------------------------------------------------------------------------// 函数名称:scan_key// 函数功能:扫描键盘,返回键值(高四位代表行,低四位代表列)// 无键按下返回0//--------------------------------------------------------------------------------------------------unsigned char scan_key() //扫描键盘,返回键值(高四位代表行,低四位代表列){unsigned char scancode,keycode,keycode_line,keycode_row;scancode="0xF0"; //列置低,行置高KEY="scancode"; //输入扫描码,扫描行keycode_line=KEY;scancode="0xF0"; //列置高,行置低KEY="scancode"; //输入扫描码,扫描列keycode_row=KEY;keycode=(((keycode_line<<4)&0xF0)|(keycode_row&0x0F));return(keycode);}//--------------------------------------------------------------------------------------------------// 函数名称:Timer0_init()// 函数功能:初始化设置// 设定INT0的工作方式//--------------------------------------------------------------------------------------------------void Timer0_init(void ){TMOD="0x20"; //定时器0工作在方式2的定时模式ET0=1; //定时器0中断允许TH0=0;TL0=0;TR0=1; //定时器0开始计数EA="1"; //系统中断允许}//--------------------------------------------------------------------------------------------------// 函数名称:TIMER0_intrupt// 函数功能:定时器0中断处理程序按键定时查询//--------------------------------------------------------------------------------------------------void TIMER0_SCANkey() interrupt 1 using 1{EA="0"; //系统中断禁止if((++COUNT_TI)%30==0){switch(COUNT_TI/30){case 1:if(scan_key()==0)COUNT_TI=0; //无键按下,计数值归零break;case 2:break;case 3:if(scan_key()==0)COUNT_TI=0; //无键按下,计数值归零,上次按键未扰动elsekey_code=scan_key(); //又有效建,获取键值break;default:if(scan_key()==0) //等待按键释放key_manage(key_code); //有一个有效按键,调用按键处理程序}}EA=1;}//--------------------------------------------------------------------------------------------------// 函数名称:key_manage// 函数功能:有效按键处理// 按键计数器加1,缓存区数据后移1位//--------------------------------------------------------------------------------------------------void key_manage(unsigned char keycode){unsigned char i;for(i=7;i>=0;i--){key_buf[i]=key_buf[i-1]; //缓冲区内数据后移1位}key_buf[0]= keycode; //将键值送入缓冲区key_COUNT++; //按键计数器加一}//-----------------------函数声明,变量定义-------------------------------------------------------- #include <reg51.h>#define KEY P1sbit DATA="P3"^1; //数据线sbit CLK="P3"^2; //时钟线unsigned char key_buf[8]; //按键缓冲区unsigned char key_COUNT; //按键计数器//--------------------------------------------------------------------------------------------------// 函数名称:delay// 入口参数:N// 函数功能:延时子程序,实现(16*N+24)us的延时// 系统采用11.0592MHz的时钟时,延时满足要求,其它情况需要改动//--------------------------------------------------------------------------------------------------void delay(unsigned int N){int i;for(i=0;i<N;i++);}//--------------------------------------------------------------------------------------------------// 函数名称:CAL_jiaoyan// 函数功能:计算校验位//--------------------------------------------------------------------------------------------------bit CAL_jiaoyan(unsigned char byte_data){//}//--------------------------------------------------------------------------------------------------// 函数名称:SEND_byte// 函数功能:发送一子节数据//--------------------------------------------------------------------------------------------------void SEND_byte(unsigned char byte_data){unsigned char i,temp;if(CLK==0) //时钟线为低temp="byte"_data;CLK="1";DA TA="0";delay(0);CLK="0"; //发送起始位for(i=0;i<8;i++){delay(0);CLK="1";DA TA=(temp&0x01); //发送数据byte_data=byte_data>>1;delay(0);CLK=0;}delay(0);CLK="1";DA TA=CAL_jiaoyan(byte_data); //发送校验位delay(0);CLK=0;delay(0);CLK="1";DA TA=1; //发送结束位delay(0);CLK=0;}//-------------------------------------------------------------------------------------------------- // 函数名称:RECEIVE_byte// 函数功能:接收一子节数据//-------------------------------------------------------------------------------------------------- unsigned char RECEIVE_byte(){unsigned char byte_data,i;CLK="0";delay(0);CLK="1"; //接收起始位,丢弃for(i=0;i<8;i++){delay(0);CLK="0";delay(0);CLK=1;byte_data=byte_data>>1;if(DATA=1)byte_data=byte_data|0x80;elsebyte_data=byte_data&0x7F; //接收8位数据}for(i=0;i<2;i++){delay(0); //接收校验位和结束位CLK="0";delay(0);CLK=1;}return(byte_data);}//--------------------------------------------------------------------------------------------------// 函数名称:manage// 函数功能:主机命令处理函数//-------------------------------------------------------------------------------------------------- void manage(unsigned char rec_data){}//--------------------------------------------------------------------------------------------------// 函数名称:SEND_keydata// 函数功能:发送按键值到主机//-------------------------------------------------------------------------------------------------- void SEND_keydata(){unsigned char ASCII_code; //// ASCII_code=judge_key(key_buf[key_COUNT]); //判断键值,按键编码成ASCII码SEND_byte(ASCII_code);key_COUNT--;}//--------------------------------------------------------------------------------------------------// 函数名称:主程序// 函数功能:循环查询主机状态//-------------------------------------------------------------------------------------------------- void main(){unsigned char rec_data;while(1){if(CLK==0&&DATA==0){rec_data=RECEIVE_byte(); //接收主机键盘manage(rec_data); //指令处理函数}if(key_COUNT!=0&&CLK==1) //有按键等待处理//线路空闲SEND_keydata();}。
51单片机PS2键盘程序

51单片机PS2键盘程序/*中断程序,帮助了解中断事件*/#include#includesbit key_data=P3^0;sbit key_clk=P3^2; //定义键盘接口的时钟脚sbit RELAY=P1^0; //继电器bit BF=0;//code unsigned char tmpdate[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};//定义常量做为输出unsigned char keyv=0; //变量为常量的索引unsigned int intNum=0; //计数接收个数unsigned char key_up=1; //检测按键按下否void ldedata(unsigned char scandata);//数据处理void main(void) //入口函数{EA=1; //首先开启总中断EX0=1; //开启外部中断0IT0=1; // 设置成下降沿触发方式P2=0;do{ //循环if(BF)ldedata(keyv);elseEA=1;}while(1);}void key_scan() interrupt 0{ //外部中断0if((intNum>0)&&(intNum<9)){keyv=keyv>>1;if(key_data)keyv=keyv|0x80;}intNum++;while(!key_clk)if(intNum>10){intNum=0;BF=1;EA=0; //等待处理完键值再开启}}void ldedata(unsigned char scandata) {// unsigned char Tempdata;if(!key_up) //键盘松开时{switch(scandata){case 0xf0:key_up=1;break;case 0x12: //左SHIFT,可以下面写相应处理RELAY=0;//shift=1;break;case 0x59: //右SHIFT,可以下面写相应处理RELAY=1;//shitf=1;break;default:P0=scandata;if(scandata==0x76) //当按下键盘上的J键时,继电器响RELAY=0;if(scandata==0x52){ //当按下键盘上的空格键时,断开继电器RELAY=1;}break;}}else{key_up=0;switch(scandata){case 0x12://shift=0;break;case 0x59://shift=0;break;}}BF=0; //标识字符处理完了}。
基于单片机的PS2键盘设计

PS2键盘在单片机上的应用摘要:在嵌入式PC应用系统中,作为人机交互设备的键盘,往往采用结构简单按键少的矩阵键盘。
标准键盘虽然能直接与嵌入式PC机的PS/2接口相连,但是体积大,按键多,不能满足需求,本文提出用一种AT89C52单片机实现具有标准PS/2接口的矩阵键盘,具有便捷,实用的特点。
关键词:PS/2接口,PS/2键盘,拨号键,AT89C52,LCD1602Abstract:PS/2 interface is one of the most useful mouse interface.It was IBM’s patent named osulum before. It is the dedicate interface of mouse and keyboard. This text implied a plan using PS/2 to make a system. PS/2 tansmit the data which was pressed, and AT89C52 receive it ,disposed it and transmit it to Lcd1602. Lcd1602 discover it to make us know which key has been pressed. PS/2 simulate a phone’s dial keyboard. This system’s feature is the circuit is sample and useful.Keywords:PS/2 keyboard, AT89C52, LCD1602,PS/2 interface,dial keyboard目录1、前言 (1)2、整体方案设计 (2)2.1方案论证 (2)2.2方案比较 (3)3、单元模块设计 (4)3.1PS2键盘模块 (4)3.2单片机模块 (6)3.3LCD显示模块 (7)4、软件设计 (9)5、系统技术指标及精度和误差分析 (10)6、结论 (11)7、设计小结 (12)8、致谢 (14)9、参考文献 (14)附录1:电路总图 (15)附录2:仿真图 (16)附录3:软件代码 (17)1、前言单片机因其性价比高, 处理能力强, 且抗干扰能力好, 在医疗器械、机电液控制、数据传输等各类工控系统和设备仪器中得到广泛应用。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
单片机系统中PS/2键盘驱动程序设计摘要分析PS/2协议;介绍PS/2标准健盘的第二套扫描码和命令集,并给出在单片机系统中支持PS/2健盘的硬件连接方式和利用Keil C51语言实现的驱动程序设计及部分代码。
该驱动程序可以方便地移植到其他单片机或嵌入式系统中。
关健词 PS/2协议 PS/2健盘单片机驱动程序在单片机系统中,经常使用的键盘都是专用键盘。
这类键盘都是单独设计制作的,成本高,连线多,且可靠性不高。
这些问题在那些要求键盘按键较多的应用系统中显得更加突出。
与此相比,在 PC系统中广泛使用的PS/2键盘具有价格低、通用可靠,且使用的连线少(仅使用2根信号线)的特点,并可满足多数系统的要求。
因此,在单片机系统中应用PS/2键盘是一种很好的选择。
本文在分析PS/2协议和PS/2键盘工作原理与特点的基础上,给出在AT89C51单片机上实现对PS/2键盘支持的硬件连接方法以及驱动程序的设计实现。
1 PS/2协议现在PC机广泛采用的PS/2接口为mini - DIN 6引脚的连接器。
其引脚如图1所示。
740)this.width=740" border=undefined>PS/2设备有主从之分,主设备采用female插座,从设备采用male插座。
现在广泛使用的PS/2键盘鼠标均工作在从设备方式下。
PS/2接口的时钟与数据线都是集电极开路结构的,必须外接上拉电阻。
一般上拉电阻设置在主设备中。
主从设备之间数据通信采用双向同步串行方式传输,时钟信号由从设备产生。
(1)从设备到主设备的通信当从设备向主设备发送数据时,首先会检查时钟线,以确认时钟线是否是高电平。
如果是高电平,从设备就可以开始传输数据;否则,从设备要等待获得总线的控制权,才能开始传输数据。
传输的每一帧由11位组成,发送时序及每一位的含义如图2所示。
740)this.width=740" border=undefined>每一帧数据中开始位总是为0,数据校验采用奇校验方式,停止位始终为1。
从设备到主设备通信时,从设备总是在时钟线为高时改变数据线状态,主设备在时钟下降沿读人数据线状态。
(2)主设备到从设备的通信主设备与从设备进行通信时,主设备首先会把时钟线和数据线设置为“请求发送”状态。
具体方式为:首先下拉时钟线至少100 us来抑制通信,然后下拉数据线“请求发送”,最后释放时钟线。
在此过程中,从设备在不超过 10us的间隔内就要检查这个状态。
当设备检测到这个状态时,将开始产生时钟信号。
此时数据传输的每一帧由12位构成,其时序和每一位含义如图3所示。
740)this.width=740" border=undefined>与从设备到主设备通信相比,其每帧数据多了一个ACK位。
这是从设备应答接收到的字节的应答位,由从设备通过拉低数据线产生,应答位ACK总是为。
主设备到从设备通信过程中,主设备总是在时钟为低电平时改变数据线的状态,从设备在时钟的上升沿读入数据线状态。
2 PS/2键盘的编码与命令集(1) PS/2扭盘的编码现在PC机使用的PS/2键盘都默认采用第二套扫描码集。
该扫描码集可参考文献[1]。
扫描码有两种不同的类型:通码(make code)和断码(break code)。
当一个键被按下或持续按住时,键盘会将该键的通码发送给主机;而当一个键被释放时,键盘会将该键的断码发送给主机。
根据键盘按键扫描码的不同,在此可将按键分为如下几类:第一类按键,通码为1字节,断码为OxFO+通码形式。
如A键,其通码为Ox1C,断码为OxFO Ox1C, 第二类按键,通码为2字节0 xEO + 0 xXX形式,断码为OxEO+OxFO+OxXX形式。
如 right ctrl键,其通码为OxEO 0x14,断码为OxEO OxFO 0x14, 第三类特殊按键有两个,print screen键通码为OxEO 0x12 OxEO Ox7C,断码为 OxEO OxFO Ox7C OxEO OxFO 0x12; pause键通码为Ox El 0x14 0x77 OxEl OxFO 0x14 OxFO 0x77,断码为空。
组合按键的扫描码发送按照按键发生的次序,如以下面顺序按左SHIFT+A键:1按下左SHIFT键,2按下 A键,3释放A键,4释放左SHIFT键,那么计算机上接收到的一串数据为0x12 Ox1C OxFO Ox1C OxFO 0x12, 在驱动程序设计中,就是根据这样的分类来对不同的按键进行不同处理的。
(2) PS/2键盘的命令集主机可以通过向PS/2键盘发送命令来对键盘进行设置或者获得键盘的状态等操作。
每发送一个字节,主机都会从键盘获得一个应答0 xFA“重发resend"和“回应echo',命令例外)。
下面简要介绍驱动程序在键盘初始化过程中所用的指令(详细键盘命令集见参考文献[1]): OxED主机在本命令后跟随发送一个参数字节,用于指示键盘上num lock, caps lock, scroll lock led的状态; OxF3主机在这条命令后跟随发送一个字节参数来定义键盘机打的速率和延时; OxF4用于在当主机发送OxF5禁止键盘后,重新使能键盘。
3 PS/2键盘与单片机的连接电路PS/2键盘与AT89C51单片机的连接方式如图4所示。
Pi. 0接 PS/2数据线,P3.2 (INTO)接 PS/2时钟线。
因为单片机的P1,P3口内部是带上拉电阻的,所以PS/2的时钟线和数据线可以直接与单片机的P1,P3相连接。
740)this.width=740"border=undefined>4 驱动程序设计驱动程序使用Keil C51语言,Keil uVision2编程环境。
PS/2 104键盘驱动程序的主要任务,是实现单片机与键盘间PS/2通信,以及将接收到的按键扫描码转换为该按键的键值KeyVal,提供给系统上层软件使用。
(1)单片机与健盘间PS/2通信的程序设计在PS/2通信过程中,主设备(单片机)是在时钟信号为低时发送和接收数据信号的。
因为单片机到键盘发送的是指令,需要键盘回应,所以这部分程序采用查询方式;而单片机接收键盘数据时,数据线上的信号在时钟为低时已经稳定,所以这部分程序采用中断方式,且不需要在程序中加人延时程序。
(2)健盘扫描码转换程序设计由于键盘扫描码无规律可循,因此由键盘扫描码获得相应按键的键值(字符键为其ASCII值,控制键如Fl,CTRL等为自定义值),只能通过查表的方式。
由于按键的三种类型及部分按键对应着两个键值(如A键的键值根据CAPS和 SHIFT键状态有 0x41 (A)和 Ox61(a)两种),因此综合考虑查表转换速度和资源消耗,设计中使用4个键盘表:键盘扫描码转换基本集和切换集kb-plain_map[ NR_ KEYS]与kb- shift- map[ NR_ KEYS];包含EO前缀的键盘扫描码转换基本集和切换集kbe0_plain_map[N又KEYS〕与kbe0_ shift-map [ NR_ KEYS]。
PS/2 104键盘按键扫描码最大值为0x83,所以设置NR_ KEYS为132。
所有四个键盘表的定义均为如下形式:KB_ MAP[ MAKE CODE] = KEYVAL,如果扫描码对应的按键为空,如KB_MAP[0x00],则定义相应键值为NULL-KEY(0x00)。
以下是键盘扫描码基本集的部分代码实例:kb_plain_map[NIZKEYS] ={……NULL- KEY; Ox2C; Ox6B; 0x69;Ox6F;Ox3O;0x39;NULL_KEY;//扫描码Ox4O-Ox47刀对应按键空,逗号,K,I,0,0,9,空//对应键值0x00,’,’,'k','i','o','0','9',0x00……};如此设计键盘转换表的另一个好处在于,以后如需扩展支持有ACPI, Windows 多媒体按键键盘时,只需要将键表中相应处修改即可。
如ACPI power按键通码为OxEO 0x37,修改 kbeO _ plain- map [ 0x37 ] = KB _ACPI_PWR即可。
特殊按键PAUSE使用单独程序处理,如果接收到OxEl就转入这段程序;而print screen键则将其看作是两个通码分别为OxEO 0x12和OxEO Ox7C的“虚键,,的组合键来处理。
在驱动程序中声明如下全局变量:led-status其bit0一scroll lock led关0、开 1; bitl一num lock led关为。
,开为1; bit2一caps lock led关为0,开为1; bit3-bit?总是。
;agcs_status记录左右shift ctrl gui alt状态,bit0一左shift键,bitl一左。
trl键,bit2一左gui键, bit3一左alt键,bit4-右shift键,bit5一右ctrl键,bit6一右gui键,bit7一右alt键,相应键按下则对应位为I,释放为。
EO_FLAG接到OxEO置1; El FLAG接收到OxEl置1; FO-FLAG接收到OxFO置1。
按键键值通过Keyval提供给上层使用。
PS/2键盘扫描码键值转换程序ps2_codetrans()流程如图5所示。
第一类按键的扫描码键值转换程序代码:if (FO-FLAG) t//接收扫描码为断码switch (mcu_revchar){//处理控制键case 0x11:agcs_status& = OxF7;break;//左alt释放case 0x12:agcs_status & =0xFE; break; //左shift释放case 0x14:agcs_status&=OxFD; break;//左ctrl释放case 0x58:if (1e走status&0x04)le走status&二0x03; //caps lock键else led_statusl =0x04;ps2_ledchange();break;case 0x59; agcs_status&二OxEF;break;//右shift释放case 0x77:if (led status&0x02 )led_status& = 0x05; //num lock键else led_status{ =0x02;ps2_ledchange();break;case Ox7E; if(led_status&0x01)led_status&=0x06; //scroll lock键else led_statusI =0x01;ps2_ledchange();break;default; break;}FO-FLAG = 0;lse { //接收扫描码为通码if (1e走status衣0x04) caps flag=1;else caps-flag二0;if (led-status & 0x02) num_flag=1;else num-flag二0;if (scga_status&0x11) shift flag=1;else shift flag=0;刀扫描码键值转换if ((caps flag==shift-flag)}1(!num_flag)) KeyVal=b_plain_map[mciLrevchar];else KeyVal二 kb-shift map[mcu_revcha];switch (mcu-revchar){ //处理控制键或状态键case 0x11: agcs_statusl二0x08;//左alt按下case 0x12: ages-status}二0x01;//左shift按下case 0x14: ages-status}二0x02;//左ctrl按下case 0x59:agcs_status}二0x10;//右shift按下default: break;}}740)this.width=740" border=undefined>第二类按键的扫描码键值转换程序与上相似。