Chinaunix首页 | 论坛 | 博客
  • 博客访问: 117896
  • 博文数量: 29
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 242
  • 用 户 组: 普通用户
  • 注册时间: 2014-07-17 13:36
文章分类

全部博文(29)

文章存档

2015年(29)

我的朋友

分类: 嵌入式

2015-04-13 13:42:34

按键检测

一、轮询方式检测

如下图所示,为轮询方式的电路图:

P3.0为按键K0输入端,实现的效果为K0按下一次,发光二极管切换一次状态。
代码如下:

点击(此处)折叠或打开

  1. #include <reg52.h>

  2. sbit LED_0 = P1^0;
  3. sbit test_port0 = P3^0;
  4. void delay_ms(int ms)
  5. {
  6.     int j;
  7.     ++ms;
  8.     while(--ms>0)
  9.         for(j=110; j > 0; --j);
  10. }
  11. int main()
  12. {
  13.     //循环查询的方法检测P3^0是否按下
  14.     while(1)
  15.     {
  16.         test_port0 = 1;
  17.         while(test_port0);
  18.         delay_ms(20);
  19.         if(test_port0==0)
  20.         {
  21.             LED_0 = !LED_0;
  22.             while(!test_port0);
  23.         }
  24.     }
  25.     return 0;
  26. }

解释:在main中的while循环先将P3.0口置1,前面说过,P3口为准双向口,要先置1,然后输入。while(test_port0)检测P3.0口是否被拉低,如果拉低,延时20ms检测,因为人工按下按键,低电平保持20ms以上,20ms后,如果检测到此时还是为低电平,就改变led灯的状态,如果不是,说明刚才的低电平是由于扰动引起的;其中if语句中还有一个while循环语句,该语句的目的是等待用户将按键放开,如果不加这一句,用户长按按键K10时,单片机将会不断检测到用户按键按下。

 

二、中断方式检测

原理图与上述原理图类似,只是输入口变为P3.2,输出口变为P1.1


如下为中断检测的控制代码:

点击(此处)折叠或打开

  1. #include <reg52.h>

  2. sbit LED_1 = P1^1;
  3. sbit test_port = P3^2;
  4. void delay_ms(int ms)
  5. {
  6.     int j;
  7.     while(--ms>=0)
  8.         for(j=110;j>0;--j);
  9. }

  10. int main()
  11. {
  12.     //使能外部中断0
  13.     EA = 1;EX0 = 1;
  14.     //0--低电平触发 1--下降沿触发
  15.     IT0 = 1;
  16.     test_port = 1;
  17.     while(1);
  18.     return 0;
  19. }
  20. void ext_int_service(void)interrupt 0
  21. {
  22.     delay_ms(20);
  23.     if(test_port==0)LED_1=!LED_1;
  24. }

解释:因为希望通过外部中断0进行按键检测,当然要先开中断总开关EA和外部中断分开关EX0,所以有“EA=1;EX0=1;”;下一句为IT0定义中断的触发方式。

中断的触发方式有两种:低电平触发和下降沿触发。

低电平触发:单片机在每个机器周期检查中断源引脚线,检测到低电平,即置位中断请求标志,如果设定了中断服务程序,进入中断,硬件清空中断标志位。在这里显然是不适合的,因为我们需要的是按下的这个动作,所以选择下降沿触发。

下降沿触发:单片机在上一个机器周期检测到中断源引脚线为高电平,下一个机器周期检测到低电平,置位中断标志,进入中断;所以,高低电平信号必须保持1个机器周期以上,否则,可能不能检测下降沿。注意:进入到中断服务程序后,外部中断标志位由硬件清零。

 

P3.2写入1,说明为输入。While中可以写总体执行程序。

外部中断0的中断号为0,所以可以写出中断服务程序。

在服务程序中,和循环检测一样,先延时20ms,再重新检测,排除由于抖动造成的误判断。

 

Ok,貌似很完美?NO,这里存在两个问题:

1、  我们说过,单片机的执行过程是这样的,先检测到下降沿,置位中断标志,处理器发现中断标志被置位了,自动跳到服务程序。但是,考虑这么一种情况,由于抖动的存在,在第一次进入服务程序后,IE0被置0,在执行过程中,又来了一个下降沿(抖动引起),此时IE0又变为了1,因为两个优先级相同,而中断的执行有一个原则:正在进行的中断过程不能被新的同级或者低级的中断请求打断。所以,即使现在IE0变为1,,处理器先不管新产生的中断,执行完服务后,cpu发现,哎呀,还来了一个下降沿啊,所以又进入到中断服务程序。这种机制有点像中断缓存,但对于系统类型的中断源,只能缓存一个中断而已。

 

如果您不信的话,先设置一个全局变量用于计数,在中断服务程序中对该计数自增,在主程序的while循环中用LED数码管显示该全局变量,你会发现,有时候,按下一次,全局变量自增两次。但是,这种现象和开关有关,有的开关抖动小,可能看不出这种情况,那就认为创造这种情况,在服务中断中的延时变长为1s,在1s内按两次,你会发现,最后全局变量加了两次。

那怎么解决呢?好办,有一个原则,在进入中断时,关闭中断,出中断时,开启中断。如果关闭的是EA,就将所有的中断都屏蔽了,而关EX0,就仅仅关闭外部中断0。其实说中断时间过长,会造成中断丢失,是指在关闭中断的情况下。不关闭中断时,可以缓存一次中断,中断来了多次,也是要丢失的。

 

2、  还有一个问题:我们说中断服务程序的时间不能太长,用延时操作是万万不可的。所以,我们可以用定时器来设置时间,进行检测。代码如下:

点击(此处)折叠或打开

  1. #include <reg52.h>

  2. volatile int g_cnt = 0;
  3. sbit test_port = P3^2;
  4. sbit LED_1 = P1^1;

  5. int main()
  6. {
  7.     //定时器0 1ms进入一次中断,时钟周期为12MHz
  8.     TH0 = (65536-1000)>>8;TL0=(65536-1000)&0xff;
  9.     TMOD = 0x01;
  10.     
  11.     IT0 = 1;
  12.     EA = 1;
  13.     EX0 = 1;
  14.     ET0 = 1;
  15.     test_port = 1;
  16.     while(1)
  17.     {
  18.         if(g_cnt>=20)
  19.         {
  20.             TR0 = 0;
  21.             g_cnt = 0;
  22.             if(test_port==0)LED_1=!LED_1;
  23.         }
  24.     }

  25.     return 0;
  26. }

  27. void extern0_service(void)interrupt 0
  28. {
  29.     EX0 = 0;
  30.     if(g_cnt==0)
  31.     {
  32.         g_cnt = 1;
  33.         TR0 = 1;
  34.     }
  35.     EX0 = 1;
  36. }

  37. void time0_service(void)interrupt 1
  38. {
  39.     TH0 = 64536>>8;TL0=64536&0xff;
  40.     g_cnt++;
  41. }

当然,不管黑猫白猫,能捉老鼠就是好猫,能够实现效果,采用哪种方式都无所谓。

 

最好的消抖的方法是硬件消抖,比如在开关处并联一个电容,这比软件好多了。

 

阅读(1369) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~