矩阵键盘 | 我的日常分享

矩阵键盘

51单片机-矩阵键盘

一、调试工具LCD1602

我们在学习C/C++时,可以通过printfcout很方便的在控制台中查看到输出的内容。但是在单片机中就没有这么方便了,所以我们需要一个能够显示内容的元件,来供我们调试使用。数码管其实也可以但由于显示内容少,采用的是扫描方式,我们选用LCD1602来显示打印输出内容。

下面提供了可以直接使用的函数:

LCD1602.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef __LCD1602_H__
#define __LCD1602_H__

//用户调用函数:


void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);

#endif

LCD1602.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
#include <REGX52.H>

//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

//函数定义:
/**
* @brief LCD1602延时函数,12MHz调用可延时1ms
* @param 无
* @retval 无
*/
void LCD_Delay()
{
unsigned char i, j;

i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}

/**
* @brief LCD1602写命令
* @param Command 要写入的命令
* @retval 无
*/
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;
LCD_RW=0;
LCD_DataPort=Command;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}

/**
* @brief LCD1602写数据
* @param Data 要写入的数据
* @retval 无
*/
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1;
LCD_RW=0;
LCD_DataPort=Data;
LCD_EN=1;
LCD_Delay();
LCD_EN=0;
LCD_Delay();
}

/**
* @brief LCD1602设置光标位置
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @retval 无
*/
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}

/**
* @brief LCD1602初始化函数
* @param 无
* @retval 无
*/
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}

/**
* @brief 在LCD1602指定位置上显示一个字符
* @param Line 行位置,范围:1~2
* @param Column 列位置,范围:1~16
* @param Char 要显示的字符
* @retval 无
*/
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
LCD_SetCursor(Line,Column);
LCD_WriteData(Char);
}

/**
* @brief 在LCD1602指定位置开始显示所给字符串
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串
* @retval 无
*/
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}

/**
* @brief 返回值=X的Y次方
*/
int LCD_Pow(int X,int Y)
{
unsigned char i;
int Result=1;
for(i=0;i<Y;i++)
{
Result*=X;
}
return Result;
}

/**
* @brief 在LCD1602指定位置开始显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~65535
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
}
}

/**
* @brief 在LCD1602指定位置开始以有符号十进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-32768~32767
* @param Length 要显示数字的长度,范围:1~5
* @retval 无
*/
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
unsigned char i;
unsigned int Number1;
LCD_SetCursor(Line,Column);
if(Number>=0)
{
LCD_WriteData('+');
Number1=Number;
}
else
{
LCD_WriteData('-');
Number1=-Number;
}
for(i=Length;i>0;i--)
{
LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
}
}

/**
* @brief 在LCD1602指定位置开始以十六进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFF
* @param Length 要显示数字的长度,范围:1~4
* @retval 无
*/
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i,SingleNumber;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
SingleNumber=Number/LCD_Pow(16,i-1)%16;
if(SingleNumber<10)
{
LCD_WriteData(SingleNumber+'0');
}
else
{
LCD_WriteData(SingleNumber-10+'A');
}
}
}

/**
* @brief 在LCD1602指定位置开始以二进制显示所给数字
* @param Line 起始行位置,范围:1~2
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{


LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
}
}

案例:显示hello world

代码:

1
2
3
4
5
6
7
8
#include<regx52.h>
#include"lcd1602.h"
void main(){
LCD_Init();
LCD_ShowString(1,2,"hello world");
while(1){
}
}

现象:

img

二、矩阵键盘的介绍

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

2.1 扫描的概念

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

    原理:显示第一位—>显示第二位—>显示第三位—> ······,然后快速循环这个过程,最终实现所有数码管同时显示的效果

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

    原理:读取第一行(列)—> 读取第二行(列)—> 读取第三行(列)—> ······,然后快速循环这个过程,最终实现所有按键同时检测的效果

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

2.2 独立按键

image-20210323182607695

独立按键的检测方式:所有的一端接GND(低电平) ,当按键按下即可被检测到输入的低电平。

2.3 矩阵按键

image-20210323193043221

矩阵键盘可以看做一行行的独立按键。

就这样从第一行然后第二行最后第四行逐行检测,例如:

检测第一行可令P17 P16 P15 P14分别为0111,即第一行与独立按键接地等效;然后检测P13 P12 P11P1 0唧可得出哪个按键被按下。

第一行按键检测代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
unsigned char oneRow(){
unsigned char KeyNumber = 0;
//令P17 P16 P15 P14分别为0111
P1 = 0xff,P1_7 = 0;
//如果S1被按下返回1
if(P1_3 == 0){
Delaynms(20);
while(P1_3 == 0){}
Delaynms(20);
KeyNumber = 1;
}
//如果S2被按下返回2
if(P1_2 == 0){
Delaynms(20);
while(P1_2 == 0){}
Delaynms(20);
KeyNumber = 2;
}
//如果S3被按下返回3
if(P1_1 == 0){
Delaynms(20);
while(P1_1 == 0){}
Delaynms(20);
KeyNumber = 3;
}
//如果S4被按下返回4
if(P1_0 == 0){
Delaynms(20);
while(P1_0 == 0){}
Delaynms(20);
KeyNumber = 4;
}
return KeyNumber;
}

然后再是第二行,可邻P17 P16 P15 P14分别为1011,即第二行与独立按键接地等效;然后同理检测P13 P12 P11 P1 0唧可得出哪个按键被按下。

第二行按键检测代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
unsigned char twoRow(){
unsigned char KeyNumber = 0;
//令P17 P16 P15 P14分别为1011
P1 = 0xff,P1_6 = 0;
//如果S5被按下返回5
if(P1_3 == 0){
Delaynms(20);
while(P1_3 == 0){}
Delaynms(20);
KeyNumber = 5;
}
//如果S6被按下返回6
if(P1_2 == 0){
Delaynms(20);
while(P1_2 == 0){}
Delaynms(20);
KeyNumber = 6;
}
//如果S7被按下返回7
if(P1_1 == 0){
Delaynms(20);
while(P1_1 == 0){}
Delaynms(20);
KeyNumber = 7;
}
//如果S8被按下返回8
if(P1_0 == 0){
Delaynms(20);
while(P1_0 == 0){}
Delaynms(20);
KeyNumber = 8;
}
return KeyNumber;
}

第三行,可令P17 P16 P15 P14分别为1101,即第三行与独立按键接地等效:同理检测P13 P12 P11P1 0即可得出哪个按键被按下。

第三行按键检测代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
unsigned char threeRow(){
unsigned char KeyNumber = 0;
//令P17 P16 P15 P14分别为1101
P1 = 0xff,P1_5 = 0;
//如果S9被按下返回9
if(P1_3 == 0){
Delaynms(20);
while(P1_3 == 0){}
Delaynms(20);
KeyNumber = 9;
}
//如果S10被按下返回10
if(P1_2 == 0){
Delaynms(20);
while(P1_2 == 0){}
Delaynms(20);
KeyNumber = 10;
}
//如果S11被按下返回11
if(P1_1 == 0){
Delaynms(20);
while(P1_1 == 0){}
Delaynms(20);
KeyNumber = 11;
}
//如果S12被按下返回12
if(P1_0 == 0){
Delaynms(20);
while(P1_0 == 0){}
Delaynms(20);
KeyNumber = 12;
}
return KeyNumber;
}

第四行,令P17 P16 P15 P14分别为1110,即第四行与独立按键接地等效;然后同理检测P13 P12P11 P1 0即可得出哪个按键被按下。

第四行按键检测代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
unsigned char fourRow(){
unsigned char KeyNumber = 0;
//令P17 P16 P15 P14分别为1110
P1 = 0xff,P1_4 = 0;
//如果S13被按下返回13
if(P1_3 == 0){
Delaynms(20);
while(P1_3 == 0){}
Delaynms(20);
KeyNumber = 13;
}
//如果S14被按下返回14
if(P1_2 == 0){
Delaynms(20);
while(P1_2 == 0){}
Delaynms(20);
KeyNumber = 14;
}
//如果S15被按下返回15
if(P1_1 == 0){
Delaynms(20);
while(P1_1 == 0){}
Delaynms(20);
KeyNumber = 15;
}
//如果S16被按下返回16
if(P1_0 == 0){
Delaynms(20);
while(P1_0 == 0){}
Delaynms(20);
KeyNumber = 16;
}
return KeyNumber;
}

将上面代码合在MatrixKey的函数中:

MartriKey.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
unsigned char MartinKey(){
unsigned char KeyNumber = 0;
//令P17 P16 P15 P14分别为0111
P1 = 0xff,P1_7 = 0;
//如果S1被按下返回1
if(P1_3 == 0){
Delaynms(20);
while(P1_3 == 0){}
Delaynms(20);
KeyNumber = 1;
}
//如果S2被按下返回2
if(P1_2 == 0){
Delaynms(20);
while(P1_2 == 0){}
Delaynms(20);
KeyNumber = 2;
}
//如果S3被按下返回3
if(P1_1 == 0){
Delaynms(20);
while(P1_1 == 0){}
Delaynms(20);
KeyNumber = 3;
}
//如果S4被按下返回4
if(P1_0 == 0){
Delaynms(20);
while(P1_0 == 0){}
Delaynms(20);
KeyNumber = 4;
}


//令P17 P16 P15 P14分别为1011
P1 = 0xff,P1_6 = 0;
//如果S5被按下返回5
if(P1_3 == 0){
Delaynms(20);
while(P1_3 == 0){}
Delaynms(20);
KeyNumber = 5;
}
//如果S6被按下返回6
if(P1_2 == 0){
Delaynms(20);
while(P1_2 == 0){}
Delaynms(20);
KeyNumber = 6;
}
//如果S7被按下返回7
if(P1_1 == 0){
Delaynms(20);
while(P1_1 == 0){}
Delaynms(20);
KeyNumber = 7;
}
//如果S8被按下返回8
if(P1_0 == 0){
Delaynms(20);
while(P1_0 == 0){}
Delaynms(20);
KeyNumber = 8;
}


//令P17 P16 P15 P14分别为1101
P1 = 0xff,P1_5 = 0;
//如果S9被按下返回9
if(P1_3 == 0){
Delaynms(20);
while(P1_3 == 0){}
Delaynms(20);
KeyNumber = 9;
}
//如果S10被按下返回10
if(P1_2 == 0){
Delaynms(20);
while(P1_2 == 0){}
Delaynms(20);
KeyNumber = 10;
}
//如果S11被按下返回11
if(P1_1 == 0){
Delaynms(20);
while(P1_1 == 0){}
Delaynms(20);
KeyNumber = 11;
}
//如果S12被按下返回12
if(P1_0 == 0){
Delaynms(20);
while(P1_0 == 0){}
Delaynms(20);
KeyNumber = 12;
}


//令P17 P16 P15 P14分别为1110
P1 = 0xff,P1_4 = 0;
//如果S13被按下返回13
if(P1_3 == 0){
Delaynms(20);
while(P1_3 == 0){}
Delaynms(20);
KeyNumber = 13;
}
//如果S14被按下返回14
if(P1_2 == 0){
Delaynms(20);
while(P1_2 == 0){}
Delaynms(20);
KeyNumber = 14;
}
//如果S15被按下返回15
if(P1_1 == 0){
Delaynms(20);
while(P1_1 == 0){}
Delaynms(20);
KeyNumber = 15;
}
//如果S16被按下返回16
if(P1_0 == 0){
Delaynms(20);
while(P1_0 == 0){}
Delaynms(20);
KeyNumber = 16;
}

return KeyNumber;
}

2.4 实现在LCD1602中显示按下按键的序号

main.c

1
2
3
4
5
6
7
8
9
10
void main(){		                     
unsigned int ret = 0;
LCD_Init();
while(1){
ret = MatrixKey();
if(ret){//判断是否按下了按键 按下则更新屏幕
LCD_ShowNum(1,1,ret,2);
}
}
}

现象:

三、利用矩阵键盘实现密码锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#include<regx52.h>
#include<intrins.h>
#include"lcd1602.h"
#include <string.h>
char * MatrixKey();
void Delaynms(int nms);
void main(){
char pwd[12] = "\0";
char ret[2] = "\0";
int count = 0;
LCD_Init();
LCD_ShowString(1,1,"Password:");
while(1){
strcpy(ret,MatrixKey());
if(strlen(ret) && count < 12 && strcmp(ret,"10") && strcmp(ret,"11")){
strcat(pwd,ret);
LCD_ShowString(2,1,pwd);
LCD_ShowString(1,14," ");
count++;
}

if(!strcmp(ret,"10")){
if(!strcmp(pwd,"666")){
LCD_ShowString(1,14,"OK ");
memset(pwd,0,12);
LCD_ShowString(2,1," ");
count = 0;
}else{
LCD_ShowString(1,14,"ERR");
memset(pwd,0,12);
LCD_ShowString(2,1," ");
count = 0;
}
}

if(!strcmp(ret,"11")){
memset(pwd,0,12);
LCD_ShowString(1,14," ");
LCD_ShowString(2,1," ");
count = 0;
}
}
}
char * MatrixKey(){
char KeyNumber[] = "\0";
//令P17 P16 P15 P14分别为0111
P1 = 0xff,P1_7 = 0;
//如果S1被按下返回1
if(P1_3 == 0){
Delaynms(20);
while(P1_3 == 0){}
Delaynms(20);
strcpy(KeyNumber,"1");
}
//如果S2被按下返回2
if(P1_2 == 0){
Delaynms(20);
while(P1_2 == 0){}
Delaynms(20);
strcpy(KeyNumber,"2");
}
//如果S3被按下返回3
if(P1_1 == 0){
Delaynms(20);
while(P1_1 == 0){}
Delaynms(20);
strcpy(KeyNumber,"3");
}
//如果S4被按下返回4
if(P1_0 == 0){
Delaynms(20);
while(P1_0 == 0){}
Delaynms(20);
strcpy(KeyNumber,"4");
}


//令P17 P16 P15 P14分别为1011
P1 = 0xff,P1_6 = 0;
//如果S5被按下返回5
if(P1_3 == 0){
Delaynms(20);
while(P1_3 == 0){}
Delaynms(20);
strcpy(KeyNumber,"5");
}
//如果S6被按下返回6
if(P1_2 == 0){
Delaynms(20);
while(P1_2 == 0){}
Delaynms(20);
strcpy(KeyNumber,"6");
}
//如果S7被按下返回7
if(P1_1 == 0){
Delaynms(20);
while(P1_1 == 0){}
Delaynms(20);
strcpy(KeyNumber,"7");
}
//如果S8被按下返回8
if(P1_0 == 0){
Delaynms(20);
while(P1_0 == 0){}
Delaynms(20);
strcpy(KeyNumber,"8");
}


//令P17 P16 P15 P14分别为1101
P1 = 0xff,P1_5 = 0;
//如果S9被按下返回9
if(P1_3 == 0){
Delaynms(20);
while(P1_3 == 0){}
Delaynms(20);
strcpy(KeyNumber,"9");
}
//如果S10被按下返回10
if(P1_2 == 0){
Delaynms(20);
while(P1_2 == 0){}
Delaynms(20);
strcpy(KeyNumber,"10");
}
//如果S11被按下返回11
if(P1_1 == 0){
Delaynms(20);
while(P1_1 == 0){}
Delaynms(20);
strcpy(KeyNumber,"11");
}

return KeyNumber;
}
void Delaynms(int nms) //@11.0592MHz
{
unsigned char i, j;
while(nms--){
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}

现象: