简介:这份资源面向单片机初学者与电子爱好者,聚焦51单片机汇编语言实现矩阵键盘扫描这一基础项目,帮助读者理解行线列线交叉检测按键的硬件原理与汇编编程方法。压缩包共1个doc文件,约283KB,内含完整程序源代码与代码块说明,涵盖变量定义、主函数初始化、按键判断与键值计算、数码管显示与移位、清零及延时子程序、共阴极段码表等模块,代码注释清晰,便于对照学习。已有569人学习下载,适合作为汇编入门与硬件I/O操作的实践参考,读者可从中掌握扫描去抖、键值计算、数码管动态显示与移位控制等关键思路,并理解循环与条件判断在嵌入式程序中的组织方式。
1. 从一块“按了没反应”的键盘说起:矩阵键盘扫描到底是什么
你手里如果有一块单片机开发板,上面通常会有几个独立按键,按一下亮一个灯,很直观。可一旦按键数量变成 16 个,你还想一个按键占一个引脚吗?引脚根本不够用。这时候就要请出矩阵键盘扫描了——它把 16 个按键排成 4 行 4 列,只用 8 根线就能全部管起来,靠“扫描”的方式一个个问过去:是你被按下了吗?
说白了,矩阵键盘扫描就是单片机用行线和列线交叉成网格,轮流给每一列送低电平,再读每一行的高低状态,从而判断哪个交叉点被按下。它解决的是“按键多、引脚少”的矛盾,适合刚接触单片机、想搞懂输入输出配合的新手。汇编语言写起来确实比 C 语言啰嗦,但正因为啰嗦,你能亲眼看到每一个电平是怎么翻转的,这份资源就是带你从零把这件事跑通。
2. 为什么新手值得从汇编啃一遍:看懂电平翻转的底层逻辑
2.1 汇编不是故意为难你,它把硬件摊开给你看
很多新手第一次听说“用汇编写矩阵键盘”,第一反应是发怵:一堆 MOV、JB、LJMP,看着像天书。我第一次学的时候也这样,盯着MOV P1, #0F0H看了半天,不知道它到底在干嘛。后来想明白一件事:汇编的每一行几乎都对应硬件上的一次动作,MOV P1, #0F0H就是把 P1 口的高 4 位写成 1、低 4 位写成 0,等于告诉列线“我准备从低电平这边开始扫”。
用 C 语言写,你可能写一句P1 = 0xF0;就过去了,编译器帮你做了很多事。汇编没有这层遮挡,你得自己安排哪个寄存器存键值、哪个变量存段码、延时循环几层。这种“什么都要自己管”的体验,恰恰是理解单片机输入输出最直接的方式。入门阶段啃一遍,后面再看任何按键扫描代码,你都能一眼看出它在扫行还是扫列。
2.2 矩阵键盘扫描在真实设备里到处都是
别以为这只是课堂作业。电子秤上的数字键、老式电话的拨号盘、工业控制面板上的参数设置键,很多都是矩阵结构。原因很朴素:按键一多,独立按键的引脚开销就受不了,而矩阵键盘用行线加列线的交叉点来复用引脚,4 根行线加 4 根列线就能覆盖 16 个键。你把这套扫描逻辑吃透,以后遇到 3×4 的小键盘或者 4×4 的扩展键盘,改一下行列数就能迁移。
这份资源里的代码还顺带把数码管显示串了进来:按下键,键值存进变量,再通过段码表送到数码管上显示。也就是说,你练的不只是“读按键”,而是“读进来—算键值—显示出去”这一整条链路。对零基础的人来说,这条链路跑通一次,比看十篇原理文章都管用。
2.3 先建立一张“程序地图”,别一上来就逐行读
新手最容易翻车的地方,是打开源代码就从第一行往下读,读到LJMP MAIN就迷路了。我的建议是先画一张粗地图,知道程序分几块:
| 程序块 | 作用 | 新手关注点 |
|---|---|---|
| 变量定义区 | S1~S9 存数码管段码 | 每个变量对应一位数码管 |
| 主函数 MAIN | 循环调用显示和扫描 | 程序永远在这两个子程序间转 |
| 键盘扫描 KEY_INT | 判断有无按键、算键值 | 去抖和行列判断是重点 |
| 显示 DISPLAY1 | 把段码送到数码管 | 注意 P2 口选位、P0 口送段码 |
| 清零 ZERO | 清空显示 | 对应“清零”功能键 |
| 移位 SWAP_SHU1/2 | 数字左移右移 | 对应“退格”类功能键 |
| 延时 DELAY1/2 | 去抖和显示停留 | 循环次数决定延时长短 |
有了这张表,你再去看代码,就不会被SETB 7AH这种标志位吓到——它只是告诉显示程序“这次要移位了”。入门阶段不需要背指令,先知道每一块负责什么,读代码就有方向。
3. 动手前的准备:硬件连线与第一个可运行框架
3.1 硬件怎么接,先对着口线认一遍
这份代码默认的接法是:矩阵键盘接在 P1 口,高 4 位 P1.4~P1.7 是行线,低 4 位 P1.0~P1.3 是列线;数码管段码送 P0 口,位选送 P2 口。你拿到开发板后,先别急着烧程序,拿万用表或者看原理图确认两件事:按键的行列是不是真的落在 P1 上,数码管是共阴还是共阳。
代码里的段码表是按共阴极写的,80H对应数字 0,06H对应数字 1,一路到6FH对应数字 9。如果你板子上的数码管是共阳极,显示会全乱,这时候要么换共阴数码管,要么把段码表整体取反。这个坑我见过太多人踩,现象就是“程序烧进去了,数码管亮得莫名其妙”,原因往往就是共阴共阳没对上。
提示:接线前先断电,确认开发板供电电压和单片机型号匹配,别热插拔排线。
3.2 第一个能跑的最小框架:先让数码管亮起来
不要一上来就把整份代码烧进去。入门最稳的做法是先跑一个最小框架:只做初始化,然后循环调用显示子程序,让数码管稳定显示一个 0。下面这段是从资源里抽出来的骨架,你可以先照着敲:
ORG 0000H LJMP START ORG 0030H START: MOV R1, #00H ; 键值初值设为 0,对应段码表第一个数字 MOV DPTR, #TABLE ; 数据指针指向共阴段码表 MAIN: LCALL DISPLAY1 ; 只调用显示,先不扫描键盘 LJMP MAIN DISPLAY1: MOV A, R1 MOVC A, @A+DPTR ; 用 R1 做偏移,从表里取段码 MOV P0, A ; 段码送 P0 口 MOV P2, #00H ; 选中第一位数码管 RET TABLE: DB 80H, 06H, 5BH, 4FH, 66H DB 6DH, 7DH, 07H, 7FH, 6FH, 3FH END这段代码只做一件事:把R1当索引,从TABLE里取出段码,送到 P0,再让 P2 选中第一位。MOVC A, @A+DPTR是查表指令,A里放偏移量,DPTR放表首地址,取出来的字节就是数码管该亮的段。跑通它的标志是:数码管第一位稳定显示 0,不闪不乱。如果显示的是乱码,先查共阴共阳,再查 P0 和 P2 有没有接反。
这一步看着简单,但它把“变量—查表—输出”这条线走通了。后面加键盘扫描,只是在这条线上多一个“读按键、改 R1”的动作。
3.3 把扫描子程序接进来,先只判断“有没有键按下”
显示稳了之后,再把键盘扫描的最小判断加进来。资源里的KS子程序就是干这个的:
KS: MOV P1, #0F0H ; 高 4 位行线置 1,低 4 位列线置 0 MOV A, P1 ; 读 P1 口实际电平 XRL A, #0F0H ; 和 0F0H 异或,有键按下时结果非零 RETMOV P1, #0F0H先把列线拉低、行线拉高,如果没有任何键按下,行线读回来还是高电平,异或之后A就是 0;一旦某个键按下,对应的行线被列线拉低,异或结果就非零。主程序里用JNZ判断这个结果,非零才继续往下走去抖和算键值。
先跑到这里,你可以用调试器单步看A的值:不按键时是 0,按住任意一个键时变成非零。这个现象确认了,说明你的硬件连线和口线方向都没问题,再往下写行列判断就有底了。
4. 把第一个例子跑通:从按键到数码管显示的完整链路
4.1 去抖这一步不能省:两次检测加一段延时
机械按键按下去的瞬间,触点会弹跳几下,单片机读到的电平可能在 0 和 1 之间抖。资源里的做法很经典:先调KS检测到可能有键,延时一段,再调一次KS确认。对应代码是KEY_INT开头那几行:
KEY_INT: LCALL KS JNZ K1 ; 第一次检测非零,跳到 K1 LCALL DELAY1 ; 没键也延时一下,避免空转太快 AJMP KEY_INT K1: LCALL DELAY1 ; 确认前先延时去抖 LCALL KS JNZ K2 ; 第二次仍非零,才认为真的按下 AJMP KEY_INTDELAY1里是MOV R3, #10加一个DJNZ循环,循环里还调了显示子程序,所以它既是延时也在维持数码管刷新。新手常问“延时多久合适”,入门阶段不用精确算,按资源里的循环次数先跑,现象稳定就行;如果发现按一下出好几个数,就把R3调大一点再试。
4.2 行列扫描怎么算出键值:列号加行首键号
确认有键之后,K2到K4这一段开始逐列扫描。思路是:先把第一列拉低(MOV R2, #11110111B),读四根行线,哪一行为低就说明按键在这一行;这一列没读到,就把扫描值右移一位换下一列,同时列号R0加一。行首键号是 01H、05H、09H、13,加上列号就得到最终键值。
K2: MOV R2, #11110111B ; 第一列拉低,其余列保持高 MOV R0, #00H ; 列号从 0 开始 K3: MOV P1, R2 JB P1.7, L1 ; P1.7 为 1 说明第 0 行没按下 MOV A, #01H ; 第 0 行首键号 AJMP LK L1: JB P1.6, L2 MOV A, #05H ; 第 1 行首键号 AJMP LK LK: ADD A, R0 ; 行首键号加列号得到键值 MOV R1, A ; 键值存进 R1,供显示使用 SETB 7AH ; 置移位标志,显示程序据此左移 AJMP K4JB P1.7, L1的意思是“如果 P1.7 是 1 就跳走”,也就是这一行没被按下;四行都没跳走,才落到MOV A, #01H记下这一行的首键号。ADD A, R0把行首键号和当前列号相加,得到的数就是 1 到 16 之间的键值。这里R0从 0 开始,所以第一列第一行是 1,第一列第二行是 5,依此类推。
4.3 键值怎么变成数码管上的数字:查表和移位
键值存进R1后,DISPLAY1会先判断它是不是功能键:0BH走退格,0CH走清零,其余当数字处理。数字处理的核心还是查表:
DISPLAY1: MOV A, R1 XRL A, #0BH JZ DEL1 ; 键值 0BH,走退格 MOV A, R1 XRL A, #0CH JZ ZERO ; 键值 0CH,走清零 JB 7AH, SWAP_SHU1 ; 移位标志置位,先左移再显示 MOV A, R1 MOVC A, @A+DPTR ; 查段码表 MOV S1, A ; 存进第一位数码管变量 MOV P2, #00H MOV P0, S1 LCALL DELAY2 ; 后面依次显示 S2 到 S8,位选每次加 04HXRL加JZ是汇编里常见的比较写法:把R1和功能键值异或,结果为 0 就说明相等。JB 7AH, SWAP_SHU1检查移位标志,如果上一次按键要求左移,就先执行SWAP_SHU1把 S8 到 S1 依次往高位挪,再回来显示。这样你连续按数字键,数码管上的数字会一位位往左推,像计算器输入一样。
MOV P2, #00H到MOV P2, #1CH是位选,每次加04H,对应 8 个数码管的位选地址。MOV P0, S1把段码送到段码口。两个口配合,一个选“哪一位”,一个送“显示什么”,这是动态扫描显示的基本套路。
4.4 清零和退格:两个功能键背后的子程序
ZERO子程序把 S2 到 S9 全部写 0,同时清掉移位标志7AH,然后跳回显示。DEL1和SWAP_SHU2负责退格:把 S2 到 S8 依次往低位挪,最后一位补 0。资源里这段代码有注释掉的旧版本,新手读的时候容易看花,你只要抓住“左移是 S8 往 S9 挪、右移是 S2 往 S1 挪”这个方向就行。
跑通完整链路的验收标准是:按数字键,数码管对应位显示数字并逐位左移;按清零键,显示全灭;按退格键,最后一位被抹掉。三个现象都对,说明扫描、键值计算、显示、移位这几块已经串起来了。
5. 新手最容易踩的坑:排查清单与常见问题
5.1 现象:数码管全亮或显示乱码
原因多半是共阴共阳没对上,或者段码表索引错位。资源里的表是按共阴写的,80H是 0,如果你板子是共阳,0 会变成全亮。解决方法是先确认数码管类型,共阳就把表里每个字节取反,或者换一块共阴数码管再试。另一个可能是R1初值不是 0,查表偏移错了,检查START里MOV R1, #00H有没有被后面的代码覆盖。
5.2 现象:按键没反应,或者按一下出好几个数
没反应先查 P1 口接线和KS的返回值,单步看A是否非零;如果一直为零,说明行线列线接反或者按键没接通。按一下出多个数,基本是去抖不够,把DELAY1里的R3从 10 调大,或者确认两次KS之间确实走了延时。还有一种情况是扫描循环DJNZ R7的初值太小,8 次没扫完就退出,检查MOV R7, #08H有没有被改动。
5.3 现象:显示闪烁严重,或者只有一位亮
闪烁通常是DELAY2太短或太长,动态扫描每位停留时间不合适;先把R4、R5的循环次数按资源原值跑,别自己乱改。只有一位亮,查位选P2的递增值是不是04H,以及MOV P2, #00H到#1CH这八条有没有漏写。还有可能是S2到S8变量地址被别的数据覆盖,确认EQU定义的 30H 到 38H 没和堆栈冲突。
5.4 现象:功能键不生效,退格和清零没区别
先看DISPLAY1里对0BH和0CH的判断顺序,XRL之后必须紧跟JZ,中间插了别的指令就会跳错。再确认键值计算出来的功能键到底是不是0BH、0CH,可以在调试器里看R1的实际值。如果键值对不上,回头检查行首键号和列号的加法,功能键通常排在数字键之后,行列位置变了键值也会变。
5.5 现象:程序跑飞,数码管突然全灭
汇编里最常见的是跳转目标写错或者子程序没有RET。检查每个LCALL对应的子程序末尾有没有RET,LJMP的目标标号是否存在。资源里DEL1那段有注释掉的代码,如果你手动删注释时漏了标号,汇编器可能不报错但运行会乱跳。养成习惯:改完代码先看汇编器有没有警告,再单步跑一遍主循环。
6. 下一步学什么:从这份代码走向自己的小项目
跑通这份矩阵键盘扫描之后,你手里其实已经有了一套可复用的骨架:初始化、扫描、去抖、查表、动态显示。接下来别急着换更复杂的芯片,先把这套骨架改出几个变体,每个变体只动一小块,这样学得最扎实。
第一个变体是改行列数。把 4×4 改成 3×4,行线少一根,行首键号和扫描值跟着改,键值范围从 1 到 12。改完你会发现,矩阵键盘扫描的核心不是固定代码,而是“列扫描值 + 行首键号 + 列号”这个计算套路。第二个变体是换显示内容,把段码表扩成带字母的,按键值显示 A 到 F,练查表和边界判断。第三个变体是加一个长按判断,在去抖确认之后计时,超过一定时间算长按,这会让你的延时子程序从“死等”变成“可计数”。
学汇编容易陷入一个误区:想把每条指令都背下来。我自己的血泪经验是,指令不用背,用多了自然记住常用的十几条,剩下的查手册就行。真正要练的是“看到现象能定位到哪一段代码”的能力。从那以后我每次改按键程序,都强制先单步跑一遍KS,确认A的返回值符合预期,再往下看行列扫描,这个习惯帮我省了很多后悔药。
如果你想把这份资源用透,建议按这个顺序走:先只跑显示,再只跑“有无按键”,然后跑完整键值,最后试功能键。每一步都用一个明确现象验收,不对就停在那一步查,别一口气全烧进去然后对着乱码发呆。等这套流程走顺了,你再去看独立按键、编码器或者更复杂的输入设备,会发现底层逻辑是相通的。
希望这份入门指南能帮你把第一块矩阵键盘真正按响,后面的路,就从你亲手改出来的第一个变体开始。
本文还有配套的精品资源,点击获取