ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

51单片机汇编实现4x4矩阵键盘扫描与数码管显示

51单片机汇编实现4x4矩阵键盘扫描与数码管显示

简介:这份资源面向单片机初学者与电子爱好者,聚焦51单片机汇编语言下的矩阵键盘扫描实践,帮助理解行线列线交叉检测按键的硬件原理与底层编程思路。压缩包共1个doc文件,约283KB,内含完整程序源代码与关键代码块说明,涵盖变量定义、主函数流程、按键判断与键值计算、数码管显示与移位、清零及延时子程序、共阴极段码表等模块,结构紧凑便于对照阅读。已有569人学习下载,适合作为汇编入门与硬件操作的参考素材。读者可从中获得一套可直接研读的矩阵键盘扫描汇编范例,理清扫描、去抖、键值计算与数码管动态显示的配合关系,并借助移位与条件判断理解循环程序的组织方式,为后续嵌入式开发打下底层基础。

1. 矩阵键盘扫描:为什么汇编实现值得你花一个晚上拆透

如果你手头有一块 51 开发板,上面挂着 4×4 矩阵键盘和 8 位数码管,想用汇编写一个能连续输入、能退格、能清零的键盘扫描程序,这份源码包基本就是照着抄的模板。它把行扫描、列检测、键值计算、数码管动态显示、移位和退格全串在一个主循环里,代码量不大,但每一段都踩在硬件时序的边界上。适合刚学完指令系统、想搞明白“按键到底怎么被认出来”的新手,也适合多年没碰汇编、需要快速找回位操作手感的熟手。矩阵键盘扫描这件事,用 C 写是调库,用汇编写才是把 P1 口的每一根线捏在手里。

2. 先看懂硬件连接:4×4 矩阵键盘与 P1 口的电平约定

2.1 行线列线怎么接,为什么扫描要分两步

矩阵键盘的 16 个按键排成 4 行 4 列,每个按键跨在一根行线和一根列线的交点上。源码里的约定是 P1 口高 4 位接行线、低 4 位接列线,检测分两步走:先让低 4 位全 0、高 4 位全 1,读一次 P1,如果高 4 位有哪一位被拉低,说明那一行有键按下;确认有键之后,再逐列输出低电平,读行线状态,定位到具体是哪一列。这个“先判有无、再判位置”的两段式结构,是矩阵扫描最稳的写法,比一次性扫描少很多误判。

源码里KS子程序就是第一步:

KS: ; 检测有无按键按下 MOV P1, #0F0H ; 高4位行线全1,低4位列线全0 MOV A, P1 ; 读回P1口 XRL A, #0F0H ; 与0F0H异或,行线被拉低则结果非零 RET

MOV P1,#0F0H之后,如果没有任何键按下,行线因为内部上拉保持高电平,读回来还是0F0H,异或结果是 0,主程序据此判断“无键”。一旦某行有键按下,那一行通过按键导通到低电平列线,读回值高位出现 0,异或结果非零,JNZ K1就跳去确认。这里有个容易忽略的点:51 的 P1 口作为输入前必须先写 1,MOV P1,#0F0H同时完成了“写 1 释放行线”和“拉低列线”两个动作,少写这一步读回来的值就是错的。

2.2 列扫描值 11110111B 的来历

确认有键之后进入K2,源码把#11110111B送进 R2 作为第一列的扫描值。这个字节的意思是 P1.3 为 0、其余为 1,也就是只把第一列拉低,然后依次检查 P1.7 到 P1.4 四根行线。哪根行线读到 0,就说明按下的键在这一行与当前列的交点上。每扫完一列,RR A把 0 位右循环一位,扫描值变成11111011B,轮到第二列,如此四轮覆盖全部 16 个键。

K2: MOV R2, #11110111B ; 第一列拉低,P1.3=0 MOV R0, #00H ; R0存列号偏移,首列为0 K3: MOV P1, R2 ; 扫描值送P1口 L0: JB P1.7, L1 ; P1.7为1说明第0行没按下,跳去查第1行 MOV A, #01H ; 第0行首键号 AJMP LK L1: JB P1.6, L2 MOV A, #05H ; 第1行首键号 AJMP LK

行首键号取 01H、05H、09H、13(十进制),间隔是 4,因为每行有 4 个键。最终键值 = 行首键号 + 列号偏移 R0,这个加法在LK里完成。列号偏移每换一列加 1,所以第一列四行的键值是 1、5、9、13,第二列是 2、6、10、14,依此类推。这套编号方式决定了后面数码管显示和退格判断用的键值常量,改键盘布局时这两个地方必须同步改,否则按 0 会显示成别的数字。

提示:不同开发板的行线列线接法可能相反,有的板子 P1 低 4 位接行、高 4 位接列。拿到源码先对照原理图确认,接反了扫描值要整体改,不是调一个常量能解决的。

3. 键值计算与去抖:从电平变化到稳定数字的完整链路

3.1 两次检测加延时,去抖为什么放在 KS 前后各一次

机械按键按下瞬间触点会弹跳几毫秒,直接读会得到一串忽高忽低的电平。源码的处理是:第一次KS发现可能有键,先LCALL DELAY1延时,再调一次KS复核,两次都非零才进入定位流程。这个“检测—延时—再检测”的结构比只在一次检测后延时更可靠,因为它把弹跳期整个跳过去了,第二次读到的已经是稳定状态。

DELAY1的实现值得单独看:

DELAY1: ; 延时一,约10×2个显示周期 MOV R3, #10 D4: LCALL DISPLAY1 ; 延时期间继续刷新数码管 DJNZ R3, D4 RET

它没有用空循环硬等,而是循环调用显示子程序。这样延时期间数码管不会熄灭,视觉上没有闪烁。代价是延时长度不精确,取决于DISPLAY1执行多久,但对按键去抖来说几十毫秒量级足够,不需要精确。如果你把DISPLAY1改短了,去抖时间会跟着变短,可能出现“按一下出两个数”的现象,这时候把 R3 的初值从 10 调大即可。

3.2 键值存进 R1 之后,显示子程序怎么用它

定位完成后MOV R1, A把键值存进 R1,同时SETB 40H和SETB 7AH置两个标志位。40H 位在退格逻辑里用,7AH 位是“需要左移”的标志。主循环回到DISPLAY1,第一件事就是拿 R1 和 0BH、0CH 比较:

DISPLAY1: MOV A, R1 XRL A, #0BH ; 键值0BH是退格键 JZ DEL1 MOV A, R1 XRL A, #0CH ; 键值0CH是清零键 JZ ZERO JB 7AH, SWAP_SHU1 ; 7AH置位则先做左移 LCALL DELAY2 MOV A, R1 MOVC A, @A+DPTR ; 查段码表 MOV S1, A ; 新键值放最右位

这里的设计是“新按下的数字永远进 S1,然后整体左移”。MOVC A,@A+DPTR用键值做偏移查TABLE段码表,得到共阴极七段码。注意键值从 1 开始编号,而段码表第 0 项是数字 0 的编码80H,所以键值 1 查到的是06H,正好是数字 1 的段码,编号和显示对得上,不需要额外减一。这个巧合是设计好的,改键值编号时要把这个偏移关系一起考虑。

3.3 段码表与共阴共阳的坑

源码末尾的TABLE是共阴极编码:80H,06H,5BH,4FH,66H,6DH,7DH,07H,7FH,6FH,3FH,依次对应 0 到 9 和最后一个 3FH(显示“-”或空白,看你怎么用)。共阴极的意思是段码位为 1 时对应段点亮,所以数字 0 是80H之外的六段全亮、中间段灭。如果你手上的板子是共阳数码管,这张表要全部取反,否则显示出来是反的——该亮的灭、该灭的亮,看起来像乱码。这是新手最常翻的车之一,拿到源码先确认数码管极性,别急着怀疑扫描逻辑。

4. 数码管动态显示与移位:8 位段码怎么在 P0/P2 上轮转

4.1 位选走 P2、段选走 P0,动态扫描的时序账

DISPLAY1的主体是 8 段几乎重复的代码:给 P2 送位选码(00H、04H、08H……1CH,每次加 4),给 P0 送对应位的段码,然后LCALL DELAY2停一会儿,再切下一位。这是典型的动态扫描——8 个数码管共用一组段线,靠位选线快速轮换,利用人眼视觉暂留看起来是同时亮的。

MOV P2, #00H ; 选第1位数码管 MOV P0, S1 ; 送第1位段码 LCALL DELAY2 MOV P2, #04H ; 选第2位 MOV P0, S2 LCALL DELAY2 ; ... 依次到 P2=1CH、P0=S8

位选码每次加 4,说明位选译码电路占用了 P2 的低几位做二进制译码,具体哪几位取决于板子的译码芯片,源码里这个步长 4 是硬件决定的,换板子要重新确认。DELAY2是MOV R4,#43套MOV R5,#12的双重循环,约 43×12 次空转,给每位数码管的点亮时间在毫秒级,8 位轮一遍刚好不闪。这个参数调小了会闪、调大了会有拖影,43 和 12 是原作者在特定晶振下试出来的值,你的板子晶振不同就要重调。

4.2 左移子程序 SWAP_SHU1 的数据搬运顺序

新数字进 S1 之后,如果 7AH 标志置位,DISPLAY1会先跳去SWAP_SHU1做左移。左移的语义是“旧数字整体往高位挪一格,给新数字腾出 S1”:

SWAP_SHU1: MOV S9, S8 ; 从高往低搬,避免覆盖 MOV S8, S7 MOV S7, S6 MOV S6, S5 MOV S5, S4 MOV S4, S3 MOV S3, S2 MOV S2, S1 CLR 7AH ; 清移位标志 LJMP DISPLAY1 ; 回去继续显示

搬运顺序必须从 S9 往 S2 倒着来,如果从 S2 开始正着搬,S2 的新值会先把 S3 的旧值覆盖掉,搬完发现所有位都是同一个数。这个顺序问题是移位类子程序的血泪经验,写反了现象很怪——显示看起来“卡住”了,其实是数据在搬运途中被自己踩了。搬完之后CLR 7AH清标志,再跳回DISPLAY1,此时 7AH 已清,不会再触发移位,直接走查表显示新键值。

4.3 退格与清零:0BH 和 0CH 两个特殊键值

退格键的键值是 0BH,清零键是 0CH,这两个值不在 1 到 16 的正常键值范围内,是扫描逻辑之外单独约定的。DISPLAY1一进来就先比对这两个值,命中就跳去DEL1或ZERO,不参与正常数字显示。ZERO子程序把 S2 到 S9 全部写 0、R1 写 0、清 7AH 标志,然后跳回显示,效果是数码管全灭、输入缓冲清空。

退格DEL1的逻辑在源码里被注释掉了一部分,只留下MOV S9,#00H和跳转SWAP_SHU2的骨架。SWAP_SHU2是右移子程序,把 S2 到 S8 依次往低位搬、S9 补零,相当于删掉最右边一位、整体右移一格。这里源码的注释状态说明作者当时在调试退格行为,最终版本可能没完全启用。你如果要完整退格功能,需要把DEL1补全成“先右移、再清最高位、清标志、回显示”的完整流程,并确认 40H 位的置清时机和右移子程序里的CLR 40H、CLR 70H不冲突。这是这份源码里最需要你自己动手补的一段,也是理解移位标志位配合的最好练习。

5. 避坑与排查:这份汇编源码最容易翻车的五个地方

5.1 现象:上电后数码管全亮或全灭,按键无反应

原因通常是段码表极性和数码管不匹配,或者 P0 口没接上拉。51 的 P0 口作为输出时是开漏的,驱动共阴数码管段线必须外接上拉排阻,很多最小系统板省了这组电阻,段码送出去电平拉不高,显示就是乱的或全灭。先量 P0 各脚在送06H时的实际电压,如果只有 1V 左右,就是缺上拉。解决是补 10K 排阻到 VCC,或者换用 P2 口驱动段线(如果硬件允许)。

5.2 现象:按一个键,数码管跳两个数

去抖没生效或延时太短。检查KEY_INT里两次KS之间是否真的调了DELAY1,以及DELAY1的 R3 初值是不是被改小了。另一个隐蔽原因是DISPLAY1被改短后,DELAY1依赖它做延时,整体去抖时间跟着缩水。解决是把DELAY1改成独立空循环,或者把 R3 从 10 加到 20 试。还有一种情况是按键本身接触不良,换一个键位测试排除硬件。

5.3 现象:键值算出来总是差 4 或差 1

行首键号和列偏移的对应关系错了。源码里行首键号是 01H、05H、09H、13,列偏移 R0 从 0 开始每列加 1,如果你把列扫描值改成了从 P1.0 开始拉低,列偏移的递增方向就反了,算出来的键值会整体错位。排查方法是单步跑K3循环,看 R0 在四列扫描中是不是 0、1、2、3,以及LK里ADD A,R0之后 A 的值是否落在 1 到 16。对不上就回头核对RR A的循环方向和JNB Acc.0,K4的退出条件。

5.4 现象:左移后显示的数字顺序颠倒

SWAP_SHU1的搬运方向写反了。正确顺序是从 S9 开始往 S2 倒着搬,如果写成从 S2 往 S9 正着搬,低位的新值会覆盖高位还没搬走的旧值,结果所有位变成同一个数或者顺序错乱。这个错误编译不报、运行不崩,只是显示不对,属于典型的黑匣子问题。排查时在搬运前后各读一次 S1 到 S9 的值,对比就能看出是哪一步覆盖错了。

5.5 现象:退格键按下没反应,或按一次清空全部

DEL1在源码里是半注释状态,SWAP_SHU2里的CLR 40H、CLR 70H和主流程的标志位管理可能打架。如果退格完全没反应,检查DISPLAY1开头的XRL A,#0BH比较是否真的命中——键值 0BH 要求扫描逻辑能产出 11 这个值,而 11 在正常键值范围里对应第 3 行第 3 列,如果那个物理键没接或者接错,0BH 永远出不来。如果按一次清空,是右移循环次数多了,检查SWAP_SHU2是否被重复调用。建议先把退格逻辑单独抽出来,用固定键值手动触发,确认右移一位的行为正确后再接回主循环。

6. 进阶验证:用单步执行和口线探针把扫描过程看穿

调试这类汇编程序,光看现象不够,得让 P1 口的每一次变化都可见。我的习惯是在仿真环境里单步跑KEY_INT,每执行完一条MOV P1,R2就记录一次 P1 的实际值,对照扫描值表确认列线确实在逐列拉低。具体做法是准备一张四列的期望值表:第一列F7H、第二列FBH、第三列FDH、第四列FEH,单步时逐个核对,哪一列对不上就说明RR A的循环出了问题。

; 在K3循环里临时插入,把当前扫描值送到P3口观察 K3: MOV P1, R2 MOV P3, R2 ; 调试用:P3接LED或逻辑分析仪 ; ... 原有行判断

这段临时插桩不影响主逻辑,P3 口在多数最小系统板上是空闲的,接八个 LED 就能直观看到扫描值在四列之间轮转。确认列扫描正常后,再在LK的ADD A,R0之后把 A 送到 P3,按不同键看输出的键值是否和预期一致。两步都过了,键值计算这条链路就通了。

数码管这边,验证动态扫描是否真的在轮转,可以用示波器看 P2 位选线的波形,正常应该是 8 个不同占空比的方波依次出现,周期在毫秒级。没有示波器就用一个笨办法:把DELAY2的 R4 从 43 临时改成 200,扫描变慢,肉眼能看到 8 位数码管依次点亮,哪一位不亮就是那一路位选或段线断了。这个“慢速扫描”技巧在排查显示缺位时比任何仪器都快。

最后说个我自己的习惯。每次改完键值编号或者段码表,我都会先只测三个键:一个正常数字键、退格、清零,确认这三条路径都走通再测全键盘。因为这三个键覆盖了查表显示、移位、清缓冲三条独立逻辑,任何一条有 bug 都会在这三个键上暴露。从那以后我每次动这类扫描程序,都强制先跑这三键冒烟测试,再谈其他。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进