1. 从零开始:为什么我们需要矩阵按键?
如果你玩过单片机或者自己DIY过一些小玩意儿,比如一个简单的计算器、一个游戏手柄,或者一个带菜单的智能家居控制器,那你肯定遇到过按键不够用的问题。一个单片机,比如我们常见的51系列或者STM32,它的GPIO(通用输入输出)引脚是有限的。一个引脚接一个按键,16个按键就要16个引脚,这太“奢侈”了,而且电路板会变得非常杂乱。
这时候,矩阵按键就闪亮登场了。它的核心思想特别像我们小时候玩的“棋盘找点”游戏。我们把按键排列成行和列,比如4行4列,总共16个按键,却只需要8个引脚(4行+4列)。每个按键都位于某一行和某一列的交叉点上。通过控制行和列的电平状态,我们就能“定位”到是哪个按键被按下了。这就像给你一个坐标(第2行,第3列),你就能在棋盘上找到唯一的那个点。
在实际项目中,我经常用矩阵按键来做菜单导航、密码输入、参数调节。比如我之前做的一个温控器,就用了一个4x4的矩阵键盘来设置目标温度、切换模式、查看历史记录,只用了一块小小的单片机就搞定了所有交互,省下来的引脚可以接显示屏、传感器和继电器,非常划算。
那么,怎么去“扫描”这个矩阵,知道哪个键被按下了呢?这就是我们今天要深入聊的两种经典方法:行列扫描法和反转扫描法。别看名字有点技术范儿,其实原理就像两种不同的“点名”方式,各有各的适用场景和脾气。我会结合我踩过的坑和实战经验,把它们的里里外外都给你讲明白,并附上可以直接抄作业的代码。
2. 行列扫描法:最直观的“逐列点名”
行列扫描法,也有人叫它“列扫描法”或“行扫描法”,取决于你先动行还是先动列。这是最容易被想到、也最直观的一种方法。它的工作方式,就像一个老师拿着花名册,按照学号顺序一个一个点名。
2.1 原理与工作流程:像查字典一样找按键
我们以最常见的“列扫描”为例来拆解这个过程。假设我们有一个4行4列的矩阵。
第一步:初始化设置。 我们把所有行引脚设置为输出模式,并且初始化为低电平(0)。把所有列引脚设置为输入模式,并且使能内部上拉电阻(这样默认就是高电平1)。你可以把“行”想象成广播喇叭,“列”想象成收音麦克风。
第二步:逐列扫描。 我们开始“点名”了。老师(单片机)说:“第一列的同学请起立!” 怎么让第一列“起立”呢?就是把第一列对应的引脚拉成低电平(0),其他列保持高电平(1)。这时候,如果第一列上有某个按键被按下了,比如位于第2行、第1列的按键,那么电流就会从第2行的输出低电平(0),通过被按下的按键,流到被拉低的第一列(0)。对于单片机来说,它去读取所有行引脚的电平。
第三步:读取行状态。 因为第2行本身输出就是低电平(0),而按键按下后,第一列也被我们主动拉低了(0),所以第2行引脚的电平状态不会改变(还是0)。但是,我们初始化时所有行都是输出低电平,怎么判断是哪一行呢?这里有个关键:当某一列被拉低后,我们依次去检查每一行引脚的电平。如果发现某一行读到的也是低电平(0),那就说明这个行和当前被拉低的列交叉点上的按键被按下了! 因为只有按键按下,才会把行的低电平“传导”到列上,让我们在输入模式下读到低电平。
第四步:循环与复位。 检查完第一列的所有行之后,我们把第一列恢复为高电平(1),然后让第二列“起立”(拉低),重复上述检查过程,直到所有列都被扫描一遍。
这个过程写成代码逻辑就是两层循环:外层循环遍历每一列,内层循环遍历每一行。我刚开始学的时候,总觉得电流方向有点绕,后来画了几次图才明白:核心在于创造一条“低电平通路”。让被按下的按键,成为连接输出低电平的行和输入被拉低的列的那座“桥”。
2.2 代码实战与“坑点”提醒
下面是一个针对通用单片机(比如STM32、Arduino)的简化示例代码,假设我们已经定义了行和列对应的引脚。
#include <stdbool.h>
// 假设是4x4矩阵
#define NUM_ROWS 4
#define NUM_COLS 4
// 这里需要替换成你实际使用的引脚,例如 {GPIO_PIN_0, GPIO_PIN_1, ...}
const uint16_t row_pins[NUM_ROWS] = {ROW0_PIN, ROW1_PIN, ROW2_PIN, ROW3_PIN};
const uint16_t col_pins[NUM_COLS] = {COL0_PIN, COL1_PIN, COL2_PIN, COL3_PIN};
void keypad_init(void) {
// 初始化行引脚为推挽输出,并置低
for (int i = 0; i < NUM_ROWS; i++) {
pinMode(row_pins[i], OUTPUT);
digitalWrite(row_pins[i], LOW);
}
// 初始化列引脚为上拉输入(默认高电平)
for (int i = 0; i < NUM_COLS; i++) {
pinMode(col_pins[i], INPUT_PULLUP);
}
}
uint8_t keypad_scan(void) {
uint8_

1万+

被折叠的 条评论
为什么被折叠?



