Chinaunix首页 | 论坛 | 博客
  • 博客访问: 485207
  • 博文数量: 164
  • 博客积分: 4024
  • 博客等级: 上校
  • 技术积分: 1580
  • 用 户 组: 普通用户
  • 注册时间: 2009-10-10 16:27
文章分类

全部博文(164)

文章存档

2011年(1)

2010年(108)

2009年(55)

我的朋友

分类: 嵌入式

2009-11-02 09:00:01

作者魏小龙 南航 MSP430 选修课试用教材 第 71 页
 
 
1 按键的工作原理
在单片机设计中常用轻触按键作为输入设备—键盘的单元电路。它一般结构是由两个电极与一个弹簧金属片构成的,如右图所示。当金属弹簧片上的 K按键 K 按下时,两个电极A 与B 被连通。但实际使用并非如此 A B简单,因为单片机的运行速度相对于操作者按下按键的速度来是太快了,所以就不得不考虑更多的一些细节问题。
先看看我们按按键的细节过程。这个细节主要是按下按键的前后都有抖动!如果按键的A 端接地,B 端接上拉电阻,则平时按键的B 端为高电平,当按键按下时为低电平,松开后又是高电平,这是理论值。而实际上呢。
如果将按键的 B 端信号送到MCU,系统会认为有几个低电平,按了几次按键呢。因为MCU 认为低电平是按键按下,而抖动过程有很多低电平,同时还有不短的时间,一般在10毫秒左右。那么怎么解决呢,常用的有3 种办法:使用R-S 触发器构成消抖动开关以消抖动;使用电阻与电容构成积分器将抖动滤除;使用软件延时的办法消除抖动。前两种方法使用硬件没什么好说的,最后一种使用的是软件的方法,怎么回事?其实很简单:当MCU 得知按键的B 端出现低电平时,就知道可能有按键按下了,于是等待10 毫秒,10 毫秒之后再检测按键的B 端,如果还是低电平,则就一般的机械按键而言,已经是处于稳定期了,按键的抖动被消除了;如果10 毫秒之后按键的B 端没有低电平了,则说明是干扰信号,而非按键按下。
 
 
2 键盘程序的一般编写方法
键盘是由若干上述的独立按键按一定的规则组合而成的。也就是说键盘的基本元素是按键,那么消除按键的抖动是必须的。键盘是由若干按键构成的,究竟是哪一个按下了呢,这就是通过判键得到键值。MCU 知道了是哪一个按键按下了之后是不是就该退出呢?如果这时退出,而按键还没有松开,则MCU 又知道了有按键按下,又去消除抖动,再判断是哪个按键按下,如果还没有松开,则又被认为有按键按下,…….这下惨了,本来你按了一次按键,而系统则认为你按了很多次按键。所以得到键值之后,还有一件事情就是等待按下按键的松开(注
意一点:如果系统中使用了看门狗,则在这里请不断地清空看门狗,因为如果使用者一直很长时间按着键,则看门狗超时、系统复位)。综合起来,一般的键盘程序有如下三个步骤:
A 消除按键抖动(如果使用硬件,则可略);
B 判断是哪个按键按下,识别键码;
C 等待按键松开。
 
 
3 通用独立按键式键盘设计
在系统中需要少量按键时,可使用按键与单片机的 I/O 口线直接连接的方法构成。我们平常使用的电子表、计步机等,它们体积都很小巧,不可能使用很多按键构成输入部分,一般有4 个左右的按键。在这种情况下,就使用独立按键式键盘。
在软件的编写上,可采用查询方式,也可采用中断方式。因为MSP430 的P0、P1、P2等三个8 位端口都有中断能力,建议读者使用中断方式。键盘程序主要是按上述的三个步骤进行编写。在主程序中必须设置P1 口使之能进入中断。
主程序:(提供端口设置,使之能进入中断)
unsigned char keybuf; // 全局变量,键值缓存
……
WDTCTL = WDTPW + WDTHOLD; // 关闭看门狗
P1DIR = 0x0F; // 设置P1.4 -P1.7 为输入方向
P1IE = 0xf0; // 设置P1.4 -P1.7 可以中断
P1IES = 0xf0; // 设置P1.4 -P1.7 为下降沿中断
_BIS_SR(LPM3_bits + GIE); // 进入最低功耗睡眠,打开总总断开关
……
是否有按键按下的判断:由于连接在键盘上的端口都有上拉电阻,所以平常这些端口为高电平,按下之后为低电平,所以在按下之后可以判断P1IN 的内容中连接在键盘上的那些位,应该为“0”。所以下面的程序如果返回0xf0 则表示没有按键按下,如果返回不是0xf0 则表示有按键按下。
unsigned char p1keyj(void) // 判键子程序
{unsigned char x;
x=(P1IN&0Xf0); // P14--P17 接有按键
return(x); // 有按键返回非全 1
}
下面为键值的查找。下面的思路为一个一个地查找。
unsigned char keycode() // 找哪个按键被按下,查键值子程序
{
unsigned char x;
if((P1IN&0xf0)= = 0x80) // 是否第一个按键
then x=0;
else
if((P1IN&0xf0)= = 0x40) // 是否第二个按键
then x=1;
else
if((P1IN&0xf0)= = 0x20) // 是否第三个按键
then x=2;
else
if((P1IN&0xf0)= = 0x20) // 是否第四个按键
x=3;
return(x);
}
中断服务程序:
#pragma vector=PORT1_VECTOR
__interrupt void p1int(void)
{ //端口1 的中断服务程序
while(p1keyj()!=0xf0) //没有按键按下,返回全1――0xf0
{
delay(500); //延时消除抖动
while(p1keyj()!=0xf0)
{
keybuf = keycode();//确信有按键按下,找按键得键值,送到全局变量keybuf
while(p1keyj()= =0) //等待按键松开
; //做对应键盘的事务
}
}
}
以上程序使用中断方式,使用查询方式的程序差不多,这里略,请读者自己编写。
汇编主程序:
……
BIC.B # 0F0H, &P1DIR ;P1.4 -P1.7 为输入模式
BIS.B #0F0H, &P1IE ;P1.4 -P1.7 中断使能
BIS.B # 0F0H, &P1IES ;P1.4 -P1.7 下降沿触发中断
EINT ;总中断使能
……
中断服务程序: ;出口参数:按键键值在R5 中
P1KEY3 CALL #KEYJ3 ;判断是否有按键
JNC KEYEND ;没有则退出
CALL #DELAY10MS ;如有,则延时10 毫秒 消抖动
CALL #KEYJ3 ;再判键
JNC KEYEND ;如没有按下则退出
CALL #KEYCODE3 ;如有,则调认键程序得到键值
PUSH R5 ;保护键值
KEYLOOP CALL #KEYJ3 ;等待按键松开
JC KEYLOOP ;没有松开,则继续等待
POP R5 ;按键松开之后,恢复键值
KEYEND RETI
KEYJ3 BIT.B #07H, &P1IN ;判断有无按键按下,如果有,则C=1
RET ;如果没有按键按下,则C=0
KEYCODE BIT.B #BIT4, &P1IN ;判断3 个按键中是哪一个被按下
JNC K2
MOV #0, R5 ;如果是接到P1.4 的按键,则输出R5=0
RET
K2 BIT.B #BIT5, &P1IN
JNC K3
MOV #1, R5 ;如果是接到P1.5 的按键,则输出R5=1
RET
K3 BIT.B #BIT6, &P1IN
JNC K4
MOV #2, R5 ;如果是接到P1.6 的按键,则输出R5=2
K4 BIT.B #BIT7, &P1IN
JNC K5
MOV #3, R5 ;如果是接到P1.7 的按键,则输出R5=3
K5 RET
 
 
2. 6 列扫描式式键盘原理与应用
如果应用系统需要较多的按键呢,采用独立式键盘的结构则不能满足需要。比如需16 个按键,则独立式键盘将花费系统系统资源为16 条I/O 口线,显然是不科学的。这里使用行列扫描的方法实现键盘接口(也被叫作矩阵键盘),则可使用少量的I/O 口线连接较多的按键。图2.6.1 为通过MSP430 的P1 口接的4*4=16 个按键(编号为0~15)构成的行列式扫描键盘。
下面来分析如何在行列式扫描键盘上实现键盘的三个步骤:判键消抖动,键码识别,等待按下按键的松开
 
A 判断有无按键按下的判键
在图 2.6.1 中,P1 口的8 条I/O 口线被分为4 条行线P1.0~P1.3,4 条列线P1.4~P1.7,其中列线分别由电阻上拉到VCC。在行线与列线的每一个交界处有个按键,按键的两端A 与B分别接在行线与列线上。如果有按键按下,则与之相接的行线与列线被连通。在检测是否有按键按下时,先让四条行线P1.0~P1.3 输出低电平,读列线P1.4~P1.7,如果有按键按下,则列线读进来的数据为非“1”;如果没有按键按下,则因所有的列线被上拉,读入MCU 的数据为“1”。由此即可判断是否有按键被按下。相应程序如下。
汇编程序:
KEYJUDGE MOV.B #0FH,&P1DIR
MOV.B #00H,&P1OUT
BIT.B #0F0H,&P1IN ; 读四根列线是否有低,是否有按健
RET ; 若有,C=1
C语言程序:
unsigned char keyj(void)
{unsigned char x;
P1DIR=0x0f;
P1OUT=0x0; //键盘硬件:P10--P13 为行线,最上面一根为P10
x=(P1IN&0XF0); // P14--P17 为列线,
return(x); // 无按键,返回 0xf0; 有按键返回非 1
}
按键抖动的消除同样是使用软件延时的办法,当检测到有按键按下之后,等待10 毫秒在检测是否有按键被按下,如果这时有按下的键,则已经是键的稳定期。具体的程序请看稍后的键盘子程序。
B 按键识别,得到键码
对于行列式矩阵键盘常使用扫描的方法识别按键。在前面判键部分已经讲到,通过让四条行线P1.0~P1.3 输出低电平,读列线P1.4~P1.7 的办法来得知是否有按键被按下。那么可以用同样的办法来确认究竟是哪一个按键被按下。
假定图 2.5.1 中的15 号键被按下,那么下面的办法将能找到按下的按键。
(1) 输出P1.0 为低电平,其余P1.1~P1.7 都是高电平;
(2) 读入列线,如果P1.4 为低电平,则说明“K12”号键被按下,因为没有被按下,所以为高(被上拉);再测试P1.5,看是否为低电平;……直到测试完P1.7;
(3) 然后再输出P1.1 为低电平,其余P1.2~P1.7、P1.0 都是高电平;
(4) 读入列线,如果P1.4 为低电平,则说明“K8”号键被按下,因为没有被按下,所以为高(被上拉);再测试P1.5,看是否为低电平;……直到测试完P1.7;
(5) 输出P1.2 为低,……
(6) 读列线,……
(7) 输出P1.3 为低电平…….
(8) 这时,读列线,就会发现P1.7 为低电平了,为什么被上拉的I/O 口线会是低电平呢,因为它与低电平输出的P1.3 连在了一起!由此就找到了被按下的按键连接在P1.3 与P1.7 上。这种方法常被称为扫描法,因为行输出是一个0,一个0 地输出,就象电视机的扫描线,一行一行地扫一样。
上面所讲述的办法找到了被按下按键的确切位置,但识别按键最终要送出一个表示按键位置的键值。究竟给一个什么数据来表示该位置上的按键呢。一般的方法是将输出的行扫描码与读入的列值组合起来,就是表示被按下按键位置的数据,比如第15 号按键被按下,则行扫描码为08H,而读入的列数据为08H(P1 口的高4 位),所以15 号按键的键值为“88H”。同样9 号键的键值为“42H”。很明显,这样的键值数据太疏松了,跨度很大,这样会给使用键盘的其他程序带来很大的不便。
4 条行线,4 条列线连接了16 个按键,如果能让所有16 个按键的编码为0~15,与图中所给的按键编号一样,那是最理想的。我们发现每一行线都通过4 个按键与4 条列线相连接,能不能给第一条行线上的4 个按键编码为0~3;后一条行线上的4 个按键为前一条行线上对应的按键键值加4 呢?答案是肯定的。先看如下的程序框图(图2.6.2)。
行内相邻两键键值为加 1 递增,行间每列上两相邻两键为加4 递增。这样便实现了按键键值的自然顺序编码。汇编程序如下。
KEYCODE MOV.B #0,&P5DIR ;关闭显示
MOV.B #0FH,&P1DIR ;低4 位作为扫描线行输出,高4 位作为列线读入
MOV #0,R9
MOV #1,R8
KEYCODELOOP MOV.B R8,&P1OUT ;R8 为扫描信号的输出
BIT.B #10H,&P1IN
JC KEYCODE1 ;测试P1.4
BIT.B #20H,&P1IN
JC KEYCODE2 ;测试P1.5
BIT.B #40H,&P1IN
JC KEYCODE3 ;测试P1.6
BIT.B #80H,&P1IN
JC KEYCODE4 ;测试P1.7
RLA.B R8 ;改变扫描码
ADD.B #4,R9 ;键值的行间改变
CMP.B #12,R9 ;四根行线扫描完了吗
JNZ KEYCODELOOP
RET
KEYCODE1 ADD #0,R9 ;键值的行内改变
RET
KEYCODE2 ADD #1,R9
RET
KEYCODE3 ADD #2,R9
RET
KEYCODE4 ADD #3,R9
RET
C语言程序如下:
unsigned char key(void) //此程序键盘为4 行*3 列,12 个按键
{
unsigned char x=0xff;
P1DIR=0X0F;
P1OUT=0X01; //扫描第一行
if((P1IN&0X70)==0X10) //如果第一行、第一根列线上有键按下,则键值为0
x=0;
else
if((P1IN&0X70)==0X20) //如果第一行、第二根列线上有键按下,则键值为1
x=1;
else
if((P1IN&0X70)==0x40) // 如果第一行、第三根列线上有键按
x=2; //下,则键值为2
else
{
P1OUT=0X2; //扫描第二行
if((P1IN&0X70)==0X10) //如果第二行、第一根列线上有键按下,则键值为3
x=3;
else
if((P1IN&0X70)==0X20) //如果第二行、第二根列线上有键按
x=4; //下,则键值为4
else
if((P1IN&0X70)==0x40) //如果第二行、第三根列线上有键按
x=5; //下,则键值为5
else
{
P1OUT=0X4; //扫描第三行,以下与上相同
if((P1IN&0X70)==0X10)
x=6;
else
if((P1IN&0X70)==0X20)
x=7;
else
if((P1IN&0X70)==0x40)
x=8;
else
{P1OUT=8; //扫描第四行
if((P1IN&0X70)==0X10)
x=9;
else
if((P1IN&0X70)==0X20)
x=10;
else
if((P1IN&0X70)==0x40)
x=11;
}
}
}
return(x);
}
 
C 等待按键松开
这与独立式键盘一样,反复调用判键子程序,直到判断结果为没有按键按下为止。程序略,与前相同。
D 完整的扫描式键盘程序
上面所讲述的只是键盘程序的一些详细细节与实用键盘子程序,完整的键盘程序则有很多中方式。图2.6.3 所示的键盘流程图是工作在查询方式的键盘程序,可以看出,采用此方式时,MCU 一直在查询有没有按键被按下,MCU 不能做其他的事情,只有等按了按键之后,才会有时间处理相应的事务。这种方式MCU 的效率肯定低下。在实际应用中,键盘只是输入部分。它的功能是送给MCU 用户的输入。所以为了提高MCU 的效率,所以为了提高MCU的效率,常使用中断的方式实现键盘输入。
中断方式的键盘实现又有两种形式:直接 I/O 口中断与定时器中断。它们的流程图分别见图2.6.4 与图2.6.5
两流程框图的差别在于,使用 I/O 口线中断方式有延时消除抖动的过程,而使用定时器中断查键盘则没有延时去抖动细节。因为定时器的定时时间就是消抖动的时间,比如10 毫秒。
首先设置两个标志位FF1、FF2,其中FF1 用于去抖动标志,FF2 用于按键识别完成标志。初始值都是0,表示没有去抖动与没有完成按键的识别。当进入中断时,如果FF1 为0,则没有消抖动,那么判断是否有键按下,如有,设置FF1=1,并退出程序,等待下次中断(等10 毫秒)。如果再次中断,则继续判断是否有键按下,如有,说明是真正的按键被按下,而且已是键盘的稳定期,调键码识别子程序得到键值,设置FF2=1,表示完成了按键的识别;如果没有按键被按下,则设置FF1=0,退出。如果这时再次中断,显然都是一次按键,FF2=1 同时表明没有松开按键,也就退出了。等到按键被松开时,设置FF1=0、FF2=0,为再次按键作准
备。
使用 I/O 口中断方式键盘程序如下(使用定时器中断方式键盘程序请读者自己编写):
P1KEY_INT CALL # KEYJUDGE ;判断是否有按键
JNC KEYEND ;没有则退出
CALL #DELAY10MS ;如有,则延时10 毫秒 消抖动
CALL # KEYJUDGE ;再判键
JNC KEYEND ;如没有按下则退出
CALL #KEYCODE ;如有,则调认键程序得到键值
PUSH R5 ;保护键值
KEYLOOP CALL # KEYJUDGE ;等待按键松开
JC KEYLOOP ;没有松开,则继续等待
POP R5 ;按键松开之后,恢复键值
KEYEND RETI
阅读(5287) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~