Arm的键盘驱动实验
一、实验目的
1. 学习键盘驱动原理。
2. 掌握通过CPU 的I/O 扩展键盘的方法。
二、实验内容
通过ARM 的rPDATC(低四位)和rPDATE(4~7 位)扩展4×4 的键盘,编程实现键
盘的驱动,通过按键可以在超级终端上和液晶屏上显示相应的键值(不带操作系统的实验仅
要求在超级终端上显示)。
三、预备知识
1. 掌握在ARM SDT 2.5 集成开发环境中编写和调试程序的基本过程。
2. 会使用Source Insight 3 编辑C 语言源程序。
3. 了解ARM 应用程序的框架结构。
4. 了解UC/OS-II 多任务的原理。
四、实验设备及工具
硬件:ARM 嵌入式开发板、用于ARM7TDMI 的JTAG 仿真器、PC 机Pentumn100 以
上。
软件:PC 机操作系统win98、ARM SDT 2.51 集成开发环境、仿真器驱动程序、Source
Insight 3.0
五、实验原理
实现键盘有两种方案:一是采用现有的一些芯片实现键盘扫描;再就是用软件实现键盘
扫描。作为一个嵌入系统设计人员,总是会关心产品成本。目前有很多芯片可以用来实现键
盘扫描,但是键盘扫描的软件实现方法有助于缩减一个系统的重复开发成本,且只需要很少
的CPU 开销。嵌入式控制器的功能很强,可以充分利用这一资源,这里就介绍一下软键盘
的实现方案。
图4-1 简单键盘电路
通常在一个键盘中使用了一个瞬时接触开关,并且用如图4-1 所示的简单电路,微处理
器可以容易地检测到闭合。当开关打开时,通过处理器的I/O 口的一个上拉电阻提供逻辑1;
当开关闭合时,处理器的I/O 口的输入将被拉低到逻辑0。可遗憾的是,开关并不完善,因
为当它们被按下或者被释放时,并不能够产生一个明确的1 或者0。尽管触点可能看起来稳
定而且很快地闭合,但与微处理器快速的运行速度相比,这种动作是比较慢的。当触点闭合
时,其弹起就像一个球。弹起效果将产生如图4-2 所示的好几个脉冲。弹起的持续时间通常
将维持在5ms∼30ms 之间。如果需要多个键,则可以将每个开关连接到微处理器上它自己的
输入端口。然而,当开关的数目增加时,这种方法将很快使用完所有的输入端口。
图4-2 按键抖动
键盘上排列这些开关最有效的方法(当需要5 个以上的键时)就形成了一个如图4-3 所示的
二维矩阵。当行和列的数目一样多时,也就是方型的矩阵,将产生一个最优化的布列方式(I/O
端被连接的时候)。一个瞬时接触开关(按钮)放置在每一行与每一列的交叉点。矩阵所需
的键的数目显然根据应用程序而不同。每一行由一个输出端口的一位驱动,而每一列由一个
电阻器上拉且供给输入端口一位。
图4-3 矩阵键盘
键盘扫描过程就是让微处理器按有规律的时间间隔查看键盘矩阵,以确定是否有键被按
下。一旦处理器判定有一个键按下,键盘扫描软件将过滤掉抖动并且判定哪个键被按下。每
个键被分配一个称为扫描码的唯一标识符。应用程序利用该扫描码,根据按下的键来判定应
该采取什么行动。换句话说,扫描码将告诉应用程序按下哪个键。
某一时刻按下多个键(意外地或者故意地)的情况被称为转滚。能够正确识别一个新键
被按下(即使n-1 个键已经被按下)的任何算法被称为具有n 键转滚的能力。本章提出的矩
阵键盘系统设计,在这种系统中用户输入可能发生相继按键。这些系统通常不需要具有像终
端或者计算机系统上的键盘的全部特征那样的键盘。
键盘扫描算法:
在初始化阶段,所有的行(输出端口)被强行设置为低电平。在没有任何键按下时。所
有的列(输入端口)将读到高电平。任何键的闭合将造成其中的一列变为低电平。为了查看
是否有一个键已经被按下,微处理器仅仅需要查看任一列的值是否变成低电平。一旦微处理器检测到有键被按下,就需要找出是哪一个键。过程很简单,微处理器只需在其中一列上输
出一个低电平。如果它在输入端口上发现一个0 值,该微处理器就知道在所选择行上产生了
键的闭合。相反,如果输入端口全是高电平,则被按下的键就不在那一行,微处理器将选择
下一行,并重复该过程直到它发现了该行为止。一旦该行被识别出来,则被按下键的具体的
列可以通过锁定输入端口上唯一的低电位来确定。微处理器执行这些步骤所需要的时间与最
小的状态闭合时间相比是非常短的,因此它假设该键在这个时间间隔中将维持按下的状态。
比如:当发现某列变为低电平时,此时微处理器仅在某一行上输出低电平,再查看列的状态,
如果此时在输入端口上发现了一个0,则就可以断定就是此行上的键按下了,反之,如果输
入端口上全为1,则就不是这一行上按下了键。根据第一步和第二步中得到的值,便可以得
到相应的扫描码。比如,第一步中行全为零时列输入B1 为零,当将输出的第二行B2 置为
零时,如果此时的列输入B1 仍为零,则可得到扫描码为×××。
为了过滤回弹的问题,微处理器以规定的时间间隔对键盘进行采样,这个间隔通常在
20ms~100ms 之间(被称为去除回弹周期),它主要取决于所使用开关的回弹特征。
另外一个的特点就是所谓的自动重复。自动重复允许一个键的扫描码可以重复地被插入
缓冲区,只要按着这个键或者直到缓冲区满为止。自动重复功能非常有用的,当你打算递增
或者递减一个参数(也就是一个变量)值时,不必重复按下或者释放该键。如果该键被按住
的时间超过自动重复的延迟时间,这个按键将被重复的确认按下。
在我们的开发板上,键盘的接法如下图所示:
本次实验实现的是4×4 的键盘扫描。分别将每一列置零,如果这时有键按下,则对应
的行将为低电平,将4 次得到的结果放到一个16 位变量中,该变量的哪一位为零则对应一
个按键,如果没有键按下则该变量的值为0xffff。做一个数组存储键盘的映射码,这样方便
修改按键对应的值。为了过滤回弹的问题,微处理器以规定的时间间隔对键盘进行采样,这
个间隔通常在20ms~100ms 之间(被称为去除回弹周期),它主要取决于所使用开关的回弹
特征。本次实验中取为50ms。另个一个的特点就是所谓的自动重复。自动重复允许一个键
的扫描码可以重复地被插入缓冲区,只要按着这个键或者直到缓冲区满为止。本次实验的自
动重复按键的第一次和第二次之间的延时为1 秒钟,以后的重复速度为50 毫秒。
六、实验步骤
1. 不带操作系统的键盘驱动的实现
① 新建工程,将44b.h 和Option.h 两个头文件加入主文件中(这两个文件中定义
了ARM 的寄存器等信息),将Uhal.h 文件中的函数以头文件的方式加入主文
件中(因为我们要将键盘扫描码发送到串口,在超级终端中显示出来)。
② 在主函数中定义键盘映射表:
unsigned char MykeyBoard_KeyMap[]={1,4,7,10,2,5,8,0,3,6,9,11,12,13,14,15};
//10-退格,11-*/.,12-↑,13-↓,14-确定,15-取消
unsigned short MyGetScanKey();
通过查找键盘映射表来确定键盘扫描码对应的按键值。
③ 定义键盘扫描函数,此函数主要实现返回键盘扫描码。
unsigned short MyGetScanKey()
{
unsigned short key;
unsigned int i,temp;
for(i=1;i<0x10;i<<=1)
{
rPDATE|=0xf0;初始化端口
rPDATE&=~(i<<4);//向列所在端口分别发送1110,1101,1011,0111
key<<=4;//右移四位
Delay(10);//延时,等待响应
temp=rPDATC;//读各行状态值
key|=(temp&0xf);//将四次所得结果保存起来
}
return key;
}
④ 定义键盘驱动函数,此函数调用键盘扫描函数,通过判断键盘扫描函数返回的
扫描码,实现去抖动和自动重复功能,并将其转化为对应键的键值。
unsigned int MyGetKey()
{
int i;
unsigned short key,tempkey=1;
static unsigned short oldkey=0xffff;
static unsigned char keystatus=0;
unsigned char keycnt=0;
while(1)
{
key=0xffff;
while(1)
{
key=MyGetScanKey();
if(key!=0xffff)//有键按下
break;
oldkey=0xffff;
}
Delay(500);//去抖动
if(key!=MyGetScanKey())
continue;//如果两次的键制不同,重新扫描
if(oldkey!=key)
keystatus=0;
if(keystatus==0) //第一次按下此键
{
keycnt=0;
keystatus=1;
}
else if(keystatus==1) //第二次重复此键
{
keycnt++;
if(keycnt==20)
keystatus=2;
else
continue;
}
oldkey=key;
break;
}
for(i=0;i<16;i++)//查找按键,不包括功能键
{
if((key&tempkey)==0)
break;
tempkey<<=1;
}
return MykeyBoard_KeyMap[i];
}
⑤ 编写主程序。将得到的键值转化为ASCII 码发送到串口。
int Main(int argc, char **argv)
{
unsigned int key;
unsigned int tempkey=0;
Uart_SendByte(0,0xa);//向串口发送0xa,表示换行
Uart_SendByte(0,0xd);//回车
for (;;)
{
key=MyGetKey();
key&=0x000f;
if(key>9)
{
Uart_SendByte(0,0x31);
tempkey=key-10;
Uart_SendByte(0,tempkey|=0x0030);
}
else
Uart_SendByte(0,key|0x0030);
Uart_SendByte(0,0x2c);
}
return 0;
}
2.带操作系统的键盘驱动的实现。
① 在主函数中定义键盘映射表,定义键盘扫描函数,定义键盘驱动函数。
② 定义键盘响应函数,将得到的键值在液晶屏上显示。
void onKey(int nkey, int fnkey)//键盘响应函数
{
char temp[3];//转换成ASC-II 的键值数组
if(nkey>9)
{
temp[0]=0x31;
temp[1]=(nkey-10)|0x30;
temp[2]=0;
}
else
{
temp[0]=nkey+0x30;
temp[1]=0;
}
LCD_printf(temp);//在液晶屏上显示键值
LCD_printf("\n");
}
③ 定义键盘扫描任务。
OS_STK My_Key_Scan_Stack[STACKSIZE]={0, }; //定义键盘扫描任务的堆栈大小
void My_Key_Scan_Task(void *Id); //定义键盘扫描任务
#define MyKey_Scan_Task_Prio 58 //定义键盘扫描任务的优先级
OSTaskCreate(My_Key_Scan_Task,(void*)0,(OS_STK*)&My_Key_Scan_Stack[STACKSIZE-1],
MyKey_Scan_Task_Prio );//在主函数中创建键盘扫描任务
void My_Key_Scan_Task(void *Id)//键盘扫描任务
{
U32 key;
u32 tempkey=0;
POSMSG pmsg;//创建消息
Uart_Printf("begin key task \n");
for (;;)
{
key=MyGetKey();
key&=0x000f;
if(key>9)
{
Uart_SendByte(0,0x31);
tempkey=key-10;
Uart_SendByte(0,tempkey|=0x0030);
}
else
Uart_SendByte(0,key|0x0030);
Uart_Printf(",");
pmsg=OSCreateMessage(NULL, OSM_KEY,key,key);//创建键盘消息
if(pmsg)
SendMessage(pmsg);//发送键盘消息
}
}
④ 定义主任务
OS_STK Main_Stack[STACKSIZE*8]={0, };//定义主任务的堆栈大小
void Main_Task(void *Id); //定义主任务
#define Main_Task_Prio 12 //定义主任务的优先级
OSTaskCreate(Main_Task,(void*)0,(OS_STK*)&Main_Stack[STACKSIZE*8-1],
Main_Task_Prio);//在主函数重创建主任务
void Main_Task(void *Id) //主任务
{
POSMSG pMsg=0;//创建消息
LCD_ChangeMode(DspTxtMode);//将液晶屏设为文本显示模式
LCD_Cls();//清屏
for(;;)
{
pMsg=WaitMessage(0); //等待消息
switch(pMsg->Message)
{
case OSM_KEY:
onKey(pMsg->WParam,pMsg->LParam);
break;
Delay(200);
}
DeleteMessage(pMsg);//删除消息,释放资源
}
}
注意:
由于本操作系统已封装了键盘驱动程序,要使用我们自己的键盘驱动程序需要在
OSAddTask.h 中将“//#define OS_KeyBoard_Scan_Task”去掉,这样才不会产生重复定义的
错误。
七、思考题
1. 键盘扫描的原理是什么?
2. 用其它方法实现键盘驱动。
评论
查看更多