资讯动态

51单片机汇编实战:4×4矩阵键盘与8位数码管动态显示

发布时间:2026/10/28 3:18:00 来源:尧图企业网站定制
简介这份资源面向单片机初学者与电子爱好者聚焦51单片机汇编语言下的矩阵键盘扫描实践帮助读者理解行线列线交叉检测按键的硬件原理并掌握汇编程序控制输入输出与数码管显示的完整流程。压缩包共1个文件为1份doc文档约283KB内含完整程序源代码与关键代码块说明涵盖变量定义、主函数初始化、按键判断与键值计算、数码管显示与移位、清零及延时子程序、共阴极段码表等模块便于对照阅读与调试。已有569人学习下载适合作为汇编入门与嵌入式底层操作训练的参考材料读者可从中获得可运行的代码框架、去抖与扫描思路以及数码管动态显示的实现细节为后续项目开发打下基础。1. 一块 4×4 键盘加 8 位数码管为什么值得用汇编写一遍去年帮一个做实验箱的朋友调程序现象很怪按键按下去数码管偶尔跳两个数松手又正常。示波器一抓P1 口在扫描间隙被拉成中间电平读回来的行线全是毛刺。问题不在硬件在于扫描和显示挤在同一个循环里谁也没让谁。这类毛病用 C 写一样会犯但用汇编写一遍你会被逼着把每一个机器周期、每一次端口读写都算清楚之后再回头看 C 的位操作心里就有底了。这份资源就是围绕这个场景展开的51 单片机、4×4 矩阵键盘、8 位共阴极数码管全部用汇编实现。功能不复杂——按键按下键值进显示缓冲数码管左移一位腾出位置按退格键删一位按清零键全清。适合刚学完指令系统、想找一个能跑通的完整工程练手的人也适合带实验课的开发者拿去当参考实现。矩阵键盘扫描这个题目网上代码很多但能把去抖、键值计算、移位显示、退格清零串成一个闭环的完整汇编工程并不多这份代码的价值就在这里。2. 工程结构与硬件接线先搞清楚 P0、P1、P2 各管什么2.1 内存布局与变量定义汇编工程没有目录树结构体现在两处一是伪指令划出的代码段和数据段二是 EQU 定义的变量区。这份代码把 8 个数码管缓冲放在 30H 到 37H另外留了 38H 给移位时的临时位。S1 EQU 30H ; 第 1 位数码管段码缓冲 S2 EQU 31H ; 第 2 位 S3 EQU 32H S4 EQU 33H S5 EQU 34H S6 EQU 35H S7 EQU 36H S8 EQU 37H S9 EQU 38H ; 左移时 S8 的旧值暂存到这里 ORG 0000H LJMP START ; 复位向量跳过中断向量区 ORG 0030H ; 主程序从 0030H 开始避开向量表30H 到 37H 属于 51 内部 RAM 的直接寻址区用MOV S1, A一条指令就能写比走寄存器间接寻址快这是选这段地址的原因。S9 单独放在 38H是因为左移时MOV S9, S8之后 S8 马上要被 S7 覆盖必须有个地方接住最右边那位。位标志用了两个40H 和 7AH。40H 在可位寻址区7AH 也在位寻址区分别标记「需要左移」和「需要右移」后面显示子程序靠查这两个位决定走哪条分支。提示EQU 只是编译期替换不占空间。真正占空间的是 30H 到 38H 这 9 个字节写代码时别把堆栈指针往这段压否则显示缓冲会被栈数据冲掉。2.2 端口分配与硬件连接三个端口分工明确接线错了后面全白搭端口用途方向说明P1.4~P1.7矩阵键盘行线输入读之前先写 1P1.0~P1.3矩阵键盘列线输出逐列拉低扫描P0数码管段码输出共阴极高电平点亮P2数码管位选输出经 74HC138 译码00H/04H/08H…1CHP1 口一行四列、四行四列正好 16 个键。列线做输出、行线做输入这是矩阵键盘最常见的接法扫描时给某一列低电平其余列高电平然后读四根行线哪根变低就说明这一列这一行的交叉点被按下。P2 送的是 138 译码器的输入所以位选值是 00H、04H、08H、0CH 这样每次加 4而不是 01H、02H、03H。这一点如果照抄别人的代码很容易翻车因为有些板子是直接驱动三极管位选就是 01H 起步。2.3 主循环显示和扫描的调度关系主程序本身只有四行但调度顺序决定了整个系统的响应手感START: MOV R1, #00H ; 键值寄存器同时兼作段码表偏移 MOV R7, #08H ; 扫描计数8 次后强制进显示 MOV DPTR, #TABLE ; 段码表首地址 MAIN: LCALL DISPLAY1 ; 先刷新一遍显示 LCALL KEY_INT ; 再扫键盘 LJMP MAIN ; 无限循环先显示后扫描是为了让数码管在两次扫描之间保持点亮视觉上不闪。R7 初值 8 是个细节KEY_INT 内部每扫完一轮列会DJNZ R7减到 0 就跳出去调一次 DISPLAY1 再返回相当于给扫描加了个上限防止某一轮卡在键盘检测里太久导致显示断流。R1 一个寄存器干两件事——平时存键值显示时当MOVC A, ADPTR的偏移量因为键值 0 到 9 正好对应段码表前 10 项这个复用省掉了一次数据搬运。3. 键盘扫描核心链路从检测按下到算出键值3.1 两级去抖KS 检测加 DELAY1 复检去抖这段是整份代码里最容易被简化掉、也最容易出问题的地方。代码用了「检测—延时—再检测」的标准结构KEY_INT: LCALL KS ; 第一次检测A 非零说明可能有键 JNZ K1 LCALL DELAY1 ; 没键也延时一下避免空转太猛 AJMP KEY_INT K1: LCALL DELAY1 ; 有键先等 10ms 左右 LCALL KS ; 再检测一次 JNZ K2 ; 两次都非零才确认 AJMP KEY_INTKS 子程序干的事很直接P1 高四位写 1、低四位写 0读回 P1 后和 0F0H 异或。没有键按下时行线被列线拉低读回高四位是 0异或结果是 0有键按下时某根行线被按键短接到低电平的列线读回高四位出现 1异或结果非零。用异或而不是直接判断是为了把「高四位有任意一位为 1」压缩成一个零/非零判断JNZ一条指令就够。DELAY1 的延时量是10 × DISPLAY1 耗时不是死循环空转DELAY1: MOV R3, #10 D4: LCALL DISPLAY1 ; 延时期间继续刷新显示 DJNZ R3, D4 RET这个写法值得学。普通延时子程序用DJNZ空转数码管在延时期间是灭的10ms 去抖就会看到明显闪烁。这里把 DISPLAY1 塞进延时循环去抖的同时显示照常刷新一举两得。代价是延时时间不精确取决于 DISPLAY1 里每个 DELAY2 的长度但对按键去抖来说 8 到 15ms 都算合格区间不需要精确。3.2 列扫描与行判断R2 循环右移的巧劲确认有键之后进入逐列扫描。R2 初值11110111B最低位那根列线拉低其余列高K2: MOV R2, #11110111B ; 第一列P1.3拉低 MOV R0, #00H ; 列号计数每换一列加 1 K3: MOV P1, R2 ; 输出列扫描值 L0: JB P1.7, L1 ; 第 0 行没按下看下一行 MOV A, #01H ; 第 0 行首键号 AJMP LK L1: JB P1.6, L2 MOV A, #05H ; 第 1 行首键号 AJMP LK L2: JB P1.5, L3 MOV A, #09H ; 第 2 行首键号 AJMP LK L3: JB P1.4, NEXT MOV A, #13 ; 第 3 行首键号 LK: ADD A, R0 ; 首键号 列号 键值 MOV R1, A SETB 40H ; 置左移标志 SETB 7AH AJMP K4 NEXT: INC R0 ; 换下一列 MOV A, R2 JNB Acc.0, K4 ; 最低位已经移到 0四列扫完 RR A ; 循环右移拉低下一列 MOV R2, A AJMP K3首键号 01H、05H、09H、13 是十进制 13也就是 0DH不是十六进制 0D 的笔误——这里用的是十进制写法因为键值最终要当段码表偏移0 到 15 连续排布最方便。行首键号加列号得到 0 到 15 的连续键值这个设计让段码表可以直接按 0 到 9 顺序排不用做映射表。RR A循环右移是这段的巧劲。R2 从11110111B开始右移一次变11111011B再移变11111101B、11111110B正好依次拉低 P1.3、P1.2、P1.1、P1.0 四列。JNB Acc.0, K4判断的是移位后最低位是否为 0——当11111110B再右移会变成01111111B最低位变 1但代码在移位前就判断所以实际是在第四列扫完后退出。这个边界条件建议自己拿纸推一遍推通了矩阵键盘的扫描逻辑就彻底懂了。3.3 键值后处理退格、清零、移位三个分支键值算出来后不是直接显示DISPLAY1 开头先做三次判断DISPLAY1: MOV A, R1 XRL A, #0BH ; 键值 11 是退格键 JZ DEL1 MOV A, R1 XRL A, #0CH ; 键值 12 是清零键 JZ ZERO JB 7AH, SWAP_SHU1 ; 有左移标志先移位再显示 ; ……正常显示流程用XRL加JZ而不是CJNE是因为CJNE会改进位标志而这段代码后面还要用位标志判断移位少动一个标志位少一分风险。0BH 和 0CH 这两个键值对应键盘上第 12、13 个键具体是哪两个物理按键取决于键盘丝印改键位只要改这两个立即数。退格分支 DEL1 在源码里被注释掉了只留了框架; DEL1: ; MOV S9, #00H ; LCALL SWAP_SHU2 ; DEL2: ; SETB 40H ; CLR 7AH ; LJMP DISPLAY1注释掉的原因大概率是退格逻辑没调通——右移之后最左边会残留旧值需要额外清零。要启用的话DEL1 里应该先把 S1 清 0 再右移或者右移后补一句MOV S1, #00H。这是这份代码留给二次开发的一个口子后面第 5 章会展开。4. 数码管动态显示与移位8 位缓冲怎么滚起来4.1 段码表与共阴极编码段码表放在程序末尾10 个数字加一个 3FHTABLE: DB 80H, 06H, 5BH, 4FH, 66H ; 0~4 DB 6DH, 7DH, 07H, 7FH, 6FH ; 5~9 DB 3FH ; 第 11 项对应键值 1080H 对应数字 0二进制10000000B最高位是 dp 段低七位全 0——等等共阴极 0 应该是 a、b、c、d、e、f 六段亮编码 3FH 才对。这里 80H 排第一说明这张表不是从 0 开始的而是把 0 放在了第二位。对照后面MOVC A, ADPTR用 R1 做偏移R1 是键值 0 到 15那键值 0 取到的是 80H显示的是一个点或者全灭。这大概率是原作者的笔误或者键盘 0 键的键值实际是 1。改的时候把 TABLE 第一项改成 3FH、最后那个 3FH 删掉或者把键值整体加 1两种改法都行但只能改一处改两处就错位。注意段码表错位是这类工程最常见的翻车点现象是「按 1 显示 0、按 0 显示乱码」。调的时候先单独写个测试循环把 0 到 9 依次送 P0确认硬件编码再回头对表。4.2 动态扫描位选加段码靠 DELAY2 撑住亮度8 位数码管共用 P0 段码线靠 P2 位选轮流点亮一次只亮一位MOV A, R1 MOVC A, ADPTR ; 查表取段码 MOV S1, A ; 存进第 1 位缓冲 MOV P2, #00H ; 选通第 1 位 MOV P0, S1 ; 送段码 LCALL DELAY2 MOV P2, #04H ; 选通第 2 位 MOV P0, S2 LCALL DELAY2 ; ……依次到第 8 位P2 送 1CH每位之间插一个 DELAY2延时量是43 × 12次空转DELAY2: MOV R4, #43 D1: MOV R5, #12 D2: DJNZ R5, D2 DJNZ R4, D1 RET43 × 12 在 12MHz 晶振下大约 1ms 出头8 位扫一遍 8ms 左右刷新率 125Hz肉眼看不到闪烁。这个值不能随便改改小了亮度不够改大了单次扫描时间变长按键响应会变迟钝。如果换 11.0592MHz 晶振延时略短问题不大换 6MHz 就要把 43 调大否则亮度掉得厉害。4.3 左移与右移缓冲区的整体搬迁左移发生在有新键值、且 7AH 标志置位时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 ; 回显示此时 S1 还是旧值注意最后没有给 S1 赋新值而是跳回 DISPLAY1 开头重新走一遍——第二次进来时 7AH 已清会走正常显示流程把 R1 里的新键值查表写进 S1。这个「移完再回来填」的写法省了一次查表但要求 R1 在移位过程中不能被改所以移位期间不能响应新按键。实际使用中 8 次扫描才进一次显示这个窗口很短感觉不到丢键。右移 SWAP_SHU2 是反方向S1 到 S8 依次被后一位覆盖S9 的值补进 S8。这段代码在退格分支里被调用但 DEL1 被注释了所以右移目前是死代码。要激活退格除了取消注释还得处理 S1 的残留——右移后 S1 还是旧值需要手动清 0否则退格退不干净。5. 避坑与排查五个真实会卡住你的点5.1 按键连击按一下出好几个数现象按一次键数码管连移两三位。原因去抖只做了「延时后复检」但没等按键释放就返回了主循环下一轮又检测到同一个按键还按着。解决在 K4 之后加一段等释放的循环LCALL KS直到 A 为零再 RET或者用「松手检测」标志位检测到释放前不再接受新键值。这份代码没做释放检测是它最明显的短板二次开发时优先补。5.2 数码管某一位常亮或全灭现象8 位里固定某一位不亮或者一直显示同一个数字。原因P2 位选值算错。138 译码器输入是 A、B、C 三根线对应值 0 到 7但代码里送的是 00H、04H、08H……1CH这是把译码输入当成了字节地址。如果你的板子 P2 直接接三极管基极位选应该是 01H、02H、04H、08H、10H、20H、40H、80H。解决先量 P2 口实际电平确认是译码还是直驱再改位选序列。5.3 段码表错位数字整体偏移现象按 1 显示 0按 0 显示一个点。原因TABLE 第一项是 80H 而不是 3FH键值 0 取到了 dp 段编码。解决把 TABLE 改成DB 3FH, 06H, 5BH, ...从 0 开始排或者把 R1 在查表前加 1。改完用单步调试看MOVC之后的 A 值和预期段码对一遍。5.4 移位后最左位残留旧数字现象左移几次后最左边一直显示第一个按下的数字。原因SWAP_SHU1 移完跳回 DISPLAY1第二次进来时如果 R1 没变比如按键已释放但键值没更新S1 不会被覆盖。解决移位后直接给 S1 赋新键值的段码不要依赖回跳或者在 MAIN 里每次扫描前把 R1 清 0只有检测到新键才写入。5.5 延时改了之后显示变暗或按键变迟钝现象把 DELAY2 的 43 改小数码管变暗改大按键要按很久才有反应。原因DELAY2 同时决定单次点亮时长和整轮扫描周期两个指标互相拉扯。解决亮度靠减小 DELAY2、提高刷新率来补但刷新率超过 200Hz 后亮度提升有限反而增加 CPU 占用。经验值是 8 位扫一遍控制在 5 到 10ms对应 DELAY2 在 30 到 60 之间调用示波器看 P2 任一位的波形周期最准。6. 二次开发把退格补全再谈键值映射这份代码最值得动手改的地方是退格分支。DEL1 被注释掉不是偶然右移之后 S1 残留旧值是绕不过去的坎。我的改法是放弃「移完回跳」的写法在 SWAP_SHU2 末尾直接补一句清零SWAP_SHU2: MOV S1, S2 MOV S2, S3 MOV S3, S4 MOV S4, S5 MOV S5, S6 MOV S6, S7 MOV S7, S8 MOV S8, S9 MOV S9, #00H ; 暂存位清空防止下次左移带出脏数据 MOV S1, #00H ; 最左位补零退格才退得干净 CLR 40H CLR 70H RET ; 不再 LJMP DISPLAY1交给调用者改完把 DEL1 的注释去掉内容换成LCALL SWAP_SHU2加LJMP DISPLAY1。验证方法是连按三个数字再连按三次退格看是否逐位清空、第四次退格是否无效果。这里有个细节S9 清 0 必须在右移之后否则左移时会把 0 带进显示区。第二个改造点是键值映射。现在 0BH 是退格、0CH 是清零硬编码在 DISPLAY1 开头。如果键盘丝印不同或者想加确认键、小数点键改立即数就行但更稳的做法是定义符号KEY_BACK EQU 0BH KEY_CLR EQU 0CH ; 判断处改成 MOV A, R1 XRL A, #KEY_BACK JZ DEL1这样换键盘只改两行 EQU。再进一步可以把 16 个键的功能做成一张跳转表用MOVC取偏移、JMP ADPTR分发加功能不用动主流程。代价是多一层间接跳转对 51 来说开销可接受。最后一个技巧关于调试汇编没有 printf排查键值最土也最有效的办法是临时把 R1 直接送 P0 显示——按哪个键数码管就显示那个键的原始键值不查表。确认键值序列 0 到 15 连续无误再接回段码表。我当年调第一块矩阵键盘时就是靠这个笨办法发现列扫描少扫了一列键值从 3 直接跳到 5。从那以后每次改扫描逻辑我都先把原始键值打出来对一遍再谈显示。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑