资讯详情

51单片机外设篇:按键

关于按键

按钮相当于一个电子开关。按下时开关打开,松开时开关断开。实现原理是通过触摸按钮内的金属弹片来打开和断开。

最基本的是用死循环来轮询判断按键动作。

例如,如果按下某个按钮,监控LED就会亮。

对于机械开关,当机械接触断开或关闭时,由于机械接触的弹性,开关不会立即稳定连接,断开时不会突然断开,因此在开关关闭和断开时会伴随一系列抖动:

可通过延迟消抖。

上拉电阻

通过电阻和电源将不确定信号(高或低电平)VCC连接,固定在高电平。

通过电阻与地面连接不确定信号(高或低电平)GND连接,固定在低电平。

当上拉电阻与下拉电阻共同作用时,表现为接地端的低电平状态。

查看原理图

独立按键:

代码实现

此处,我将JP5引脚接到P1端口,将LED灯接到P0端口。

1.按一个按钮,照亮相应的对应LED灯。

/**   *@file    key.c   *@author  Timi   *@date    2022.07.21   */  #include <reg51.h>  #define uchar unsigned char  void KeyLightLed();          ///函数入口 void main(void) {         KeyLightLed(); }  /**   *@brief   *@param[in]   *@param[out]   *@return   */ void KeyLightLed() {     uchar portData[] = {0xFE, 0xFD, 0xFB, 0xF7, 0xEF, 0xDF, 0xBF, 0x7F};          while(1)     {         int i = 0;         for (i; i < 8; i  )         {             if(P1 == portData[i])             {                 P0 = ~portData[i];             }           }     }   }  

2.点击按钮控制其中一个LED灯,再次点击按钮,然后熄灭,再次按下并点亮,以便循环来回。也就是说,每个按钮都会改变相应的LED灯的状态。

/**   *@file    key.c   *@author  Timi   *@date    2022.07.21   */  #include <reg51.h>  #define uchar unsigned char      sbit P00  = P0^0; sbit P01  = P0^1; sbit P02  = P0^2; sbit P03  = P0^3; sbit P04  = P0^4; sbit P05  = P0^5; sbit P06  = P0^6; sbit P07  = P0^7;  sbit P10  = P1^0; sbit P11  = P1^1; sbit P12  = P1^2; sbit P13  = P1^3; sbit P14  = P1^4; sbit P15  = P1^5; sbit P16  = P1^6; sbit P17  = P1^7;  void KeyLightLed();          ///函数入口 void main(void) {         KeyLightLed(); }  /**   *@brief   *@param[in]   *@param[out]   *@return   */ void KeyLightLed() {     P0 = 0x0;          while(1)     {          if(P10 == 0)         {             P00 = ~P00;         }                  if(P11 == 0)         {             P01 = ~P01;         }                  if(P12 == 0)         {             P02 = ~P02;         }                  if(P13 == 0)         {             P03 = ~P03;         }                  if(P14 == 0)         {             P04 = ~P04;         }                  if(P15 == 0)         {             P05 = ~P05;         }                  if(P16 == 0)         {             P06 = ~P06;         }                  if(P17 == 0)         {             P07 = ~P07;         }     }   } 

关于这种方法的灵敏度。

当按数量少的时候,其实并不明显,如果按键数量过多,就会导致按键时,CPU其他部分的代码仍在执行中,因此检测按钮不及时。

上面提到的按钮在按下和弹起的过程中会抖动,这会导致一些问题。例如,按下和弹起按钮时,按钮会闪烁。更明显的是,例如,按钮控制数字管数量依次增加。如果不抖动,按下时可能会多次检测到低电平,因为抖动,所以显然只按下一个按钮,但数字不规则增加,例如,有时增加1,有时超过1。

所以常常都需要消抖,消抖有两方面,一方面是硬件消抖(比如利用电容器来使抖动变得更平滑),另一方面是软件消抖,即在检测到一次某种电平时,不急着进行某种操作,而是延时一段时间,因为这种电平有可能是抖动期间引起的,并不稳定。

通常,抖动的延迟时间在5-20毫秒之间,一般选择10毫秒。示例代码如下:

/**   *@file    key.c   *@author  Timi   *@date    2022.07.21   */  #include <reg51.h>  #define uchar unsigned char      sbit P00  = P0^0;  sbit P10  = P1^0;   void KeyLightLed(void); void Delay10ms(void);          ///函数入口 void main(void) {         KeyLightLed(); }  /**   *@brief   *@param[in]   *@param[out]   *@return   */ void KeyLightLed(void) {     P0 = 0x0;          while(1)     {          if(P10 == 0)         {             ///不着先点亮LED,延时10ms后再判断,如果状态仍然相同,点亮             Delay10ms();                          if (P10 == 0)             {                 P00 = 1;             }         }         else         {             ///不着先点亮LED,延时10ms后再判断,如果状态仍然相同,点亮             Delay10ms();                          if (P10 == 1)             {                 P00 = 0;             }         }     }   }  /**   *@brief   *@param[in]   *@param[out]   *@return   */ void Delay10ms(void)   //误差 0us {     uchar a, b, c;          for(c=5; c > 0; c--)     {         for(b=4;b>0;b--)         {             for(a=248;a>0;a--);         }     }         }

中断的引入

任务:独立数字管循环显示0-F,同时,按键控制LED亮灭。

这两个功能都需要实现CPU执行死循环怎么办?最简单的就是把这两个程序放在同一个死循环里。这个问题可以在一定程度上解决。

但也有一个问题,即按键检测可能不及时,导致按键不敏感。

为什么呢?因为只有在执行完t时间过后,来t2.按钮检测代码只能在时间内执行,所以如果按钮一直在等待检测,它仍然可以工作;如果你快速按下按钮,很有可能t一次,然后就检测不到了。

这两个任务需要宏观并行执行。t1时间远大于t2的时间,t1可以是秒级,t几乎是微秒级,差距还是比较大的。

p>此时,数码管循环显示就可以看做是主线任务。“主线任务”为常规任务,默认运行。

可以将类似按键检测的这种任务绑定到中断。中断发生后CPU暂停主线任务转去处理中断任务,完成后再回来接着执行主线任务。由于中断处理程序所耗费的时间较短,所以几乎不会对主线任务产生影响。

中断流程:

中断处理能力让CPU可以全力处理主线任务而不用担心会错过中断任务(举例:看电影和收快递)

中断式比轮询式更适合处理异步事件,效率更高。

中断中处理的事件的特点是:无法预料、处理时间短、响应要求急。

中断能力是CPU本身设计时支持的,并不是编程制造出来的 程序员只要负责2件事即可:主程序中初始化中断、定义中断处理程序。 当中断条件发生时,硬件会自动检测到并且通知CPU,CPU会自动去执行中断处理程序,这一切都是CPU设计时定下的,不需要编程干预。

51单片机的中断

 

 内容较多,具体内容阅读数据手册,中断系统相关章节。

外部中断0使用示例如下:

我们这里就用INT0来进行简单的按键处理。

在电路连接上,需要将按键的一端连接到中断引脚上,INT0的引脚为P3.2

如果中断没有打开并使能,那么P3.2就是一个普通的GPIO。

这里我有个疑惑,那就是,INT0只有一个引脚,那么不就只有1个按键能触发中断了?这也太少了吧。

暂且不管,后面再说。~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

中断代码实现

实现功能如下:按键K1触发外部中断0,然后就执行相应的代码,点亮LED。

/**
  *@file    key.c
  *@author  Timi
  *@date    2022.07.21
  */

#include <reg51.h>

#define uchar unsigned char
    
sbit P00  = P0^0;

sbit P10  = P1^0;

void KeyLightLed(void);
void Delay10ms(void);
        
//函数入口
void main(void)
{    
    IT0 = 1;    //选择下降沿触发
    EX0 = 1;    //使能INT0
    EA  = 1;    //打开中断开关
    
    while(1);
}

/**
  *@brief
  *@param[in]
  *@param[out]
  *@return
  */
void KeyLightLed(void) interrupt 0
{
    P0 = 0xAA;
}

矩阵键盘

按键作为最基本的输入设备,有独立按键和矩阵按键两种。

和LED类似的理念,独立按键就是两个引脚都有单独的线连接。矩阵按键则是将许多按键的同一端连接在一起,然后通过扫描来检测哪个按键被按下。

独立按键与单片机连接时, 每一个按键都需要单片机的一个 I/O 口, 若某单片机系统需较多按键, 用独立按键便会占用过多的 I/O 口资源。 单片机系统中 I/O 口资源往往比较宝贵, 当用到多个按键时为了减少 I/O 口引脚, 引入了矩阵按键,一般叫做矩阵键盘。

可以看到是将16个按键排成4行4列,前面的四行分别连接io口的每一行,后面的四行分别连接io口的每一列,这样就实现了每个io口都连接四个按键,同样通过这样的方式也可以实现3X3,5X5等这样的布局。

那么在检测的时候又是如何实现的呢,这种按键的检测一般是通过扫描来实现的。

具体原理解析如下:

首先,假定p10-p13接地,p14-p17接电源,则可以通过检测p14-p17是否有电平被拉低,因为扫描的速度够快,所以能检测到按键按下,但这种情况只能确定有哪一行被按下了,并不能定位到具体的按键。

要想定位到具体的按键,那么就需要用到二维检测。

首先,让p10置低电平,p11-p13置高电平,同时检测p14-p17是否有电平被拉低,假如此时检测到p14有电平变化,则肯定是s1按键被按下了。同理,可以检测到任意一个按键是否被按下。

矩阵键盘: 优点:省单片机IO 缺点:不能同时按下多个按键

数码管扫描(输出扫描)

原理:显示第1位→显示第2位→显示第3位→……,然后快速循环这个过程,最终实现所有数码管同时显示的效果;

矩阵键盘扫描(输入扫描)

原理:读取第1行(列)→读取第2行(列) →读取第3行(列) → ……,然后快速循环这个过程,最终实现所有按键同时检测的效果。

查看原理图

代码实现

将JP4连接到P1端口,P1端口全部为高电平。

/**
  *@file    key.c
  *@author  Timi
  *@date    2022.07.21
  */

#include <reg51.h>

#define uchar unsigned char
    
sbit P10  = P1^0;
sbit P11  = P1^1;
sbit P12  = P1^2;
sbit P13  = P1^3;
sbit P14  = P1^4;
sbit P15  = P1^5;
sbit P16  = P1^6;
sbit P17  = P1^7;

void KeyLightLed(void);
void FeelKey();
        
//函数入口
void main(void)
{    
    KeyLightLed();
}

/**
  *@brief
  *@param[in]
  *@param[out]
  *@return
  */
void KeyLightLed(void)
{
    while(1)
    {  
        P1 = 0xFE;  //经测试,最后一列是P1.7控制的
        
        FeelKey();
        
        P1 = 0xFD;  //经测试,最后一列是P1.7控制的
        
        FeelKey();
        
        P1 = 0xFB;  //经测试,最后一列是P1.7控制的
        
        FeelKey();
        
        P1 = 0xF7;  //经测试,最后一列是P1.7控制的
        
        FeelKey();
    }
}

/**
  *@brief
  *@param[in]
  *@param[out]
  *@return
  */
void FeelKey()
{
    if(P14 == 0)
    {
        P0 = 0x01;
    }
    if(P15 == 0)
    {
        P0 = 0x02;
    }
    if(P16 == 0)
    {
        P0 = 0x04;
    }
    if(P17 == 0)
    {
        P0 = 0x08;
    }
}

 

标签: 26k1电阻电阻9k1

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台