【51单片机】5. 矩阵键盘与矩阵键盘密码锁Demo
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.c
和MatrixKeyboard.h
,里面写扫描的代码
需要先理解矩阵键盘的按键按下是怎么被扫描到的,我这里直接复制了deepseek的回答:
在矩阵键盘中,检测按钮被按下的正确原理是:
- 行线(输出端)需要主动置低
- 列线(输入端)被按键下拉为低
- 检测时需满足:行线输出低电平 + 列线检测到低电平
基于此,我们需要给行线置低(根据上面的电路图图示,行线分别是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--; // 位数-1continue;}// 取消操作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,允许用户输入0password = currNum;LCD_ShowNum(2,1,password,4);passwordNum++;}else if (passwordNum < 4){if (currNum == 13) currNum = 0; // 用13作为数字0,允许用户输入0password = password * 10 + currNum;LCD_ShowNum(2,1,password,4);passwordNum++;}}}
}
效果如下:
输入密码:
输入密码后按一下S10(回退一格):
按取消(密码置0):
输入错误密码按确定(密码清0,显示ERR):
输入正确密码按确定: