矩阵键盘

在键盘中按键数量较多时,为了减少I/O口的占用,通常将按键排列成矩阵形式。采用逐行或逐列地“扫描”,就可以读出任何位置按键的状态。

51单片机矩阵键盘

查询开发板原理图:

image-20221231221436722

对比:

image-20221231230854338

  • 按普通方式连接16个按键:占用16个I/O口。
  • 矩阵键盘16个按键:占用8个I/O口。

如果按键数量更多,减少的I/O口占用数量会更可观。

扫描

  • 数码管扫描(输出扫描)

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

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

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

以上两种扫描方式的共性:节省I/O口。

矩阵键盘扫描

行扫描:

  1. 将第一行公共端(P17)置0,其他行公共端(P16 P15 P14)置1。
  2. 读取各列引脚(P13 P12 P11 P10)。如果读取到某个列为0(单片机I/O口上电默认为高电平1),则可以确定第一行某列的引脚被按下。
  3. 将第二行公共端(P16)置0,其他公共端(P17 P15 P14)置1。重复读取列操作。
  4. 对其余行重复上述操作。反复快速地读取,就可以实时监控哪个按键被按下。

列扫描同理,将上述行列互换即可。

旧版本的普中开发板P15引脚与蜂鸣器复用,使用行扫描时反复将P15置高低电平会使其一直响,因此旧版普中开发板宜使用列扫描。

51单片机弱上拉模式

上述情境中,当我们使用扫描方法读取按键状态时,如果为某行的公共端置0,并且按下此行的某个按键,那么某列的引脚就可以读取到0。但是51单片机引脚上电默认为高电平1,即此处用来读取列的引脚的电平为1。那么按键将他们接通,为什么没有短路呢?原因是这里的引脚是弱上拉、强下拉模式,即高电平驱动能力弱、低电平驱动能力较强。

可以按下图的简化模型进行通俗的理解。图中是一个弱上拉I/O口内部结构,既可以输出又可以输入。绿色部分的电路是控制引脚输出高低电平的,粉色部分是负责读取引脚输入电平的。

上电默认引脚为高电平,即绿色部分的电路通过电阻接通VCC。此时读取到的电平也是高电平。

如果在引脚外部输入低电平,由于电阻的存在并不会短路,并且此时读取到的是低电平。这也是低电平驱动能力更大的原因。

STC89C52单片机的P1 P2 P3引脚均为弱上拉模式。P0为开漏输出模式,但在普中开发板上接入了上拉电阻所以功能和弱上拉一样。所以可以认为普中开发板上所有I/O口均为弱上拉模式。

除此之外更高阶的单片机的I/O口还有其他模式,并且支持配置,如:

  • 推挽输出:没有上拉电阻,只能作为输出引脚
  • 高阻输入:没有输出电阻,只能作为输入引脚

I/O口模式详细信息请参考手册4.1节:STC89C52.pdf | Mikumikumi 文件站

获取按下的键码

程序是按照上面的列扫描思路写的,方便理解,存在优化空间。来自B站江科大自化协。

返回当前按下的按键键码的源码:

#include <REGX52.H>
#include "Delay.h"

/**
  * @brief  矩阵键盘读取按键键码
  * @param  无
  * @retval KeyNumber 按下按键的键码值
            如果按键按下不放,程序会停留在此函数,松手的一瞬间,返回按键键码,没有按键按下时,返回0
  */
unsigned char MatrixKey()
{
    unsigned char KeyNumber = 0;

    P1 = 0xFF;
    P1_3 = 0;
    if(P1_7==0){Delay(20); while(P1_7==0); Delay(20); KeyNumber=1;}
    if(P1_6==0){Delay(20); while(P1_6==0); Delay(20); KeyNumber=5;}
    if(P1_5==0){Delay(20); while(P1_5==0); Delay(20); KeyNumber=9;}
    if(P1_4==0){Delay(20); while(P1_4==0); Delay(20); KeyNumber=13;}

    P1 = 0xFF;
    P1_2 = 0;
    if(P1_7==0){Delay(20); while(P1_7==0); Delay(20); KeyNumber=2;}
    if(P1_6==0){Delay(20); while(P1_6==0); Delay(20); KeyNumber=6;}
    if(P1_5==0){Delay(20); while(P1_5==0); Delay(20); KeyNumber=10;}
    if(P1_4==0){Delay(20); while(P1_4==0); Delay(20); KeyNumber=14;}

    P1 = 0xFF;
    P1_1 = 0;
    if(P1_7==0){Delay(20); while(P1_7==0); Delay(20); KeyNumber=3;}
    if(P1_6==0){Delay(20); while(P1_6==0); Delay(20); KeyNumber=7;}
    if(P1_5==0){Delay(20); while(P1_5==0); Delay(20); KeyNumber=11;}
    if(P1_4==0){Delay(20); while(P1_4==0); Delay(20); KeyNumber=15;}

    P1 = 0xFF;
    P1_0 = 0;
    if(P1_7==0){Delay(20); while(P1_7==0); Delay(20); KeyNumber=4;}
    if(P1_6==0){Delay(20); while(P1_6==0); Delay(20); KeyNumber=8;}
    if(P1_5==0){Delay(20); while(P1_5==0); Delay(20); KeyNumber=12;}
    if(P1_4==0){Delay(20); while(P1_4==0); Delay(20); KeyNumber=16;}

    return KeyNumber;
}