ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ESP32矩阵键盘实战:16个按键只用8个GPIO,扫描原理与代码详解

ESP32矩阵键盘实战:16个按键只用8个GPIO,扫描原理与代码详解 刚入坑ESP32的时候大家最先遇到的一个问题几乎都一样想在一台小项目上多塞几个按键板子引脚不够用。特别是做菜单选择、密码输入这类交互功能动不动就要十几个按键如果每个按键都老老实实占一个GPIO用不了几个按键就得换更大封装的开发板。我第一次在ESP32上做4x4矩阵键盘时也被“16个按键只占8个引脚”这个说法吸引住了逻辑上想不通物理上不知道从哪接代码更不知道怎么扫描。实际动手之后才发现所谓矩阵键盘说穿了就是把按键摆成一张“行和列交叉的小桌子”通过分时扫描的办法用较少的引脚换回大量按键。这篇文章就从这个思路出发把硬件接线、扫描原理、代码实现、排错经验一次讲透特别适合零基础、第一次用ESP32接矩阵键盘的朋友照着做。1. 矩阵键盘到底是怎么“省引脚”的1.1 普通接法和矩阵接法一笔账算清楚先看最直观的方案如果16个按键每一个都直接接到一个GPIO上那至少需要16个输入引脚而且在ESP32这类开发板上你还得考虑引脚是否支持中断、是否有内部上拉真正能舒舒服服用的引脚打个折扣16个可能就得顶掉大半的引脚资源非常心疼。矩阵键盘的思路是把按键分成4行和4列。行和列交叉的位置放按键物理上按下某个键就等于把对应行和对应列的那根线接通了。这样一来引脚从16个降成8个4个行引脚加4个列引脚。16个按键8根线节省了一半资源。如果再往大扩展5行5列的键盘就是25个按键用10个引脚键盘规模越大矩阵接法越划算这个账非常好算。不过也要说清楚矩阵键盘不是真的“同时检测”16个按键。它采用逐行扫描的工作方式同一时间只判断一个行上的按键情况扫描速度足够快的话人按下去的感觉就是所有按键同时在响应。1.2 扫描原理把键盘当成一张坐标表用一个生活化的例子来理解教室里有4排座位每排4个座位一共16个座位。老师想确认哪个座位坐人了不会同时去看16个座位而是先看第一排挑右边的同学再看第二排、第三排。扫描的过程就和这个流程几乎一样。具体到电路上我们把4个行引脚全部设为输出4个列引脚全部设为带上拉的输入。第一步把第0行拉低到低电平另外三行保持高电平然后读取所有列引脚的电压值如果第一列读到低电平说明“第0行、第1列”这个交叉点上有按键被按下也就是键盘上的1号键。第一行检查完之后把第0行恢复高电平再把第1行拉低重复刚才的动作。四行全部扫描一遍之后回到第0行继续循环。这个过程在代码里就是嵌套两层循环外层循环切换行内层循环读列。因为ESP32主频很高整个扫描一轮的时间可以控制在几毫秒内用户感觉不到延迟。原则上这个扫描过程是“逐行分时复用”不是真正的并行检测但在实际使用中响应速度完全够用。注意列引脚一定不能设为普通输入模式必须开启内部上拉或外部加上拉电阻否则按键没按下时列引脚的电平是悬空的读取结果会随机乱跳。ESP32的pinMode(pin, INPUT_PULLUP)就是用来干这件事的。2. 动手接线前先把物料和引脚选对2.1 需要准备哪些东西以最常见的4x4薄膜矩阵键盘为例物料清单非常简洁一块带有USB口的ESP32开发板我用的是最常见的DevKit V138引脚版本一块4x4薄膜矩阵键盘上面印着1~9、0、*、#、A、B、C、D8根公对公杜邦线或者公对母杜邦线一个面包板可选但建议至少用来整理线和观察接点薄膜矩阵键盘背面通常贴有标签比如R1到R4和C1到C4或者用1到8的数字引脚标识。不同厂家偶尔有差异买回来第一件事是确认引脚顺序不要想当然。没有说明书的话用万用表的通断档去量哪个引线和哪个按键之间存在连通关系一量就清楚了。2.2 ESP32引脚选择的几个硬规则很多人会问“8个引脚随便选可以吗”我的答案是尽量别随便选因为ESP32并非每个引脚都能当普通数字口随便用。有几个坑是新手最容易踩的。GPIO 6到GPIO 11是连接外部Flash存储器的引脚默认状态下不引出来也不建议当普通IO用用了可能直接导致开发板无法启动或程序行为异常。GPIO 34到GPIO 39是输入专用引脚没有内部上拉和下拉功能如果用在矩阵键盘的列引脚上就必须外接电阻比较麻烦。我习惯把它们留作读ADC模拟量之类的用途。GPIO 0、GPIO 2、GPIO 12、GPIO 15在开发板上往往连接着启动模式选择电阻或LED指示灯虽然日常使用没有问题但使用前最好查一下自己那块板子的原理图避免和启动冲突。GPIO 16、GPIO 17在部分集成PSRAM的模组上有特殊用途普通DevKit上一般没问题但为了稳妥初学阶段不建议优先选它们。我实际推荐一套非常稳的组合行引脚用GPIO 13、14、27、26列引脚用GPIO 25、33、32、15。这套组合在大多数ESP32 DevKit上都工作良好而且避开了ADC2冲突、启动模式敏感引脚等主要雷区。2.3 实际接线顺序与首轮验证接线时把行和列分开别让线缠成一团。推荐这么接键盘R1接GPIO13R2接GPIO14R3接GPIO27R4接GPIO26键盘C1接GPIO25C2接GPIO33C3接GPIO32C4接GPIO15接好线之后别急着写代码。第一步先用万用表的通断档手动量键盘万用表一端接R1另一端依次碰C1、C2、C3、C4按下对应按键时万用表会“嘀”一声。这一步的目的是确认键盘引脚顺序也顺便确认杜邦线有没有接反。我遇到过两次“代码完全没问题但按键全部不响应”的情况最后发现都是杜邦线和薄膜键盘母座接触不良稍微碰一下就脱落。薄膜键盘引脚的连接器很薄接的时候要用力插到底必要时可以用胶带稍微固定。确认硬件通路没问题之后再进入代码阶段。3. 让16个按键工作起来的完整代码3.1 基础扫描代码先把键盘“激活”用Arduino框架写ESP32代码是最快的方式。新建一个工程文件复制下面的代码编译上传后打开串口监视器波特率设定为115200按下某一个按键串口就会打印出对应字符。#include Arduino.h const int ROW_PINS[4] {13, 14, 27, 26}; const int COL_PINS[4] {25, 33, 32, 15}; const char KEY_MAP[4][4] { {1, 2, 3, A}, {4, 5, 6, B}, {7, 8, 9, C}, {*, 0, #, D} }; void setup() { Serial.begin(115200); for (int i 0; i 4; i) { pinMode(ROW_PINS[i], OUTPUT); digitalWrite(ROW_PINS[i], HIGH); pinMode(COL_PINS[i], INPUT_PULLUP); } } char scanKey() { for (int r 0; r 4; r) { digitalWrite(ROW_PINS[r], LOW); // 当前行拉低 for (int c 0; c 4; c) { if (digitalRead(COL_PINS[c]) LOW) { delay(20); // 简单防抖 if (digitalRead(COL_PINS[c]) LOW) { digitalWrite(ROW_PINS[r], HIGH); return KEY_MAP[r][c]; } } } digitalWrite(ROW_PINS[r], HIGH); // 当前行恢复高电平 } return 0; // 没有按键返回空字符 } void loop() { char key scanKey(); if (key ! 0) { Serial.print(Key: ); Serial.println(key); } delay(10); }这段代码是最典型的“拉低行、读列”扫描。行引脚依次输出低电平其余时间保持高电平列引脚通过输入上拉保持高电平一旦读到低电平就说明当前扫描行和该列的交叉按键被按下。KEY_MAP数组则起到“翻译官”作用把行号列号转换成实际的按键字符。这里有一个很容易踩的坑扫描完一行之后如果代码里没有digitalWrite(ROW_PINS[r], HIGH)这一步下一轮扫描时行输出可能处于不明确的状态干扰其他行的判断。所以每次扫描完当前行之后务必把它恢复成高电平再切换下一行。3.2 加点防抖按键不听话的元凶薄膜按键的内部结构是两片金属弹片接触按下和松开的瞬间会产生几毫秒到十几毫秒的电平抖动看起来就像按键被反复按了好几下。如果不处理一次按键可能会被识别成多次。代码里面用了最经典的软件防抖方案第一次读到低电平之后delay(20)等待20毫秒然后重新读一次如果还是低电平就认定确实是按键被按下。有些人觉得delay会阻塞程序确实如此。但考虑到4行4列全部扫描完需要的时间很短在最简单的场景下这样用没有大问题。更好的方案是“读到电平变化后先记录时间到时间后再确认”这个需要用millis()实现非阻塞防抖。初学阶段先理解delay(20)的防抖逻辑再优化成非阻塞版本会顺畅很多。3.3 状态机思路只让按键“触发”一次基础代码有一个小烦恼手指一直按着某个键不松开循环扫描会无数次返回同一个按键值串口会疯狂打印。很多实际项目不希望出现这种重复触发比如密码锁你按住“5”不放不应该连续输入几十个“5”。做法是记录上一次按键状态。只有当前按键是按下状态、并且上一次是松开状态才认为是“一次新的按下”然后返回键值。反过来如果按键一直保持按下就返回0不产生新的触发。实现时可以这样改在loop函数里维护一个全局的lastKeyState变量char lastKey 0; void loop() { char key scanKey(); if (key ! 0 key ! lastKey) { Serial.print(Key: ); Serial.println(key); } lastKey key; delay(10); }这段逻辑其实就是一个最简单的状态机检测到新字符才处理重复字符直接忽略。做密码锁、菜单选择这类项目时这个状态判断几乎是标配。4. 常见故障排查与调优实录4.1 按键乱跳、串键、没反应的排查顺序矩阵键盘的问题表象五花八门但根因基本就那么几类按照下面的顺序排查大部分问题都能解决。完全没有一个按键有反应先检查行引脚和列引脚是否接反以及杜邦线是否插牢。再检查代码里的引脚定义是否和实际接线一致。某一整行或某一整列全部没反应大概率是那一根杜邦线接触不良换一根线试试或者直接用万用表通断档量。按键值和实际按键对不上比如按下“7”却显示“A”这是KEY_MAP的数组顺序和键盘丝印不一致稍微调整数组里字符的位置即可。一个按键触发两次或按键按下后乱跳优先考虑防抖不够把防抖延时从20毫秒增加到30甚至50毫秒再看是否解决。还有一类比较隐蔽的问题是“鬼键”现象因为同时按下了多个按键导致扫描时检测到本来没有按下的交叉点。普通学习项目用的时候只要不同时按太多键基本不会触发鬼键不用过度担心。4.2 引脚选择里的隐藏坑位矩阵键盘使用8个引脚如果引脚选得不合适即使代码写得没问题也会遇到各种奇怪现象。我自己的实际教训是不要把列引脚放在GPIO 34到GPIO 39上因为这一组引脚不支持内部上拉需要手工给每个列引脚焊上10k欧姆的上拉电阻到3.3V否则读取状态不稳定。能用内部上拉就尽量用内部上拉外部电阻既多几根线又增加焊点出问题的可能性。另外ADC2相关引脚在连接WiFi后如果同时做模拟量读取有可能会出现冲突因为ADC2和WiFi射频在ESP32内部会争夺部分资源。如果项目里既要连WiFi又要接矩阵键盘最好把行扫描引脚放在普通数字IO上不要把有模拟量需求的引脚混进来。4.3 用串口监视器快速定位问题我给自己的代码加的调试习惯是把扫描过程中的行号列号和读到的电平都打印出来而不仅仅打印最终键值。比如在scanKey函数的循环里临时加一个Serial.printf(r%d c%d val%d\n, r, c, val);看一眼哪一行的哪一列读到了异常电平问题范围一下就缩小了。调试完成之后再把多余的打印注释掉。毕竟串口打印本身会占用处理器时间高频打印还可能影响按键扫描的实时性。我用过几次这个方法基本上15分钟以内就能定位“按下去不识别”是接线问题还是逻辑问题。5. 把4x4矩阵键盘真正用进项目5.1 适合矩阵键盘的三种典型场景矩阵键盘在真实项目中的用途非常集中最适合的是中小型交互面板。第一个场景是密码锁。把矩阵键盘当成密码输入设备用户输入4到6位密码通过对比校验后控制继电器或舵机开锁。ESP32的GPIO足够驱动一个继电器再配一台小舵机一个简单门禁项目就成型了。这个场景需要用到前面说的“按下才触发一次”的状态检测不然按住密码数字的时候会重复输入。第二个场景是菜单导航。ESP32接了OLED显示屏屏幕上有菜单选项键盘的“8”和“2”做成上下移动“6”或“D”做成确认“#”或“A”做成返回。矩阵键盘比单独买导航按键模块便宜布局也更灵活。用矩阵键盘加OLED屏做的智能控制面板在很多DIY项目里都出现过。第三个场景是宏键盘。把矩阵键盘连接到电脑配合ESP32模拟USB HID设备ESP32-S3支持原生USB外设每个按键可以映射成一串组合命令。虽然不是零基础的第一天就能做完但思路很清晰矩阵键盘负责采集信号USB HID逻辑负责发送键盘事件。很多桌面小键盘DIY项目就是这样实现的。5.2 功能升级长按、组合键、按下松开的完整判断按键逻辑做到后面光有防抖还不够还得区分短按、长按和组合键。简单说扫描的时候不是只记录“按键是不是按下”而要额外记录“这个按键按下了多久”。长按判断可以这样设计当某按键被识别为按下后记录按下时间戳在循环中持续扫描如果发现按键一直保持按下且时长超过800毫秒就进入长按分支。短按功能则在松开时触发。这样做需要把按键检测从“扫描一次返回一次键值”改成“扫描更新状态由状态决定事件”本质上就是状态机设计。ESP32有足够的计算性能这套逻辑跑起来非常轻松。组合键稍微复杂一点因为矩阵扫描天然串行我们得在每一轮扫描中同时记录多个按键的状态生成一个16位的按键状态字。先确定哪一行哪一列状态为低然后做一个逻辑或操作把多个按键信息合并。状态字比对之后就可以识别“同时按下1和A”这类组合操作。5.3 还想继续扩展给你几个方向矩阵键盘还能扩展出很多变体比较大的方向有三个。用8个引脚接入更大的键盘比如6行6列总共36个按键还是只用12个引脚。扫描原理不变只需要在数组里多塞几组数据。换成I2C接口的键盘扩展芯片比如TCA8418或MCP23017用2个引脚就能管理几十个按键。这个方案适合按键数量特别多、或者GPIO极度紧张的项目。给每个按键增加二极管做成二极管矩阵可以实现真正的N键无冲突适合需要同时按多个键的场景。标准4x4键盘按下3个键以上时可能出现鬼键加二极管之后就能规避。我个人在实际操作中的体会是矩阵键盘是嵌入式开发里性价比极高的一种交互方案原理不复杂代码不长但涉及的知识点非常细引脚分配、上拉电阻、扫描时序、防抖、状态机每一个点都能在后续学习里复用。尤其是“行扫描、列读取”的思想在做LED点阵屏、传感器轮询、多设备通信时都会再次遇到。最后分享一个小技巧接线之前先在每根杜邦线上贴一个写有引脚名称的小标签比如R1、C3之类。4x4矩阵键盘的8根线在项目后期经常会改动位置没有标签全靠记忆的话调试起来会非常痛苦。我因为这个问题至少多折腾过一个晚上。先花两分钟做标记后面能省下两小时。祝大家一次点亮矩阵键盘。
返回列表