|
事无巨细,独立键盘
经过多天的等待,元器件终于补齐了,虽然焊接的过程中出现了非常悲剧的事情,但最终我的板子得以“残”貌示人,就傻傻的高兴一下好了。
言归正传,今天写写独立按键的原理及编程方法。
按键是什么东西,我想这个就不必由我向各位阐述了。嗯,如你所见,按键种类繁多,功能有简有繁,极大的充斥着我们的生活。但是无论如何,所有的按键其实都有一个原型,来源于同一种原理,所有的按键无论多复杂,多华丽,都是从这样一个原型发展而成的。好比你就算长的再帅,你也是只猩猩变来的,呵呵。我们平日所见到的绝大部分的按键,其实都可以归类为一种,叫“接触式按键”。下图为一个典型的接触式按键(又称轻触开关)的实物图一:
需要特别说明的是,这里说的“接触”,是指机械层面上的接触,而不是感光或者某些特殊涂层(比如触摸屏)一类的接触。所以,按键的工作特性其实是一种机械特性,下文会详细说明。
现在来看一个最简单的按键的原理图(图二),
如上所见,一个按键的左右端分别是IO_1和IO_2,显然,在按键按下之前IO_1和IO_2是没有电气连接的,当按键按下之后,IO_1和IO_2就有了电气连接。但是,市面上所能买到的接触式按键,绝大部分并不是上图那样只有两个引脚的,而是如图一所示有四个引脚,其中有什么悬殊呢,来看(图三):
如上图,请对照图一想象,1、2、3、4分别对应按键的四个引脚,其中蓝色的线表示按键未被按下之时的状态,我成为初始状态,它是不导通的;而绿色的线是却永久导通的。各位明白了么,其实是两个相同的结构连在一起了。我们只要将需要按键开关作用的线路分别接在1、3和2、4的任意取一组合,概括起来就是(1,2)、(1,4)、(3,2)、(3,4)四种组合,都可以起到我们预期的开关作用。
相信以上说明使大家对按键的工作原理有了个比较清晰的认识了,现在来说说一个小知识。先看下图(图四):
首先说明的是,上图的连法是不允许的,因为当按键按下之后,电源和地短接,会将导线直接烧毁。但是此处用作特例,假设导线不会烧毁。现在来提出一个问题,当按键按下以后,请问如果这时用万用表测量导线上任何一处的电压,得到的结果是VCC还是GND的点压呢?
答案是:GND,即表示测出的电压为0V。为什么呢,因为导线上,对于两端的电平是一种类似于程序语言逻辑运算里面的“与”,即对于导线两端:有零即为零,只有全为一是才为一。理解了这点,按键的工作前提就有了。
接下来讲按键工作的一个最重要的特性:抖动。且看下两图(图五、六):
还以图四为例,按键未按下之前,图四按键左端的导线因为连在VCC上而显示高电平,右端显示低电平,按键按下后,按键闭合,整个导线都显示低电平,然后按键松开,又回到按键按下之前的点评状态。如果只考察按键左端的电平变化,应该是上左图中所显示的一个负脉冲波形。但是,实际上,正确的波形应该是上右图。相比于左图,大家都看到了在高低电平直接有一段锯齿一样的波形,这就是所谓的按键抖动。
为什么会有按键抖动呢,原因很简单,接触式按键是靠机械的接触来实现开关作用的。这种接触方式就注定了它要经历一个“接触不稳定——正在稳定中——彻底稳定”的一种过程。就好比你用手抓紧一颗石头,即使你一开始就很用力的握紧,也不可能马上就达到最紧的状态,也要经历一个从握住到最紧握的过程。那么在这个过程里,接触式按键就处于一种徘徊在“闭合”与“断开”两者之间的状态。体现在电路中,就是在一小段时间内有非常多的“按下——抬起”动作。而这段抖动的时间,大概是10~20毫秒,依不同的环境条件而定。
显而易见,按键的这种抖动是一种负面的存在,因为我们按下一次按键的时候,只希望发生一次按键的动作,得到一次按键的效果。但是抖动明显会把这种期望打破。所以我们怎么来避免这种影响呢。对了,延时,用延时的办法,在按键产生抖动的期间调用一个延时函数,来忽略对按键的读取,延时结束后,抖动也结束了,这时就可以得到一个稳定的按键电平。
有必要看看CEPARK AVR开发板的独立按键部分电路原理:
也是很简单的一个原理,K17~K20四个独立按键分别接在PB0,PD2,PD6,PD7四个IO口,按键的另外一段接地GND。在此说说什么是独立按键:即一个按键占用单独的一个I/O口;
所以,使用这四个按键,即是分别扫描PB0,PD2,PD6,PD7四个端口,如果扫描到某端口显示低电平,则表示该按键被按下了。以下附上源程序,功能是扫描开发板上的四个独立按键,并将扫描结果显示在数码管上。数码管显示函数部分的详细说明请在《事无巨细,数码管》一文查看,在此节省篇幅不做重复说明。
#include
#include
#define uint unsigned int
#define uchar unsigned char
unsigned char const LedData[]=
{0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xF8,
0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e};
unsigned char const LedPos[]=
{0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
uchar n,x,key;
void HC595send(uchar x);
void init(void);
void HC595shift(void);
void HC595store(void);
void display(uchar pos,uchar dat);
uchar keyscan(void);
int main(void)
{
while(1)
{
x=keyscan(); //按键扫描,返回值存于全局变量X中
if(x) //当有按键按下时显示
{
init(); //设置数码管控制端口
display(0,x); //显示扫描值
}
}
}
void init(void)
{
PORTB=0x00;//B口全部低电平
DDRB=0xff;//B口全部输出
}
uchar keyscan(void) //键盘扫描函数,返回值对应按键
{
uchar temp=0,key_value=0; //定义局部变量
PORTB|=(1<<0);
DDRB&=~(1<<0); //设置PB0为输入口,不使用上拉电阻,高阻态
temp=PINB; //读PB口电平
key_value=(temp&0x01); //保留最低位,屏蔽其他端口干扰
if(key_value==0) //第一次判断是否有按键按下
{
_delay_ms(20); //有按键按下,延时消抖
temp=PINB; //读PB口电平
key_value=(temp&0x01);//保留最低位,屏蔽其他端口干扰
if(key_value==0) //再次判断是否有按键按下
{
key=1; //消抖之后再次确认有按键按下,则表示按键确实按下
}
}
while(!key_value) //送手检测
{
temp=PINB;
key_value=(temp&0x01);
}
/*以下扫描方式流程与上类同*/
PORTD|=(1<<2);
DDRD&=~(1<<2);
temp=PIND;
key_value=(temp&0x04);
if(key_value==0)
{
_delay_ms(20);
temp=PIND;
key_value=(temp&0x04);
if(key_value==0)
{
key=2;
}
}
while(!key_value)
{
temp=PIND;
key_value=(temp&0x04);
}
PORTD|=(1<<6);
DDRD&=~(1<<6);
temp=PIND;
key_value=(temp&0x40);
if(key_value==0)
{
_delay_ms(20);
temp=PIND;
key_value=(temp&0x40);
if(key_value==0)
{
key=3;
}
}
while(!key_value)
{
temp=PIND;
key_value=(temp&0x40);
}
PORTD|=(1<<7);
DDRD&=~(1<<7);
temp=PIND;
key_value=(temp&0x80);
if(key_value==0)
{
_delay_ms(20);
temp=PIND;
key_value=(temp&0x80);
if(key_value==0)
{
key=4;
}
}
while(!key_value)
{
temp=PIND;
key_value=(temp&0x80);
}
/*以上扫描方式流程与上类同*/
return key; //返回按键值
}
void HC595send(uchar x)
{
uchar n,temp;
for(n=0;n<8;n++)
{
temp=x&0x80;
if(temp!=0)
{
PORTB|=(1<< PB5);
HC595shift();
}
else
{
PORTB&=~(1<< PB5);
HC595shift();
}
x<<=1;
}
}
void HC595store(void)
{
PORTB|=(1<< PB4);
PORTB&=~(1<< PB4);
}
void HC595shift(void)
{
PORTB|=(1<< PB7);
PORTB&=~(1<< PB7);
}
void display(uchar pos,uchar dat)
{
HC595send(LedPos[pos]);
HC595send( LedData[dat]);
HC595store();
}
下面还是要挑几个关键的地方讲讲:
1、在任意一个函数里(包括main函数!!!)定义的变量都是局部变量,局部变量不似全局变量初始化的时候默认为0,而是随机值,所以未免产生不必要的问题,切记一定要在定义局部变量的时候赋初值。
2、把AVR的IO口置为输入状态的时候,最好把上拉电阻使能,可以起到减少干扰的作用。
3、注意初始化DDRx和PORTx这两个寄存器时,严重建议先写PORTx后写DDRx。原因是AVR单片机初始状态是DDRx和PORTx都为0,如果先写DDRx,当设置为输出方向时,因为PORTx为0,则这时单片机引脚会出现我们不一定想要的低电平。新手们一定要注意这点。
4、和51单片机不同,读取AVR引脚电平是通过读取PINx来读取的。注意在此键盘扫描程序中,按键只占一位IO口,但因为GCC AVR不支持单位操作所以我们读回来是8位数据。所以要用一个“按位与”的语句将不需要的位屏蔽掉,只留下按键所在位,避免干扰。
5、按键不仅有按下时的抖动,还有松手时的抖动,所以要加上松手检测环节。但是因为松手之后程序会继续往下执行,所以松手抖动的时间无法持续到下一次的此个按键的第二次按键判断。所以松手抖动可以不那么严格。
6、按键扫描程序,无论是有返回按键值还是不返回按键值,都有其存在的道理,大家要注意揣摩。此外延时消抖并不是唯一的也不是最理想的消抖方法。详情请关注论坛的51按键活动。
7、注意AVR单片机的DDRx寄存器的延时特性,即在改变DDRx的设置之后的1个时钟周期内,PINx的值是不稳定的,要过了这个不稳定期再读取电平值。
8、论坛的板子,最右边有一排单片机端口引出的排针,注意如果在扫描按键的时候你的手通过这些排针碰到相应的按键IO,有可能将干扰引入。
希望能给有需要的人一点帮助。
[ 本帖最后由 losingamong 于 2010-3-13 10:46 编辑 ]
|
|