4x4 矩阵键盘

罗大富 BigRich大约 6 分钟ESP32Arduino

今天我们来学习 4x4 矩阵键盘,并最终实现一个密码开锁的功能。

实验原理

薄膜按键,是一块带触点的 PET 薄片,用在 PCB、FPC 等线路上作为开关使用,在使用者与仪器之间起到一个重要的触感型开关的作用。与传统的硅胶按键相比,薄膜按键具有更好的手感、更长的寿命,可以间接的提高使用导电膜的各类型开关的生产效率。

薄膜按键的工作原理很好理解,薄膜上的触点位于 PCB 板上的导电部位,当按键受到外力按压时,触点的中心点下凹,接触到 PCB 上的线路,从而形成回路,电流通过,整个产品就得正常工作。

这个键盘有 16 个按键,如果 16 个按键均为独立按键的话,需要占用 16 个 IO 口,对于我们的开发板来说还是可以接受的,但是如果有 64 个按键,那单片机的 IO 口就完全不能能满足我们的需求,因此,就出现了矩阵键盘将这 8 根线连接到单片机的 8 个 IO 口上,通过程序扫描键盘就可检测 16 个键,如果我们想实现 64 个按键的话就只需要用到 16 个 IO口,可以参考 LED 点阵屏

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

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

硬件电路设计

物料清单(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 屏幕显示我们输入的内容,并且校验输入的密码是否正确,

在这里,我们会需要用到一些新的函数:

  1. int strcmp(const char* str1, const char* str2):判断两个字符串的大小,相等则返回 0;
  2. void *memset(void *s, int v, size_t n):数组初始化函数,这里 s 可以是数组名,也可以是指向某一内在空间的指针;v 为要填充的值,一般使用 0 初始化内存单元;n 为数组的字节大小;memset 是对大的数组或结构体进行初始化操作的最快方法,因为他是直接对内存进行操作的。
  3. 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);
}
上次编辑于:
贡献者: Luo