矩阵按键介绍

  独立按键与单片机连接时,每一个按键都需要单片机的一个 I/O 口,若某单片机系统需较多按键,如果用独立按键便会占用过多的 I/O 口资源。单片机系统中 I/O 口资源往往比较宝贵,当用到多个按键时为了减少 I/O 口引脚,引入了矩阵按键。

  无论是独立键盘还是矩阵键盘,单片机检测其是否被按下的依据都是一样的,也就是检测与该键对应的 I/O 口是否为低电平。独立键盘有一端固定为低电平,此种方式编程比较简单。而矩阵键盘两端都与单片机 I/O 口相连,因此在检测时需编程通过单片机 I/O 口送出低电平。检测方法有多种,最常用的是行列扫描和线翻转法。

  • 行列扫描法:先送一列为低电平,其余几列全为高电平(此时我们确定了列数),然后立即轮流检测一次各行是否有低电平,若检测到某一行为低电平(这时我们又确定了行数),则我们便可确认当前被按下的键是哪一行哪一列的,用同样方法轮流送各列一次低电平,再轮流检测一次各行是否变为低电平,这样即可检测完所有的按键,当有键被按下时便可判断出按下的键是哪一个键。当然我们也可以将行线置低电平,扫描列是否有低电平。从而达到整个键盘的检测。
  • 线翻转法: 使所有行线为低电平时,检测所有列线是否有低电平,如果有,就记录列线值;然后再翻转,使所有列线都为低电平,检测所有行线的值,由于有按键按下,行线的值也会有变化,记录行线的值。从而就可以检测到全部按键。

硬件设计

  本实验用到的硬件资源如下:

  1. 静态数码管
  2. 4*4矩阵键盘

  4*4矩阵键盘模块电路图如下所示:

  从上图中可以看出,该电路是独立的,4*4矩阵按键引出的8根控制管脚并未直接连接到51单片机的IO上,而是连接到JP3端子上。电路中的ARRAY_H1表示矩阵键盘第1行,ARRAY_L1表示矩阵键盘第1列。

软件设计

  本实验通过数码管显示矩阵按键S1-S16按下后键值0-F。
  使用P1口来检测4*4矩阵按键,使用P0口控制静态数码管。单片机的P17口连接矩阵键盘的第1行,P13口连接矩阵键盘第1列。

实物接线图
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
#include "reg52.h"

// 使用宏定义矩阵按键控制口
#define KEY_MATRIX_PORT P1
// 使用宏定义数码管段码口
#define SMG_A_DP_PORT P0

// 对系统默认数据类型进行重命名
typedef unsigned int u16;
typedef unsigned char u8;

//共阴极数码管显示0~F的段码数据
u8 gsmg_code[17] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};

// 延时函数
void delay_10us(u16 ten_us)
{
while(ten_us--);
}

/********************************************************************
*函数名: key_matrix_ranks_scan
*函数功能:使用行列式扫描方法,检测矩阵按键是否按下,按下则返回对应键值
*输入:无
*输出: key_value:1-16,对应S1-S16键,
0:按键未按下
*********************************************************************/
u8 key_matrix_ranks_scan()
{
u8 key_value = 0;

KEY_MATRIX_PORT = 0xF7; // 11110111 第一列赋值为0,其余全为1
if(KEY_MATRIX_PORT != 0xF7) // 判断第一列按键是否按下
{
delay_10us(1000); // 消抖
switch(KEY_MATRIX_PORT) // 保存第一列按键按下后的键值
{
case 0x77: key_value = 1;break; // 01110111 S1按下
case 0xB7: key_value = 5;break; // 10110111 S5按下
case 0xD7: key_value = 9;break; // 11010111 S9按下
case 0xE7: key_value = 13;break; // 11100111 S13按下
}
}
while(KEY_MATRIX_PORT != 0xF7); // 等待按键松开

KEY_MATRIX_PORT = 0xFB; // 11111011 第二列赋值为0,其余全为1
if(KEY_MATRIX_PORT != 0xFB) // 判断第二列按键是否按下
{
delay_10us(1000); // 消抖
switch(KEY_MATRIX_PORT) // 保存第二列按键按下后的键值
{
case 0x7B: key_value = 2;break; // 01111011 S2按下
case 0xBB: key_value = 6;break; // 10111011 S6按下
case 0xDB: key_value = 10;break; // 11011011 S10按下
case 0xEB: key_value = 14;break; // 11101011 S14按下
}
}
while(KEY_MATRIX_PORT != 0xFB); // 等待按键松开

KEY_MATRIX_PORT = 0xFD; // 11111101 第三列赋值为0,其余全为1
if(KEY_MATRIX_PORT != 0xFD)
{
delay_10us(1000); // 消抖
switch(KEY_MATRIX_PORT) // 保存第三列按键按下后的键值
{
case 0x7D: key_value = 3;break; // 01111101 S3按下
case 0xBD: key_value = 7;break; // 10111101 S7按下
case 0xDD: key_value = 11;break; // 11011101 S11按下
case 0xED: key_value = 15;break; // 11101101 S15按下
}
}
while(KEY_MATRIX_PORT != 0xFD); // 等待按键松开

KEY_MATRIX_PORT = 0xFE; // 11111110第四列赋值为0,其余全为1
if(KEY_MATRIX_PORT != 0xFE)
{
delay_10us(1000); // 消抖
switch(KEY_MATRIX_PORT) // 保存第四列按键按下后的键值
{
case 0x7E: key_value = 4;break; // 01111110 S4按下
case 0xBE: key_value = 8;break; // 10111110 S8按下
case 0xDE: key_value = 12;break; // 11011110 S12按下
case 0xEE: key_value = 16;break; // 11101110 S16按下
}
}
while(KEY_MATRIX_PORT != 0xFE); // 等待按键松开

return key_value;
}

/********************************************************************
*函数名: key_matrix_flip_scan
*函数功能:使用线翻转扫描方法,检测矩阵按键是否按下,按下则返回对应键值
*输入:无
*输出: key_value:1-16,对应S1-S16键,
0:按键未按下
*********************************************************************/
u8 key_matrix_flip_scan()
{
static u8 key_value = 0;

KEY_MATRIX_PORT = 0x0F; // 给所有行赋值为0,列全为1
if(KEY_MATRIX_PORT != 0x0F) // 判断按键是否按下
{
delay_10us(1000); // 消抖
if(KEY_MATRIX_PORT != 0x0F)
{
// 测试列
KEY_MATRIX_PORT = 0x0F; // 00001111 行全为0,列全为1
switch(KEY_MATRIX_PORT) // 保存行为0,按键按下后的列值
{
case 0x07: key_value = 1;break; // 00000111 第一列按下
case 0x0B: key_value = 2;break; // 00001011 第二列按下
case 0x0D: key_value = 3;break; // 00001101 第三列按下
case 0x0E: key_value = 4;break; // 00001110 第四列按下
}
// 测试行
KEY_MATRIX_PORT = 0xF0; // 11110000 行全为1,列全为0
switch(KEY_MATRIX_PORT) // 保存列为0,按键按下后的键值
{
case 0x70: key_value = key_value;break; // 01110000 第一行按下
case 0xB0: key_value = key_value + 4;break; // 10110000 第二行按下
case 0xD0: key_value = key_value + 8;break; // 11010000 第三行按下
case 0xE0: key_value = key_value + 12;break; // 11100000 第四行按下
}
while(KEY_MATRIX_PORT != 0xF0); // 等待按键松开
}
}
else
{
key_value = 0;
}

return key_value;
}

void main()
{
u8 key = 0;
while(1)
{
// key = key_matrix_ranks_scan(); // 行列扫描
key = key_matrix_flip_scan(); // 线翻转扫描
if(key != 0)
SMG_A_DP_PORT = ~gsmg_code[key - 1]; // 得到的按键值减一换算成数组下标对应0-F段码
}
}

  行列式扫描原理比较简单,与独立式按键操作类似,即给每一列赋值0,此时的矩阵按键就被分割成独立按键,然后再判断每一列中的按键按下情况,并返回对应的键值。如此循环4组,就可将4列4行按键按下键值全部得到。