TA的每日心情 | 怒 2013-8-4 18:50 |
---|
签到天数: 6 天 连续签到: 1 天 [LV.2]偶尔看看I
|
本帖最后由 xinxincaijq 于 2012-12-3 08:47 编辑
BASYS2 试用心得4
现在来具体跑一跑板子上的主要资源,主要有8 个拨码开关、8 个LED、四个触发按键、四个数码管、PS2 接口、VGA 接口,其他外设接口等。
学习开发板,阅读高质量的demo 程序是一个好方法,能够培养好的代码风格和一些非常实用的编写方法。从官方给出的程序中,进行了一些裁减,只保留了数码管、按键、LED和PS2 部分。至于VGA 相对复杂,单独学习。其中涉及到PS2 相对其他外设比较陌生。所以得先做一些对PS2 协议的熟悉工作。那就看看在单片机上如何实现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 0x7C0xE0 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 LockLed 的状态;0xF3,主机在这条命令后跟随发送一个字节参数定义键盘机打的速率和延时;0xF4,用于当主机发送0xF5 禁止键盘后,重新使能键盘.
3 PS/2 键盘与单片机的连接电路
PS/2 键盘与AT89C51 单片机的连接方式如图4 所示.P1.0 接PS/2 数据线; 3.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 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
}
单片机由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 多媒体按键键盘时,只需要将键表中相应处修改即可,如ACPIPower 按键通码为0xE0 0x37,修改kbe0_plain_map[0x37]=KB_ACPI_PWR 即可.
特殊按键Pause 使用单独程序处理,如果接收到0xE1 就转入这段程序.而PrintScreen 键则将其看作是两个通码分别为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.0cps),接收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 键盘的应用也有借鉴意义
笔记(完整篇)请见附件!
新建 Microsoft Word 文档4.pdf
(403.51 KB, 下载次数: 108)
|
|