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 |
矩阵键盘从左到右依次接 D19、D18、D5、D17、D16、D4、D2、D15。
LCD 1602 接 5V 电源,SCL 接 D27,SDA 接 D14。
软件程序设计
这里我们需要用到 Python 的一个内置函数 enumerate()
。
enumerate()
函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在 for 循环当中。
以下是 enumerate() 方法的语法:enumerate(sequence, [start=0])
,其中的参数:
sequence
:一个序列、迭代器或其他支持迭代对象;start
:下标起始位置。
用法如下:
seasons = ['Spring', 'Summer', 'Fall', 'Winter']
for i, ele in enumerate(seasons):
print(i, ele)
这样,我们就能既获取到索引值 i
,也能获取到元素 ele
。
因此,我们可以先把按键值打印在 Shell 命令行中,代码如下:
import time
from machine import Pin
# 行引脚设置为输入
row_pins = [Pin(19, Pin.IN, Pin.PULL_UP),
Pin(18, Pin.IN, Pin.PULL_UP),
Pin(5, Pin.IN, Pin.PULL_UP),
Pin(17, Pin.IN, Pin.PULL_UP)]
# 列引脚设置为输出
col_pins = [Pin(16, Pin.OUT),
Pin(4, Pin.OUT),
Pin(2, Pin.OUT),
Pin(15, Pin.OUT)]
def read_keypad():
keys = [
['1', '2', '3', 'A'],
['4', '5', '6', 'B'],
['7', '8', '9', 'C'],
['*', '0', '#', 'D']
]
for j, col_pin in enumerate(col_pins):
col_pin.value(0) # 将当前列设置为低电平
for i, row_pin in enumerate(row_pins):
if row_pin.value() == 0: # 检测行引脚的状态
# 将当前列恢复为高电平
col_pin.value(1)
return keys[i][j] # 返回按下的按键
col_pin.value(1) # 将当前列恢复为高电平
return None # 没有按键被按下
# 循环读取键盘状态
while True:
key = read_keypad()
if key is not None:
print("按下的按键:", key)
time.sleep(0.1) # 短暂延迟
最后,我们就可以再使用 LCD1602 屏幕显示我们输入的内容,并且校验输入的密码是否正确,代码如下:
import time
from machine import Pin, SoftI2C
from libs.i2c_lcd import I2cLcd
# 行引脚设置为输入
row_pins = [Pin(19, Pin.IN, Pin.PULL_UP),
Pin(18, Pin.IN, Pin.PULL_UP),
Pin(5, Pin.IN, Pin.PULL_UP),
Pin(17, Pin.IN, Pin.PULL_UP)]
# 列引脚设置为输出
col_pins = [Pin(16, Pin.OUT),
Pin(4, Pin.OUT),
Pin(2, Pin.OUT),
Pin(15, Pin.OUT)]
# 定义硬件 I2C 控制对象
i2c = SoftI2C(sda=Pin(14), scl=Pin(27), freq=100000)
# 获取 I2C 设备地址
address = i2c.scan()[0]
# 定义 I2cLcd 对象
i2c_lcd = I2cLcd(i2c, address, 2, 16)
def read_keypad():
keys = [
['1', '2', '3', 'A'],
['4', '5', '6', 'B'],
['7', '8', '9', 'C'],
['*', '0', '#', 'D']
]
for j, col_pin in enumerate(col_pins):
col_pin.value(0) # 将当前列设置为低电平
for i, row_pin in enumerate(row_pins):
if row_pin.value() == 0: # 检测行引脚的状态
# 将当前列恢复为高电平
col_pin.value(1)
return keys[i][j] # 返回按下的按键
col_pin.value(1) # 将当前列恢复为高电平
return None # 没有按键被按下
# 正确密码
password = '123456'
# 显示的密码
pwd_input = ''
i2c_lcd.putstr('Enter Password:\n')
# 循环读取键盘状态
while True:
key = read_keypad()
if key is not None:
# 重置屏幕内容
i2c_lcd.clear()
i2c_lcd.putstr('Enter Password:\n')
if key == 'D' and pwd_input != '':
pwd_input = pwd_input[:-1]
elif key == '*':
# 校验密码
i2c_lcd.clear()
i2c_lcd.putstr('Enter Password:\n')
print(f'pwd_input: {pwd_input}, pwd: {password}')
if pwd_input == password:
i2c_lcd.putstr('Correct!')
else:
i2c_lcd.putstr('Wrong!')
# 重置 pwd_input
pwd_input = ''
# 非特殊按键则显示在屏幕上
elif key != 'D' and key !='*' and len(pwd_input) < 15:
pwd_input += key
i2c_lcd.putstr(pwd_input)
time.sleep(0.2) # 短暂延迟