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键盘的应用也有借鉴意义。