简介这份资源面向单片机初学者与嵌入式入门者围绕51单片机平台用汇编语言完整实现矩阵键盘扫描与数码管显示联动帮助理解行线列线交叉检测按键、键值计算与移位显示等底层硬件操作。压缩包内共1个doc文件约283KB集中收录了完整程序源代码与关键代码块说明涵盖变量定义、主函数流程、矩阵按键判断、键值获取、数码管清零与退格、段码表及延时子程序等模块便于对照阅读与移植调试。目前已有569人学习下载适合作为汇编语言编程实践与单片机输入输出教学的参考案例读者可从中掌握扫描去抖、行列定位、数码管动态显示与循环条件判断等常见任务思路为后续嵌入式开发打下底层基础。1. 矩阵键盘扫描的汇编实现从键值计算到数码管移位显示很多初学者第一次把矩阵键盘的代码烧进单片机都会遇到同一个玄学现象按下按键数码管要么纹丝不动要么乱跳甚至显示出一串完全对不上的数字。问题往往不在硬件焊接而在扫描逻辑里那几个容易被忽略的细节——列扫描值的循环移位方向、行首键号的偏移量、以及去抖延时和显示刷新之间的时序冲突。这份资源用 51 汇编完整实现了一套 4×4 矩阵键盘扫描加 8 位数码管显示的方案代码结构清晰从变量定义、主循环、按键判断、键值计算到数码管移位、退格清零、延时子程序每个模块都有独立入口。它适合正在学单片机底层输入输出、想搞懂行列扫描到底怎么算出键值的电子相关专业学生也适合需要快速复现一套稳定键盘驱动逻辑的嵌入式入门开发者。下面我按实际调试顺序把这份源码拆开讲一遍。2. 行列扫描的硬件逻辑与键值计算为什么是 0x0F 和 0xF02.1 矩阵键盘的行列结构与本项目引脚分配4×4 矩阵键盘的本质是 4 根行线和 4 根列线交叉每个交叉点放一个按键。没有按键按下时行线和列线在电气上互不相通按下某个键对应的行和列就被短接。单片机要做的就是主动输出一个电平到行线或列线然后读列线或行线根据哪一根线被拉低来判断按键位置。这份代码用的是 P1 口。从KS子程序里MOV P1, #0F0H可以反推P1 高 4 位P1.7~P1.4接行线低 4 位P1.3~P1.0接列线。0F0H的意思是行线全输出 1列线全输出 0。当没有按键按下时行线被内部或外部上拉保持高电平读回来还是0F0H一旦有键按下某根行线会被对应的列线拉低读回来的值就不再是0F0H。XRL A, #0F0H这步异或就是把“有变化”的位标记出来——如果 A 变成非零说明确实有键按下。这里有个容易翻车的点不同开发板的行线列线接法可能相反。有的板子 P1 低 4 位接行、高 4 位接列那KS里的0F0H就得改成0FH后面的列扫描值也要跟着调。拿到一份源码直接烧之前先拿万用表蜂鸣档量一下按键两端到底连到哪几个引脚比对着代码猜要靠谱得多。2.2 列扫描与行判断键值是怎么一步步算出来的确认有键按下之后代码进入K2开始的列扫描流程。核心思路是逐列拉低然后读行线看哪一行被拉低。K2: MOV R2, #11110111B ; 第一列 P1.3 置 0其余列线为 1 MOV R0, #00H ; R0 存列号偏移首列为 0 K3: MOV P1, R2 ; 列扫描值送 P1 口 L0: JB P1.7, L1 ; 读 P1.7为 1 说明第 0 行没按下跳去查第 1 行 MOV A, #01H ; 第 0 行首键号 1 AJMP LK L1: JB P1.6, L2 ; 第 1 行判断 MOV A, #05H ; 第 1 行首键号 5 AJMP LK L2: JB P1.5, L3 ; 第 2 行判断 MOV A, #09H ; 第 2 行首键号 9 AJMP LK L3: JB P1.4, NEXT ; 第 3 行判断 MOV A, #13 ; 第 3 行首键号 13 LK: ADD A, R0 ; 首键号 列偏移 最终键值 MOV R1, A ; 键值存入 R1 SETB 40H SETB 7AH ; 置数码管前移标志 AJMP K4 NEXT: INC R0 ; 当前列没查到列号加一 MOV A, R2 JNB Acc.0, K4 ; 如果列扫描值最低位已经是 0说明 4 列扫完 RR A ; 右循环移位把 0 移到下一列 MOV R2, A AJMP K3这段逻辑的关键在R0和首键号的配合。第一列P1.3 拉低时R00第 0 行按下得到键值01H第 1 行得到05H第 2 行09H第 3 行0DH即 13。第二列时R01第 0 行变成02H以此类推。最终键值范围是01H到10H16 个键其中0BH和0CH被单独用作退格和清零功能键。RR A这条指令是右循环移位配合JNB Acc.0判断是否扫完 4 列。初始R211110111B最低位是 1RR A之后变成11111011B0 移到了 P1.2 对应的位再移一次11111101B再移一次11111110B此时最低位变成 0JNB Acc.0成立跳出列循环。这个移位方向如果搞反扫描顺序就全乱了键值自然对不上。2.3 去抖与键值确认为什么 KS 要调用两次KEY_INT子程序里KS被调用了两次中间夹了一个DELAY1。第一次KS检测到可能有键按下不急着算键值先延时去抖延时后再调一次KS如果仍然检测到按下才进入K2做真正的行列扫描。这是机械按键的经典处理方式——按键闭合瞬间会有 5~20ms 的抖动期间电平快速跳变如果第一次就读键值很可能读到错误的行或列。DELAY1的延时长度是MOV R3, #10配合LCALL DISPLAY1循环。注意这里延时子程序里又调了显示子程序意味着去抖期间数码管还在刷新。这种“延时中夹显示”的写法在汇编里很常见因为数码管动态扫描本身就需要不断刷新如果延时期间不刷新数码管会闪烁或熄灭。但这也带来一个隐患如果DISPLAY1执行时间太长去抖延时会跟着变长按键响应变迟钝。实际调试时如果觉得按键反应慢可以先把R3的值改小试试比如改成#05看响应速度是否改善。3. 数码管动态显示与移位操作8 位段码怎么管3.1 段码表与位选控制代码里TABLE定义了共阴极数码管的段码TABLE: DB 80H, 06H, 5BH, 4FH, 66H ; 0~4 DB 6DH, 7DH, 07H, 7FH, 6FH, 3FH ; 5~9, 空注意第一个80H对应的是数字 0 吗不是。共阴极数码管显示 0 的段码通常是3FHa~f 段亮g 段灭而80H只有最高位dp 小数点为 1实际显示的是一个小数点。这份代码里TABLE的首地址对应键值 0但键值从01H开始所以80H实际上不会被正常按键触发到除非程序跑飞或初始化异常。真正对应数字 0 的是3FH放在表尾。这种“表首放一个不用的值”的做法是为了让键值直接作为偏移量查表——键值 1 查06H显示 1键值 2 查5BH显示 2以此类推。如果键值从 0 开始编号表首就得放3FH整个偏移逻辑都要改。位选控制通过 P2 口输出MOV P2, #00H选第一位#04H选第二位#08H第三位一直到#1CH第八位。每个位选之间都有LCALL DELAY2这是动态扫描的典型结构——快速轮流点亮每一位利用视觉暂留让人眼看到 8 位同时显示。DELAY2的延时参数是R443, R512嵌套循环大约产生几百微秒的延时8 位扫一轮大概几毫秒刷新率足够。3.2 移位子程序左移和右移的段码搬运SWAP_SHU1是左移新数字从右边进入原有数字往左推SWAP_SHU2是右移。以左移为例SWAP_SHU1: MOV S9, S8 ; S9 接收 S8 的值 MOV S8, S7 MOV S7, S6 MOV S6, S5 MOV S5, S4 MOV S4, S3 MOV S3, S2 MOV S2, S1 ; S1 的值移到 S2 CLR 7AH ; 清除移位标志 LJMP DISPLAY1这段代码把 S1 到 S8 的值依次往高位搬S1 空出来准备接收新键值。注意搬运顺序必须从高到低如果反过来先MOV S2, S1再MOV S3, S2S2 的新值会被覆盖到 S3导致所有位显示同一个数字。这是移位操作里最经典的翻车点血泪经验就是搬数组一定要从目标端开始别从源端开始。右移SWAP_SHU2方向相反从 S2 开始往 S1 搬S9 的值移到 S8。代码里还清了40H和70H两个标志位这两个位在KEY_INT里被置过用来标记移位方向。实际调试时如果发现按键后数字移动方向反了先检查这两个标志位的置位和清零逻辑多半是某个分支漏了CLR或SETB。3.3 退格与清零0BH 和 0CH 的特殊处理DISPLAY1开头有一段键值判断MOV A, R1 XRL A, #0BH JZ DEL1 ; 键值 0BH退格 MOV A, R1 XRL A, #0CH JZ ZERO ; 键值 0CH清零DEL1标号在源码里被注释掉了实际退格逻辑需要自己补。从上下文看退格应该是把最右边一位清零然后触发一次右移或直接刷新显示。清零ZERO子程序则把 S2 到 S9 全部写00H并清除移位标志然后跳回DISPLAY1。这里有个细节ZERO里没有清 S1但 S1 在显示子程序里会被键值对应的段码覆盖所以不影响。如果发现清零后第一位还残留旧数字检查一下ZERO之后是否真的回到了DISPLAY1并且R1被重新赋值。4. 避坑与排查按键不响应、显示乱码、移位错位的常见原因4.1 按键完全无反应KS 读回值始终是 0F0H现象上电后数码管有显示但按任何键都没反应用调试器看KS子程序里MOV A, P1读回的值始终是0F0H。原因P1 口作为输入时没有正确配置。51 单片机 P1 口作为通用 I/O 使用时写 1 才能读外部电平但这份代码在KS里先MOV P1, #0F0H再读理论上没问题。实际翻车多半是硬件上拉电阻没接或阻值太大或者按键另一端没有可靠接地。另一个可能是开发板的 P1 口被其他外设占用比如接了 LCD 或传感器导致电平被拉死。解决先断开所有非键盘外设只保留键盘和数码管用万用表量按键两端在按下时的通断。如果硬件没问题在KS里MOV A, P1之后加一句CPL A或XRL A, #0F0H再判断确保逻辑电平方向对得上。4.2 键值算出来总是差一列或差一行现象按下第一行第一列的键数码管显示的是第二列的数字或者显示的是第二行的数字。原因列扫描值的初始值或移位方向错了。MOV R2, #11110111B对应 P1.3 拉低如果实际硬件上 P1.3 接的是第二列而不是第一列键值就会整体偏移。另外RR A是右循环如果误写成RL A0 会往反方向移扫描顺序完全颠倒。解决拿万用表确认 P1.0~P1.3 分别接键盘的哪一列然后调整R2的初始值和移位指令。如果列顺序是反的可以把RR A改成RL A或者把初始值改成11101111B从 P1.4 那端开始扫。4.3 数码管闪烁或某一位特别暗现象8 位数码管显示时有的位亮有的位暗或者整体有轻微闪烁。原因动态扫描的刷新率不够或者某一位的延时被其他子程序拉长了。DISPLAY1里每位之间的DELAY2是固定的但如果KEY_INT或DELAY1执行时间波动扫描周期就会不均匀。另外如果DELAY2的循环参数R4或R5被其他子程序意外修改延时长度会变。解决把DELAY2的R4和R5改成用PUSH/POP保护或者在调用DELAY2之前重新赋值。如果闪烁严重适当减小R4的值比如从43改成30提高刷新率。但注意别改太小否则数码管亮度会下降。4.4 移位时数字被覆盖或消失现象按数字键后新数字出现但原来的数字不见了或者所有位显示同一个数字。原因移位子程序里搬运顺序反了。前面说过SWAP_SHU1必须从 S9 开始往 S2 搬如果从 S2 开始往 S9 搬S2 的旧值会覆盖 S3S3 的新值又覆盖 S4最后所有位都变成 S1 的值。解决检查SWAP_SHU1和SWAP_SHU2的MOV顺序确保目标寄存器在源寄存器之前被赋值。如果不确定可以在移位前后加两个断点用调试器看 S1~S9 的值变化。4.5 退格键按下后程序跑飞或显示全灭现象按退格键键值 0BH数码管突然全灭或显示乱码再按其他键也没反应。原因源码里DEL1标号被注释掉了JZ DEL1跳转到一个不存在的标号汇编器可能报错或者链接到了错误地址。即使汇编通过DEL1后面的代码如果没写好可能破坏了堆栈或关键寄存器。解决自己补一个DEL1子程序最简单的做法是把 S9 清零然后调用一次SWAP_SHU2把整体右移再跳回DISPLAY1。注意在DEL1里不要动R1否则键值判断会乱。5. 进阶技巧用标志位管理移位方向与显示状态这份代码里用了两个位地址40H和7AH来标记状态。7AH是数码管前移标志40H在KEY_INT里被SETB在SWAP_SHU2里被CLR。这种用位地址做状态机的写法在汇编里很省资源但可读性差调试时容易搞混。我一般会这样做在内存里划一小块区域专门放状态标志比如用30H存移位方向0 左移1 右移用31H存当前显示位数。每次按键处理前先读状态处理完再写回。这样即使程序跑飞用调试器看内存也能快速定位问题。另一个实用技巧是给DISPLAY1加一个“脏标志”。只有当键值变化或移位发生时才刷新数码管没有变化时直接返回减少无效扫描。具体做法是在DISPLAY1开头判断一个位标志如果为 0 就RET为 1 才继续执行显示逻辑。这个标志在KEY_INT算出新键值后置 1在DISPLAY1末尾清 0。这样能明显降低 CPU 占用按键响应也更跟手。验证方法很简单在DISPLAY1的RET前加一句CPL P3.0用示波器或逻辑分析仪看 P3.0 的翻转频率。如果频率随按键变化而变说明脏标志生效了如果一直是固定频率说明标志没起作用检查置位和清零的位置是否配对。从那以后我每次写键盘扫描都会先把状态标志和显示刷新分开按键处理只改标志显示子程序根据标志决定刷不刷。这样即使键盘扫描逻辑再复杂显示也不会跟着乱。希望帮到你。本文还有配套的精品资源点击获取