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抑制通信,然后下拉数据线“请求发送”,最后释放时钟线.在此过程中,从设备在不超过10ms的间隔内必须检查这个状态,当设备检测到这个状态时,它将开始产生时钟信号.此时数据传输的每一帧由12位构成,其时序和每一位含义如图3所示.
与从设备到主设备通信相比,其每帧数据多了一个ACK位.这是从设备应答接收到字节的应答位,由从设备通过拉低数据线产生,应答位ACK总是为0.主设备到从设备通信过程中,主设备总是在时钟线为低电平时改变数据线的状态,从设备在时钟上升沿读入数据线状态.
3 PS/2键盘与单片机的连接电路
PS/2键盘与AT89C51单片机的连接方式如图4所示.P1.0接PS/2数据线;P3.2(INT0)接PS/2时钟线.因为单片机的P1,P3口内部是带上拉电阻的,所以PS/2的时钟线和数据线可以直接与单片机的P1,P3相连接.
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 us delay100us(); PS2_SGN_DATA= 0; //起始位 PS2_SGN_CLOCK = 1; //发送DATA0-7 for(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;//发送停止位,停止位总为1 while(!PS2_SGN_CLOCK) _nop_(); //等待时钟线变高 while(PS2_SGN_CLOCK) _nop_(); //等待时钟线变低 //接收ACK //if(PS2_SGN_DATA) error(); //ACK信号由键盘发出,总为低电平 while(!PS2_SGN_CLOCK) _nop_(); //等待时钟线变高 EX0= 1; //开外部中断0 }
void int0() interrupt 0 using 0 {//外部中断0设置为下降沿触发 EX0=0; //关外部中断0 switch(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 }
if(F0_FLAG){//接收扫描码为断码 switch(mcu_revchar){//处理控制键 case 0x11:ages_status&=0xF7;break;//左alt释放 case 0x12:ages_status&=0xFE;break;//左shift释放 case 0x14:agcs_status&=0xFD;break;//左ctrl释放 case 0x58;if(led_status&0x04) led_status &= 0x03; //caps lock else led_status |=0x04; ps2_ledchange(); break; case 0x59: agcs_status &= 0xEF;break;//右shift释放 case 0x77: if(led_status&0x02)led_status&=0x05;//num lock else led_status |=0x02; ps2_ledchange(); break; case 0x7E:if(led_status&0x01) led_status&=0x06;//scroll lock else led_status |=0x01; ps2_ledchange(); break; default;break; } F0_FLAG=0; } else{//接收扫描码为通码 if(led_status&0x04) caps_flag=1;else caps_flag = 0; if(led_status&0x02) num_flag =1;else num_flag =0; if(agcs_status&0x11) shift_flag = 1;else shift_flag=0; //扫描码键值转换 if((caps_flag == shift_flag) || (!num_flag)) KeyVal=kb_plain_map[mcu_revchar]; else KeyVal = kb_shift_map[mcu_revchar]; switch(mcu_revchar)(//处理控制键或状态键 case 0x11:agcs_status|= 0x08;//左alt按下 Case 0x12:agcs_status|= 0x01;//左shift按下 case 0x14:agcs_status|= 0x02;//左ctrl按下 case 0x59:agcs_status|= 0x10;//右shift按下 default:break; } }
5 结语
该驱动程序经Keil uVision2 编译,在AT89C51单片机上运行通过,实现了对PS/2 104键盘的支持,实现了对字符按键大小写切换,Num Lock切换、控制键及组合按键的支持.同时该程序对其他嵌入式或单片机系统中PS/2键盘的应用也有借鉴意义。