4x4 矩阵键盘
今天我们来学习 4x4 矩阵键盘,并最终实现一个密码开锁的功能。
实验原理
薄膜按键,是一块带触点的 PET 薄片,用在 PCB、FPC 等线路上作为开关使用,在使用者与仪器之间起到一个重要的触感型开关的作用。与传统的硅胶按键相比,薄膜按键具有更好的手感、更长的寿命,可以间接的提高使用导电膜的各类型开关的生产效率。
薄膜按键的工作原理很好理解,薄膜上的触点位于 PCB 板上的导电部位,当按键受到外力按压时,触点的中心点下凹,接触到 PCB 上的线路,从而形成回路,电流通过,整个产品就得正常工作。
这个键盘有 16 个按键,如果 16 个按键均为独立按键的话,需要占用 16 个 IO 口,对于我们的开发板来说还是可以接受的,但是如果有 64 个按键,那单片机的 IO 口就完全不能能满足我们的需求,因此,就出现了矩阵键盘将这 8 根线连接到单片机的 8 个 IO 口上,通过程序扫描键盘就可检测 16 个键,如果我们想实现 64 个按键的话就只需要用到 16 个 IO口,可以参考 LED 点阵屏。
无论是独立键盘还是矩阵键盘,单片机检测其是否被按下的依据都一样,即检测与该键对应的 IO 口是否为低电平,独立键盘有一端固定为低电平,此种方式编程比较简单。而矩阵键盘两端都与单片机 IO 口相连,因此在检测时需编程通过单片机1/0口送出低电平,检测方法有多种,最常用的是 行列扫描
和 线翻转法
。
行列扫描法
:检测时,先送一列为低电平,其余几列全为高电平(确定列数),然后立即轮流检测一次各行是否有低电平,若检测到某一行为低电平(确定行数),则便可确认当前被按下的键是哪一行哪一列的,用同样方法轮流送各列一次低电平,再轮流检测一次各行是否变为低电平,这样即可检测完所有的按键,当有键被按下时便可判断出按下的键是哪一个键。当然,也可以将行线置低电平,扫描列是否有低电平,从而达到整个键盘的检测;线翻转法
:使所有行线为低电平时,检测所有列线是否有低电平,如果有,就记录列线值:然后再翻转,使所有列线都为低电平,检测所有行线的值,由于有按键按下,行线的值也会有变化,记录行线的值。从而就可以检测到全部按键
硬件电路设计
物料清单(BOM 表):
材料名称 | 数量 |
---|---|
4*4 矩阵键盘 | 1 |
LCD1602 液晶屏 | 1 |
杜邦线(跳线) | 若干 |
面包板 | 1 |
矩阵键盘从左到右依次接 D13、D12、D14、D27、D26、D25、D33、D32。
LCD 1602 接 5V 电源,SCL 接 D22,SDA 接 D21。
软件程序设计
1. 在串口监视器中显示触发的按键
我们可以先把按键值打印在串口监视器中,代码如下:
// 定义行引脚
int row_pins[4] = {13, 12, 14, 27};
// 定义列引脚
int col_pins[4] = {26, 25, 33, 32};
// 按下的按键
char key;
void setup() {
// 设置通信波特率
Serial.begin(9600);
// 行引脚设置为输入
for (int i=0;i<4;i++) {
pinMode(row_pins[i], INPUT_PULLUP);
}
// 列引脚设置为输出
for (int i=0;i<4;i++) {
pinMode(col_pins[i], OUTPUT);
// 初始化为高电平
digitalWrite(col_pins[i], HIGH);
}
}
char read_keypad() {
// 定义键盘按键布局
char keys[4][4] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
// 行列扫描法
for (int j=0;j<4;j++) {
// 将当前列设置为低电平
digitalWrite(col_pins[j], LOW);
for (int i=0;i<4;i++) {
// 检测行输入引脚状态, 检测到低电平,说明按键被按下,则返回该按键值
if (!digitalRead(row_pins[i])) {
// 将该行恢复为高电平
digitalWrite(col_pins[j], HIGH);
return keys[i][j];
}
}
// 将该行恢复为高电平
digitalWrite(col_pins[j], HIGH);
}
return NULL;
}
void loop() {
// 保存读取到的按键值
key = read_keypad();
if (key) {
Serial.printf("检测到按键按下: %c\n", key);
}
delay(200);
}
2. 与 LCD1602 实现密码锁功能
最后,我们就可以再使用 LCD1602 屏幕显示我们输入的内容,并且校验输入的密码是否正确,
在这里,我们会需要用到一些新的函数:
int strcmp(const char* str1, const char* str2)
:判断两个字符串的大小,相等则返回 0;void *memset(void *s, int v, size_t n)
:数组初始化函数,这里 s 可以是数组名,也可以是指向某一内在空间的指针;v 为要填充的值,一般使用 0 初始化内存单元;n 为数组的字节大小;memset 是对大的数组或结构体进行初始化操作的最快方法,因为他是直接对内存进行操作的。strlen (const char* str)
:计算字符串 str 的长度
代码如下:
#include "LiquidCrystal_I2C.h"
// 设置 LCD1602 的地址,列数,行数
LiquidCrystal_I2C lcd(0x27,16,2);
// 定义行引脚
int row_pins[4] = {13, 12, 14, 27};
// 定义列引脚
int col_pins[4] = {26, 25, 33, 32};
// 按下的按键
char key;
// 定义密码
const char password[] = "4567";
char enteredPassword[5] = ""; // 保存用户输入的密码
void setup() {
// 设置通信波特率
Serial.begin(9600);
// 行引脚设置为输入
for (int i=0;i<4;i++) {
pinMode(row_pins[i], INPUT_PULLUP);
}
// 列引脚设置为输出
for (int i=0;i<4;i++) {
pinMode(col_pins[i], OUTPUT);
// 初始化为高电平
digitalWrite(col_pins[i], LOW);
}
// 初始化 LCD 对象
lcd.init();
// 开启背光
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Enter Password:");
}
char read_keypad() {
// 定义键盘按键布局
char keys[4][4] = {
{'1', '2', '3', 'A'},
{'4', '5', '6', 'B'},
{'7', '8', '9', 'C'},
{'*', '0', '#', 'D'}
};
// 行列扫描法
for (int j=0;j<4;j++) {
// 将当前列设置为低电平
digitalWrite(col_pins[j], LOW);
for (int i=0;i<4;i++) {
// 检测行输入引脚状态, 检测到低电平,说明按键被按下,则返回该按键值
if (!digitalRead(row_pins[i])) {
// 将该行恢复为高电平
digitalWrite(col_pins[j], HIGH);
return keys[i][j];
}
}
// 将该行恢复为高电平
digitalWrite(col_pins[j], HIGH);
}
return NULL;
}
// 检测密码
void check_password(char key) {
if (key) {
if (key == '#') {
// 输入完成,验证密码
if (strcmp(enteredPassword, password) == 0) {
lcd.clear();
lcd.print("Access granted!");
} else {
lcd.clear();
lcd.print("Access denied!");
}
delay(2000);
lcd.clear();
lcd.print("Enter Password:");
memset(enteredPassword, 0, sizeof(enteredPassword)); // 清空已输入的密码
} else {
// 添加输入的字符到密码字符串中
if (strlen(enteredPassword) < 4) {
lcd.setCursor(strlen(enteredPassword), 1);
lcd.print("*");
enteredPassword[strlen(enteredPassword)] = key;
}
}
}
delay(500);
}
void loop() {
// 获取按键信息
key = read_keypad();
// 显示并校验密码
check_password(key);
}