单片机由PS/2键盘接收数据程序:外部中断0设置为下降沿触发
- void int0() interrupt 0 using 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
- }
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_KEYS]=
- {……
- 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多媒体按键键盘时,只需要将键表中相应处修改即可,如ACPI
Power按键通码为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类按键的扫描码键值转换程序代码。
- 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;
- }
- }
第2类按键的扫描码键值转换程序与上面相似.注意:在退出该程序段时,对E0_FLAG和F0_FLAG标识清0.Pause键的处理程序,如果接收到0xE1,置E1_FLAG=1,然后顺次将后续接收到的7个字节数据和Pause的通码后7个字节比较,一致则返回KeyVal=KB_PAUSE;在比较完所有7个字节后清除E1_FLAG标识.键盘初始化程序kb_init()流程为:
① 上电后,接收键盘上电自检通过信号0xAA,或者自检出错信号0xFC.单片机接收为0xAA则进入下一步,否则进行出错处理.
② 关LED指示,单片机发送0xED,然后接收键盘回应0xFA,接着发送0x00接收0xFA.
③ 设置机打延时和速率:单片机发送0xF3,接收0xFA,发送0x00(250 ms,2.0 cps),接收0xFA.
④ 检查LED,发送0xED,接收0xFA,发送0x07(开所有LED),接收0xFA.发送0xED,接收0xFA,发送0x00(关LED),接收0xFA.
⑤ 允许键盘,发送0xF4,接收0xFA.键盘LED改变ps2_ledchange()函数流程:发送0xED;接收0xFA;发送led_status;接收0xFA.
5 结语
该驱动程序经Keil uVision2 编译,在AT89C51单片机上运行通过,实现了对PS/2 104键盘的支持,实现了对字符按键大小写切换,Num Lock切换、控制键及组合按键的支持.同时该程序对其他嵌入式或单片机系统中PS/2键盘的应用也有借鉴意义.