给机器人加个操作面板,或者给柜子做一把口令锁,绕不开同一件事:怎么用最少的引脚读一大堆按键。薄膜矩阵键盘就是干这个的,4x4 一共 16 个键,只吃 8 根线。这篇把扫描原理、接线和密码输入代码一次理顺,参数与代码逐项对照 Last Minute Engineers(教程 2026 年 1 月更新)和 ArduinoGetStarted 两家的页面核过,新手照着做能直接跑通。

16 个键为什么只要 8 根线
先算一笔账。如果 16 个键每个都当独立按键接,按每键一个引脚加公共地的接法,要吃掉 17 个引脚,UNO 根本供不起。排成 4 行 4 列的矩阵之后,每个键正好压在一个行线和一个列线的交叉点上,整块键盘只要 8 根线:4 根行线加 4 根列线。
这种用行列交叉复用引脚的思路叫多路复用(Multiplexing)。代价是程序不能一次判断哪个键被按下,得靠逐行扫描去定位,这也是很多人第一次接键盘时觉得"代码怎么这么绕"的成因。好在扫描逻辑库都封装好了,先把原理看懂,后面用起来就踏实。

常见的是 4x3(12 键)和 4x4(16 键)两种,布局和电话键盘一致,上手没有学习成本。薄膜键盘还防水、便宜、耐按,做机器人面板或者门禁都合适。键盘背面引出一条杜邦母座排线,正面朝自己时,行引脚在左、列引脚在右。
扫描是怎么回事:逐行拉低,读列线
扫描的分工很明确:行线接 Arduino 的输出脚,列线接输入脚并启用内部上拉(INPUT_PULLUP),没有按键时列线稳定读高电平。单片机每次只把一行拉低、其余行保持高,然后逐个读列线;如果某根列线也变低了,说明这根行线和这根列线被某个键接通了,交叉点上的那个键就是被按下的键。
举个例子:程序拉低第 2 行,接着发现第 2 列也是低电平,那按下的就是行 2 列 2 交叉点上的"5"。每检测到一次按键,程序等键松开后再去 keymap 数组里查表,把行列坐标翻译成对应的字符,比如'4'、'A'或者'#'。整个过程几毫秒跑一轮,人手再快也感觉不到延迟。

顺带看一眼薄膜键盘的内部结构,一共六层:最上面是聚酯材质的图形面层,下面是负责"咔哒"手感的金属弹片,再往下是上电路层、隔层、下电路层,最后是背胶。按下一个键,实际是弹片把上下两层的导电触点压到了一起。
接线:一条杜邦排线解决
接线几乎不用动脑子,因为键盘引脚顺序和 Arduino 数字脚正好一一对应。从键盘 1 脚接 D9 开始,往后依次往下排就行。对照表如下:
| 键盘引脚 | Arduino 引脚 |
|---|---|
| R1(行1) | D9 |
| R2(行2) | D8 |
| R3(行3) | D7 |
| R4(行4) | D6 |
| C1(列1) | D5 |
| C2(列2) | D4 |
| C3(列3) | D3 |
| C4(列4) | D2 |
4x3 键盘少一列,C4 那一行对应关系直接去掉即可,程序里把列数改成 3。想接得整齐,用一条 8 针公对母杜邦排线最省事,不容易插错位;打算长期跑的项目,ArduinoGetStarted 的教程还建议加一块螺丝端子扩展板,把每根线拧在螺丝底下,比杜邦线扛折腾。


装库与按键检测: getKey 一把梭
扫描逻辑不用自己写,装 Keypad 库就行。Arduino IDE 左侧点开库管理器,搜 keypad,认准 Mark Stanley、Alexander Brevig 写的那个,点安装。

下面这份 4x4 例程从底稿教程原样搬来,逐字符没动过,唯一改动是头文件写法:站点编辑器会把尖括号头文件吞掉,所以按 Arduino 等价写法换成了双引号。
#include "Keypad.h"
const byte ROWS = 4; //four rows
const byte COLS = 4; //four columns
char keys[ROWS][COLS] = {
{ '1', '2', '3', 'A' },
{ '4', '5', '6', 'B' },
{ '7', '8', '9', 'C' },
{ '*', '0', '#', 'D' }
};
byte rowPins[ROWS] = { 9, 8, 7, 6 }; //connect to the row pinouts of the keypad
byte colPins[COLS] = { 5, 4, 3, 2 }; //connect to the column pinouts of the keypad
//Create an object of keypad
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
void setup() {
Serial.begin(9600);
}
void loop() {
char key = keypad.getKey(); // Read the key
// Print if key pressed
if (key) {
Serial.print("Key Pressed : ");
Serial.println(key);
}
}
烧录进板子,打开串口监视器,波特率设 9600,按哪个键屏幕上就打出哪个字符。代码骨架就四件事:keys 二维数组按键盘实际布局填字符,做计算器的话可以换成加减乘除;rowPins、colPins 两个数组按接线顺序填引脚号,顺序错了按键就对不上号;Keypad 构造函数把这几样收进去生成对象;loop 里 getKey() 不停轮询,有键按下就返回字符,没有就返回 0。

库还有几个函数值得记:waitForKey() 会阻塞程序死等一个键,菜单场景以外慎用;getState() 返回键的四种状态 IDLE、PRESSED、RELEASED、HOLD,配 keyStateChanged() 能做长按短按;setDebounceTime() 设消抖毫秒数,防止一次按压被算成两下;setHoldTime() 定长按阈值;addEventListener() 则挂个回调,有键事件就自动触发。
密码输入:两个功能键把按键串成口令
键盘最经典的用法就是口令锁。约定两个功能键:* 键清空重输,# 键提交比对,其余键都算密码字符。按下普通键就往字符串末尾追加,按下 # 就拿整串和预设密码比对,然后清空重来。
下面这份密码例程同样原样搬运自底稿教程,预设密码是"1234":
/*
* Created by ArduinoGetStarted.com
*
* This example code is in the public domain
*
* Tutorial page: https://arduinogetstarted.com/tutorials/arduino-keypad
*/
#include "Keypad.h"
const int ROW_NUM = 4; //four rows
const int COLUMN_NUM = 3; //three columns
char keys[ROW_NUM][COLUMN_NUM] = {
{'1','2','3'},
{'4','5','6'},
{'7','8','9'},
{'*','0','#'}
};
byte pin_rows[ROW_NUM] = {9, 8, 7, 6}; //connect to the row pinouts of the keypad
byte pin_column[COLUMN_NUM] = {5, 4, 3}; //connect to the column pinouts of the keypad
Keypad keypad = Keypad( makeKeymap(keys), pin_rows, pin_column, ROW_NUM, COLUMN_NUM );
const String password = "1234"; // change your password here
String input_password;
void setup(){
Serial.begin(9600);
input_password.reserve(32); // maximum input characters is 33, change if needed
}
void loop(){
char key = keypad.getKey();
if (key){
Serial.println(key);
if(key == '*') {
input_password = ""; // clear input password
} else if(key == '#') {
if(password == input_password) {
Serial.println("password is correct");
// DO YOUR WORK HERE
} else {
Serial.println("password is incorrect, try again");
}
input_password = ""; // clear input password
} else {
input_password += key; // append new character to input password string
}
}
}
这份例程用的是 4x3 键盘,换 4x4 只改列数、keymap 和列引脚数组三处。串口里输入 123456 按 #,会回"password is incorrect, try again";输入 1234 按 #,回"password is correct",真正做锁的时候把注释那行换成开锁动作就行。input_password.reserve(32) 是给字符串预分配内存,避免频繁拼接产生内存碎片,这个小细节很多自己写口令的例子都漏了。
整体看,矩阵键盘是入门性价比极高的一块外设:接线十分钟,代码十几行,扫描原理还顺便把"复用引脚"这个思想讲透了。往后想升级,把提交动作接到继电器驱动电磁锁,或者把按下的键显示到 LCD 上做成完整面板,都是顺手的事。











照着接了一遍,8根杜邦线插完直接就出字了,比想象中顺利。就是C4那根一开始插错列,按出来全对不上号,回来看引脚表才找到问题。
getKey() 轮询确实省事,不过waitForKey()那句提醒很有必要,我上次用它直接把电机控制卡死了,菜单场景慎用是真的。
密码那个例子改改就能接到电磁锁上了,实用
扫描那段写得很清楚,之前一直纳闷为什么键盘就8根线能管16个键,看到行列交叉那张图才反应过来,本质上就是数学里的组合问题。把行拉低读列这个思路,其实跟LED点阵屏是一个道理,学会一个另一个就通了。
reserve(32)这个细节提得好,String拼接确实容易炸内存,新手踩过一次就长记性了