实验目的
使用51单片机的矩阵键盘模块以及led1602显示屏,实现模拟密码锁。
实验现象
当程序烧录到单片机中后,led1602屏幕会显示文字。
第一行会显示单词“PASSWORD”,第二行显示4个0,表示我们要写入的四位密码,每位默认为0。
矩阵键盘前两行与第三行的前两个分别代表输入1-9与0,第三行第三个按钮表示删除,第三行第四个按钮表示确认。
依次按下第一行的前三个按钮后,屏幕显示输入“0123”。
按下确认键后,若输入的密码就是设置的密码(这里是0123),显示“SUCCESS”字样,如下图:
反之,当输入的密码错误的时候,显示“FAIL”字样,并在5秒后回到输入密码界面,如下图:
当按下删除键时,删除最近输入的值,例如当前输入内容是“1234”,那么在按下删除键后将显示“0123”,如下图:
除了上述功能,当已经输入了四位数的时候,单片机将不再继续接受数据;当单片机数据为四个0的时候,按下删除键后单片机将不再继续删除数字。
硬件
本实验使用到4*4矩阵键盘,其内部结构图如下:
我们可以将独立按键的原理应用到矩阵键盘上。当独立按键被按下后,若有一端接地,那么另一端也将被置于低电平。矩阵键盘同理,当键盘上某个按键被按下后,如果它有一端为低电平(相当于接地),那么另一端也会被置0。
由此可见,想要找到被按下的那个按钮,如要对每个按钮进行如下操作:“先将按钮的一端置低电平,再监测按钮的另一端是否也为低电平。如果是,那么被按下的就是这个按钮”。
由上图可知,矩阵键盘16个按钮用8根线串在一起,每一行的按钮或者每一列的按钮用一根线连在一起,大大降低了GPIO口占用。P10到P13分别将第四列到第一列的按钮绑在一起,P14到P17分别将第四行到第一行的按钮绑在一起。
查找被按下的按钮时,可以按以下方式查找:
-
将P1口全部置高电平
-
将第i列的GPIO口置低电平(开始时i等于1,GPIO口就是P13)
-
依次检查第一行到第四行的GPIO口电平情况(P17到P14),有低电平的话就是这个按钮被按下
-
扫描完这一列,没有低电平,说明这一列没有被按下的,i+1后执行第二步
-
如果扫描四列都没有,说明没有按下的按钮。
程序
首先这个程序要在跑起来后,不断地对键盘进行扫描,如果扫描到了被按下的按钮,就执行对应的操作。
因此应当有一个带有返回值的函数,这个函数用于返回被按下的按钮编号。主函数中循环执行这个函数,直到返回按钮的数值:
unsigned short scan();//扫描键盘,有按钮被按下就返回编号,否则返回0
int main(){
unsigned short res = 0;
while(1){
res = scan();
if(res != 0){
//扫描到被按下的按钮,执行对应操作
}
}
}
扫描函数
上面的scan()函数就是执行扫描键盘的函数。在scan中,要实现对四列的扫描,要先初始化P1,再将要扫描的那一列的GPIO口置0,再看每一行的电平。
由于要扫描四列,因此我写了四个函数,在scan中依次执行这四个函数,并返回数值。
unsigned short scanColumn1();
unsigned short scanColumn2();
unsigned short scanColumn3();
unsigned short scanColumn4();
unsigned short scan(){
unsigned short res = 0;
res = scanColumn1();
if (res != 0) return res;
res = scanColumn2();
if (res != 0) return res;
res = scanColumn3();
if (res != 0) return res;
res = scanColumn4();
if (res != 0) return res;
return 0;
}
unsigned short scanColumn1(){
P1 = 0xff;//初始化P1口
P1_3 = 0;//第一列GPIO口置低电平
if (P1_7==0){//第一行有没有变成0
delay(20);//消抖
while(P1_7==0);
delay(20);
return 1;//返回按钮编号
}
else if (P1_6==0){//第二行有没有变成0
delay(20);
while(P1_6==0);
delay(20);
return 5;
}
else if (P1_5==0){//第三行有没有变成0
delay(20);
while(P1_5==0);
delay(20);
return 9;
}
else if (P1_4==0){//第四行有没有变成0
delay(20);
while(P1_4==0);
delay(20);
return 13;
}
return 0;
}
扫描第2-4列的函数与上面扫描第一行的大体相同,只是GPIO口与返回编号不一样而已。
主函数
上面写道当返回值不为0时,执行对应操作。现在返回值与操作的关系如下:
返回值 | 操作 |
---|---|
0,13,14,15,16 | 无 |
1 | 输入1 |
2 | 输入2 |
3 | 输入3 |
4 | 输入4 |
5 | 输入5 |
6 | 输入6 |
7 | 输入7 |
8 | 输入8 |
9 | 输入9 |
10 | 输入0 |
11 | 输入删除 |
12 | 确认 |
我们要使用一个初始值为0的变量表示当前输入的密码,当接收到输入0-9的指令时,将这个变量乘以10在加上0-9。
例如最开始是0000,按下1后,0*10+1=1,led显示0001;当按下删除时,将这个变量除以10,由于c语言整形做除法舍去小数点,因此可实现删除最近输入的效果:1234,按下删除后1234/10=123,显示0123。
除了要有一个变量表示密码,还要有一个初始值为0的变量表示已输入长度,每输入一位密码,这个变量值加1,当值为4时不再继续接受新的密码数字,当值为0是不支持删除操作。
当按下12时,做一个简单的if-else判断,判断密码变量的值是否等于0-9999中的值(代码中设置),是的话打印SUCCESS,不是的话先打印FAIL,停滞几秒后程序打印刚刚输入的密码,等待重新输入。
代码
lcd1602显示屏的代码是我在b站找到的现成代码,不是我自己写的,就不放出了,只知道是在lcd1602显示屏上打印变量的值即可。
main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "matrixKeyboard.h"
#include "Delay.h"
void main(){
unsigned short _code,_len,a;//code属于keil关键字,不可用code当变量名!
_code = 0;//密码变量
_len = 0;//长度变量
LCD_Init();
LCD_ShowString(1,1,"PASSSWORD");
LCD_ShowNum(2,1,_code,4);
while(1){
a = scan();//循环扫描,取编号
if (a>0&&a<=10){//按下输入0-9,且已输入密码长度小于4
if (_len<4){
if (a == 10) a = 0;
_len++;
_code = _code * 10+a;
LCD_ShowNum(2,1,_code,4);
}
}
else if(a == 11){//按下删除且当前密码长度大于0
if (_len>0){
_len--;
_code = _code / 10;
LCD_ShowNum(2,1,_code,4);
}
}
else if(a == 12){//确认
if (_code == 123) LCD_ShowString(2,1,"SUCCESS");//密码正确,这里是123
else{//密码错误
LCD_ShowString(2,1,"FAIL");//错误信息,延时后打印刚刚输入的密码
delay(5000);
LCD_ShowString(2,1," ");
LCD_ShowNum(2,1,_code,4);
}
}
}
}
matrixKeyboard.h
#ifndef __MATRIXKEYBOARD_H__
#define __MATRIXKEYBOARD_H__
#include <REGX52.H>
#include "Delay.h"
unsigned short scanColumn1();
unsigned short scanColumn2();
unsigned short scanColumn3();
unsigned short scanColumn4();
unsigned short scan();
#endif
matrixKeyboard.c
#include "matrixKeyboard.h"
unsigned short scanColumn1(){
P1 = 0xff;//P1初始化
P1_3 = 0;//相应列的GPIO置低电平
if (P1_7==0){//看哪个是低电平
delay(20);
while(P1_7==0);
delay(20);
return 1;
}
else if (P1_6==0){
delay(20);
while(P1_6==0);
delay(20);
return 5;
}
else if (P1_5==0){
delay(20);
while(P1_5==0);
delay(20);
return 9;
}
else if (P1_4==0){
delay(20);
while(P1_4==0);
delay(20);
return 13;
}
return 0;
}
unsigned short scanColumn2(){
P1 = 0xff;
P1_2 = 0;
if (P1_7==0){
delay(20);
while(P1_7==0);
delay(20);
return 2;
}
else if (P1_6==0){
delay(20);
while(P1_6==0);
delay(20);
return 6;
}
else if (P1_5==0){
delay(20);
while(P1_5==0);
delay(20);
return 10;
}
else if (P1_4==0){
delay(20);
while(P1_4==0);
delay(20);
return 14;
}
return 0;
}
unsigned short scanColumn3(){
P1 = 0xff;
P1_1 = 0;
if (P1_7==0){
delay(20);
while(P1_7==0);
delay(20);
return 3;
}
else if (P1_6==0){
delay(20);
while(P1_6==0);
delay(20);
return 7;
}
else if (P1_5==0){
delay(20);
while(P1_5==0);
delay(20);
return 11;
}
else if (P1_4==0){
delay(20);
while(P1_4==0);
delay(20);
return 15;
}
return 0;
}
unsigned short scanColumn4(){
P1 = 0xff;
P1_0 = 0;
if (P1_7==0){
delay(20);
while(P1_7==0);
delay(20);
return 4;
}
else if (P1_6==0){
delay(20);
while(P1_6==0);
delay(20);
return 8;
}
else if (P1_5==0){
delay(20);
while(P1_5==0);
delay(20);
return 12;
}
else if (P1_4==0){
delay(20);
while(P1_4==0);
delay(20);
return 16;
}
return 0;
}
unsigned short scan(){
unsigned short res = 0;
res = scanColumn1();
if (res != 0) return res;
res = scanColumn2();
if (res != 0) return res;
res = scanColumn3();
if (res != 0) return res;
res = scanColumn4();
if (res != 0) return res;
return 0;
}
Delay.h
#ifndef __DELAY_H__
#define __DELAY_H__
#include "intrins.h"
void delay(int ms);
#endif
Delay.c
#include "Delay.h"
void Delay(int ms) //@11.0592MHz
{
unsigned char i, j;
while(ms--){
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
其他
"code"属于keil关键字,不可取做变量名。