【51单片机】5. 矩阵键盘与矩阵键盘密码锁Demo

发布于:2025-06-14 ⋅ 阅读:(23) ⋅ 点赞:(0)

1. 矩阵键盘原理

在这里插入图片描述

通过矩阵连接的模式,原本需要16个引脚连接的按钮只需要8个引脚就能连接好,减少了I/O口的占用。

矩阵按钮是通过扫描来读取状态的。

2. 扫描的概念

输出扫描示例:数码管扫描

原理:显示第1位→显示第2位→显示第2位→…,快速重复此过程实现所有数码管同时显示的效果

输入扫描示例:矩阵键盘扫描

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

这种循环扫描的优势在于节省I/O口

3. Templates的定义

Template实际上就是双击出来指定的内容,比如平时写.h文件的时候,总是需要反复写如下开头结尾:

#ifndef __XXX_H__
#define __XXX_H__

#endif

会比较麻烦,通过Template可以将这部分代码定义存储,再使用的时候双击即可,定义Template步骤如下:

第一步:点击下方的【Templates】→【右键】→【Configure Templates】

在这里插入图片描述

第二步:新建,起一个好识别的名字,写入常用的部分作为Text

在这里插入图片描述

第三步:在希望光标落入的地方打一个|,最终Text部分输入如下:

#ifndef __|_H__
#define

#endif

在文件中双击,就会出现如图所示的结果:

在这里插入图片描述

4. 矩阵键盘扫描方式1

建立了MatrixKeyboard.cMatrixKeyboard.h,里面写扫描的代码

需要先理解矩阵键盘的按键按下是怎么被扫描到的,我这里直接复制了deepseek的回答:

在矩阵键盘中,检测按钮被按下的正确原理是:

  1. 行线(输出端)需要主动置低
  2. 列线(输入端)被按键下拉为低
  3. 检测时需满足:行线输出低电平 + 列线检测到低电平

基于此,我们需要给行线置低(根据上面的电路图图示,行线分别是P17,P16,P15,P14),同时检测列线(P13,P12,P11,P10)。

我在MatrixKeyboard.c中自己写了一个不带消抖的按行扫描:

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


// 按行扫描
unsigned char MatrixKeyboardScan()
{
    unsigned char keyNum = 0;

    P1 = 0xFF;
    P1_7 = 0;
    if (P1_3 == 0) keyNum = 1;
    if (P1_2 == 0) keyNum = 2;
    if (P1_1 == 0) keyNum = 3;
    if (P1_0 == 0) keyNum = 4;

    P1 = 0xFF;
    P1_6 = 0;
    if (P1_3 == 0) keyNum = 5;
    if (P1_2 == 0) keyNum = 6;
    if (P1_1 == 0) keyNum = 7;
    if (P1_0 == 0) keyNum = 8;

    P1 = 0xFF;
    P1_5 = 0;
    if (P1_3 == 0) keyNum = 9;
    if (P1_2 == 0) keyNum = 10;
    if (P1_1 == 0) keyNum = 11;
    if (P1_0 == 0) keyNum = 12;

    P1 = 0xFF;
    P1_4 = 0;
    if (P1_3 == 0) keyNum = 13;
    if (P1_2 == 0) keyNum = 14;
    if (P1_1 == 0) keyNum = 15;
    if (P1_0 == 0) keyNum = 16;

    return keyNum;
}

MartixKeyboard.h如下:

#ifndef __MATRIXKEYBOARD_H__
#define __MATRIXKEYBOARD_H__

unsigned char MatrixKeyboardScan();

#endif

为了验证不带消抖按行扫描存在的问题,我写了如下一段主函数:

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

void main()
{
    unsigned int myCount = 0;
    unsigned char keyboardNum = 0;
    LCD_Init();
    // LCD_ShowNum(2,1,keyboardNum,2);
    while (1)
    {
        keyboardNum = MatrixKeyboardScan();
        if (keyboardNum)
        {
            myCount++;
            LCD_ShowNum(1,1,keyboardNum,2);
            LCD_ShowNum(2,1,myCount,3);
        }
    }
}

此时,LCD第一行显示按下的按键,第二行显示受抖动电压影响,LCD_ShowNum语句执行的次数:

在这里插入图片描述

可以看到,不稳的电压起码抖动了11次,从而11次执行了LCD_ShowNum语句,为此,消抖语句的存在还是很必要的,故修改如下:

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


// 按行扫描
unsigned char MatrixKeyboardScan()
{
    unsigned char keyNum = 0;

    P1 = 0xFF;
    P1_7 = 0;
    if (P1_3 == 0) {Delay(20); while(P1_3 == 0) ; Delay(20); keyNum = 1;}
    if (P1_2 == 0) {Delay(20); while(P1_2 == 0) ; Delay(20); keyNum = 2;}
    if (P1_1 == 0) {Delay(20); while(P1_1 == 0) ; Delay(20); keyNum = 3;}
    if (P1_0 == 0) {Delay(20); while(P1_0 == 0) ; Delay(20); keyNum = 4;}

    P1 = 0xFF;
    P1_6 = 0;
    if (P1_3 == 0) {Delay(20); while(P1_3 == 0) ; Delay(20); keyNum = 5;}
    if (P1_2 == 0) {Delay(20); while(P1_2 == 0) ; Delay(20); keyNum = 6;}
    if (P1_1 == 0) {Delay(20); while(P1_1 == 0) ; Delay(20); keyNum = 7;}
    if (P1_0 == 0) {Delay(20); while(P1_0 == 0) ; Delay(20); keyNum = 8;}

    P1 = 0xFF;
    P1_5 = 0;
    if (P1_3 == 0) {Delay(20); while(P1_3 == 0) ; Delay(20); keyNum = 9;}
    if (P1_2 == 0) {Delay(20); while(P1_2 == 0) ; Delay(20); keyNum = 10;}
    if (P1_1 == 0) {Delay(20); while(P1_1 == 0) ; Delay(20); keyNum = 11;}
    if (P1_0 == 0) {Delay(20); while(P1_0 == 0) ; Delay(20); keyNum = 12;}

    P1 = 0xFF;
    P1_4 = 0;
    if (P1_3 == 0) {Delay(20); while(P1_3 == 0) ; Delay(20); keyNum = 13;}
    if (P1_2 == 0) {Delay(20); while(P1_2 == 0) ; Delay(20); keyNum = 14;}
    if (P1_1 == 0) {Delay(20); while(P1_1 == 0) ; Delay(20); keyNum = 15;}
    if (P1_0 == 0) {Delay(20); while(P1_0 == 0) ; Delay(20); keyNum = 16;}

    return keyNum;
}

修改后的执行结果如下,可以看到按下一次按钮,相应的代码只会执行一次:

在这里插入图片描述

5. 矩阵扫描方式2

先扫出按键的列,再扫出按键的行

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

// 先找列再找行
unsigned char MatrixKeyboardScan()
{
    // 先扫出按下按键所处列
    unsigned char keyNum = 0;
    P1 = 0x0F;
    switch(P1)
    {
        case (0x07): keyNum = 1; break;
        case (0x0B): keyNum = 2; break;
        case (0x0D): keyNum = 3; break;
        case (0x0E): keyNum = 4; break;
    }
    // 再扫出按下按键所处行
    P1 = 0xF0;
    switch(P1)
    {
        case (0x70): keyNum += 0; break;
        case (0xB0): keyNum += 4; break;
        case (0xD0): keyNum += 8; break;
        case (0xE0): keyNum += 12; break;
    }
    return keyNum;
}

原理很简单,当第一步将P1置为0x0F时,通过判断低位处于什么样的状态,就可以知道按下的是哪一列的按钮:

在这里插入图片描述

同理,第二步将P1置为0xF0时,判断高位处于什么样的状态,就可以判断出按下按钮的所处行。

因为在第一步假设按下按钮位于第1行,分别置了1、2、3、4;则第二行仅需根据行数,加上具体相隔的按钮值即可。

但是我不太清楚消抖应该加在哪,我自己试了一下会很奇怪,所以这里的代码只是简单写了一下,仅供参考,可能会出现抖动问题。

6. 主函数代码

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

void main()
{
    unsigned char keyboardNum = 0;
    LCD_Init();
    LCD_ShowString(1,1,"Press:");
    LCD_ShowNum(2,1,keyboardNum,2);
    while (1)
    {
        keyboardNum = MatrixKeyboardScan();
        if (keyboardNum)
        {
            LCD_ShowNum(2,1,keyboardNum,2);
        }
    }
}

结果如下:

在这里插入图片描述

7. 矩阵键盘密码锁

需求是由用户输入密码,单片机检测是否正确,正确则输出“OK”,反之输出“ERR”,除此之外,还需要有“删除(回退一格)”和“取消(输入密码全部清空)”功能。

我的代码实现如下,S1-S9代表数字1-9,S10是回退一格,S11是取消,S12是确认:

#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKeyboard.h"

void main()
{
    unsigned int password = 0;
    unsigned int truePassword = 1234;
    unsigned int passwordNum = 0;
    LCD_Init();
    LCD_ShowString(1,1,"Password:");
    LCD_ShowNum(2,1,password,4);
    password = password / 10;
    while(1)
    {
        unsigned int currNum = MatrixKeyboardScan();
        if (currNum)
        {
            // 非法输入不允许,直接忽略
            if (currNum == 13 || currNum == 14 || currNum == 15 || currNum == 16) continue;

            // 功能键:10删除,11取消,12确认检查密码
            // 删除操作
            if (currNum == 10 && passwordNum <= 4)
            {
                password /= 10; // 回退一位
                LCD_ShowNum(2,1,password,4); // 刷新密码
                passwordNum--; // 位数-1
                continue;
            }
            // 取消操作
            else if (currNum == 11)
            {
                passwordNum = 0;
                password = 0;
                LCD_ShowNum(2,1,password,4);
                continue;
            }
            // 确认操作
            else if (currNum == 12)
            {
                if (password == truePassword) // 密码正确显示OK,程序结束
				{
					LCD_ShowString(1,15,"OK");
				}					
				else // 密码错误显示ERR,password重置为0
				{
					LCD_ShowString(1,14,"ERR");
					passwordNum = 0;
					password = 0;
					LCD_ShowNum(2,1,password,4);
				}
				continue;
			}
			
			// 功能键以外的数字键处理
			if (passwordNum == 0)
			{
				LCD_ShowString(1,14,"   ");
				if (currNum == 13) currNum = 0; // 用13作为数字0,允许用户输入0
				password = currNum;
				LCD_ShowNum(2,1,password,4);
				passwordNum++;
			}
			else if (passwordNum < 4)
			{
				if (currNum == 13) currNum = 0; // 用13作为数字0,允许用户输入0
				password = password * 10 + currNum;
				LCD_ShowNum(2,1,password,4);
				passwordNum++;
			}
		}
	}
}

效果如下:

密码锁Demo

输入密码:
在这里插入图片描述
输入密码后按一下S10(回退一格):
在这里插入图片描述
按取消(密码置0):
在这里插入图片描述

输入错误密码按确定(密码清0,显示ERR):
在这里插入图片描述
输入正确密码按确定:
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到