文章目录
- [一、 前言](#一、 前言)
- 二、仿真
-
- 1.006-报警产生器
-
- [1.1 电路仿真](#1.1 电路仿真)
- [1.2 仿真程序](#1.2 仿真程序)
- 2.007-I_O并行口直接驱动LED显示
-
- [2.1 电路仿真](#2.1 电路仿真)
- [2.2 仿真程序](#2.2 仿真程序)
- 3.008-按键识别方法之一
-
- [3.1 电路仿真](#3.1 电路仿真)
- [3.2 仿真程序](#3.2 仿真程序)
- 4.009-一键多功能按键识别技术
-
- [4.1 电路仿真](#4.1 电路仿真)
- [4.2 仿真程序](#4.2 仿真程序)
- 5.010-00-99计数器
-
- [5.1 电路仿真](#5.1 电路仿真)
- [5.2 仿真程序](#5.2 仿真程序)
- 三、总结
一、 前言
汇编语言是二进制指令的文本形式,与指令是一一对应的关系。比如,加法指令00000011写成汇编语言就是 ADD。只要还原成二进制,汇编语言就可以被 CPU 直接执行,所以它是最底层的低级语言。
最近对汇编的仿真产生兴趣,虽然尚未系统学习,但搜集了一些资料尝试实践。学习汇编有助于更深入理解硬件工作原理,顺带记录一下学习过程,也欢迎交流指正。
二、仿真
1.006-报警产生器
/*
006-报警产生器
一个典型的可切换频率方波发生器程序
模式1(FLAG=1):低频方波
周期:2ms(500Hz)
高电平时间:1ms,低电平时间:1ms
占空比:50%
总持续时间:200 × 2ms = 400ms
模式2(FLAG=0):高频方波
周期:1ms(1kHz)
高电平时间:0.5ms,低电平时间:0.5ms
占空比:50%
总持续时间:200 × 1ms = 200ms
*/

1.1 电路仿真


1.2 仿真程序
c
/*
006-报警产生器
一个典型的可切换频率方波发生器程序
模式1(FLAG=1):低频方波
周期:2ms(500Hz)
高电平时间:1ms,低电平时间:1ms
占空比:50%
总持续时间:200 × 2ms = 400ms
模式2(FLAG=0):高频方波
周期:1ms(1kHz)
高电平时间:0.5ms,低电平时间:0.5ms
占空比:50%
总持续时间:200 × 1ms = 200ms
*/
FLAG BIT 00H ; 定义位变量FLAG,使用内部RAM位寻址区的00H位(20H.0)
; FLAG用于标记当前的输出频率模式
ORG 00H ; 程序从地址00H开始执行
START: JB P1.7, START ; 检测P1.7引脚电平
; 如果P1.7=1(高电平),跳转回START(等待)
; 如果P1.7=0(低电平),顺序执行(相当于按键按下才开始)
JNB FLAG, NEXT ; 判断FLAG标志位的状态
; 如果FLAG=0,跳转到NEXT标号处
; 如果FLAG=1,顺序执行
; ===== FLAG=1时的模式:低频方波(周期约1秒) =====
MOV R2, #200 ; 设置循环计数器R2=200次
DV: CPL P1.0 ; 取反P1.0引脚电平(低电平变高,高电平变低)
LCALL DELY500 ; 调用延时500μs
LCALL DELY500 ; 再次调用延时500μs,总共延时1000μs(1ms)
DJNZ R2, DV ; R2减1,不为0则跳转回DV继续循环
; 这个循环产生200个周期,每个周期2ms,总时间400ms
CPL FLAG ; 取反FLAG标志位(1变0,0变1),切换模式
; 执行完低频模式后切换到高频模式
; ===== FLAG=0时的模式:高频方波(周期约0.5秒) =====
NEXT: MOV R2, #200 ; 设置循环计数器R2=200次
DV1: CPL P1.0 ; 取反P1.0引脚电平
LCALL DELY500 ; 调用延时500μs(只调用一次)
DJNZ R2, DV1 ; R2减1,不为0则跳转回DV1继续循环
; 这个循环产生200个周期,每个周期1ms,总时间200ms
CPL FLAG ; 取反FLAG标志位,切换模式
; 执行完高频模式后切换到低频模式
SJMP START ; 跳转回START,重新检测P1.7并开始新一轮输出
; ===== 500微秒延时子程序 =====
DELY500: MOV R7, #250 ; 设置延时循环计数器R7=250
LOOP: NOP ; 空操作指令,消耗1个机器周期
DJNZ R7, LOOP ; R7减1,不为0则跳转回LOOP
RET ; 返回主程序
END ; 程序结束
2.007-I_O并行口直接驱动LED显示
/*
007-I_O并行口直接驱动LED显示
一个经典的共阴数码管动态显示程序
共阴数码管会以约0.2秒的间隔依次显示:0 → 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9 → 0 ... 循环不断。
*/
LED数码显示原理
七段LED显示器内部由七个条形发光二极管和一个小圆点发光二极管组成,根据各管的极管的接线形式,可分成共阴极型和共阳极型。
LED数码管的g~a七个发光二极管因加正电压而发亮,因加零电压而不以发亮,不同亮暗的组合就能形成不同的字形,这种组合称之为字形码,下面给出共阴极的字形码见表

由于显示的数字0-9的字形码没有规律可循,只能采用查表的方式来完成我们所需的要求了。这样我们按着数字0-9的顺序,把每个数字的笔段代码按顺序排好!建立的表格如下所示:TABLE DB 3FH,06H,5BH,4FH,66H,6DH,7DH,07H,7FH,6FH

2.1 电路仿真

2.2 仿真程序
c
/*
007-I_O并行口直接驱动LED显示
一个经典的数码管动态显示程序
数码管会以约0.2秒的间隔依次显示:0 → 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9 → 0 ... 循环不断。
*/
ORG 0 ; 程序从地址0开始执行
START: MOV R1, #00H ; 初始化计数器R1=0,用于索引数码管显示数据
; ===== 主循环:控制数码管显示 =====
NEXT: MOV A, R1 ; 将计数器值送入累加器A
MOV DPTR, #TABLE ; 将数据指针DPTR指向七段码表的起始地址
MOVC A, @A+DPTR ; 查表指令:读取七段码数据
; A = TABLE[R1],获取对应数字的七段码
MOV P0, A ; 将七段码数据输出到P0端口,控制数码管显示
LCALL DELAY ; 调用延时子程序,保持当前显示
INC R1 ; 计数器R1加1,指向下一个数字
CJNE R1, #10, NEXT ; 比较R1是否等于10
; 如果R1≠10,跳转到NEXT继续显示下一个数字
; 如果R1=10,顺序执行(已显示完0-9)
LJMP START ; 跳转回START,重新从0开始显示
; ===== 延时子程序 =====
DELAY: MOV R5, #20 ; 外层循环计数器R5=20
D2: MOV R6, #20 ; 中层循环计数器R6=20
D1: MOV R7, #248 ; 内层循环计数器R7=248
DJNZ R7, $ ; R7减1,不为0则跳转到当前地址(短暂延时)
DJNZ R6, D1 ; R6减1,不为0则跳转到D1
DJNZ R5, D2 ; R5减1,不为0则跳转到D2
RET ; 返回主程序
; ===== 七段数码管码表(共阳极) =====
TABLE:
DB 3FH ; 数字0: 0011 1111 → gfedcba (a段亮)
DB 06H ; 数字1: 0000 0110 → gfedcba (b,c段亮)
DB 5BH ; 数字2: 0101 1011 → gfedcba (a,b,d,e,g段亮)
DB 4FH ; 数字3: 0100 1111 → gfedcba (a,b,c,d,g段亮)
DB 66H ; 数字4: 0110 0110 → gfedcba (b,c,f,g段亮)
DB 6DH ; 数字5: 0110 1101 → gfedcba (a,c,d,f,g段亮)
DB 7DH ; 数字6: 0111 1101 → gfedcba (a,c,d,e,f,g段亮)
DB 07H ; 数字7: 0000 0111 → gfedcba (a,b,c段亮)
DB 7FH ; 数字8: 0111 1111 → gfedcba (全部段亮)
DB 6FH ; 数字9: 0110 1111 → gfedcba (a,b,c,d,f,g段亮)
END ; 程序结束
3.008-按键识别方法之一
/*
008-按键识别方法之一
一个典型的按键计数器程序
初始状态:显示0(所有LED灭,因为0取反后是255=11111111B)
第1次按键:显示1(LED0亮)
第2次按键:显示2(LED1亮)
第3次按键:显示3(LED0和LED1亮)
...直到255后重新从0开始
*/

要把我们手上的干扰信号以及按键的机械接触等干扰信号给滤除掉,一般情况下,我们可以采用电容来滤除掉这些干扰信号,但实际上,会增加硬件成本及硬件电路的体积,这是我们不希望,总得有个办法解决这个问题,因此我们可以采用软件滤波的方法去除这些干扰信号,一般情况下,一个按键按下的时候,总是在按下的时刻存在着一定的干扰信号,按下之后就基本上进入了稳定的状态。具体的一个按键从按下到释放的全过程的信号图如上图所示:
从图中可以看出,我们在程序设计时,从按键被识别按下之后,延时5ms以上,从而避开了干扰信号区域,我们再来检测一次,看按键是否真得已经按下,若真得已经按下,这时肯定输出为低电平,若这时检测到的是高电平,证明刚才是由于干扰信号引起的误触发,CPU就认为是误触发信号而舍弃这次的按键识别过程。从而提高了系统的可靠性。
对于按键识别的指令,我们依然选择如下指令JB BIT,REL指令是用来检测BIT是否为高电平,若BIT=1,则程序转向REL处执行程序,否则就继续向下执行程序。或者是 JNB BIT,REL指令是用来检测BIT是否为低电平,若BIT=0,则程序转向REL处执行程序,否则就继续向下执行程序。

3.1 电路仿真

3.2 仿真程序
c
/*
008-按键识别方法之一
一个典型的按键计数器程序
初始状态:显示0(所有LED灭,因为0取反后是255=11111111B)
第1次按键:显示1(LED0亮)
第2次按键:显示2(LED1亮)
第3次按键:显示3(LED0和LED1亮)
...直到255后重新从0开始
*/
ORG 0
START:
MOV R1, #00H ; 初始化计数器为0
MOV A, R1 ; A = 0000 0000B
CPL A ; 取反:A = 1111 1111B(全灭)
MOV P1, A ; 输出到P1口
; ===== 按键检测循环 =====
REL:
JB P3.7, REL ; 等待按键按下(P3.7=0)
LCALL DELAY10MS ; 延时消抖
JB P3.7, REL ; 再次确认是否真的按下
; ===== 按键处理 =====
INC R1 ; 计数器加1
MOV A, R1 ; 计数值送A
CPL A ; 取反(共阳极需要取反)
ANL A, #0FH ; 只保留低4位,高4位清零(因为你只有4个LED)
MOV P1, A ; 输出到P1口
WAIT_RELEASE:
JNB P3.7, WAIT_RELEASE ; 等待按键释放
SJMP REL ; 继续检测
; ===== 10ms延时子程序 =====
DELAY10MS:
MOV R6, #20
L1:
MOV R7, #248
DJNZ R7, $
DJNZ R6, L1
RET
END
4.009-一键多功能按键识别技术
/*
009-一键多功能按键识别技术
一个按键控制4个LED的状态切换
第1次按键:LED1开始闪烁
第2次按键:LED2开始闪烁(LED1停止)
第3次按键:LED3开始闪烁(LED2停止)
第4次按键:LED4开始闪烁(LED3停止)
第5次按键:LED1开始闪烁(LED4停止)...循环
*/

4.1 电路仿真

4.2 仿真程序
c
/*
009-一键多功能按键识别技术
一个按键控制4个LED的状态切换
第1次按键:LED1开始闪烁
第2次按键:LED2开始闪烁(LED1停止)
第3次按键:LED3开始闪烁(LED2停止)
第4次按键:LED4开始闪烁(LED3停止)
第5次按键:LED1开始闪烁(LED4停止)...循环
*/
; ===== 符号定义 =====
ID EQU 30H ; 定义ID变量,使用内部RAM的30H单元作为状态计数器
SP1 BIT P3.7 ; 定义SP1为P3.7引脚(按键)
L1 BIT P1.0 ; 定义L1为P1.0引脚(LED1)
L2 BIT P1.1 ; 定义L2为P1.1引脚(LED2)
L3 BIT P1.2 ; 定义L3为P1.2引脚(LED3)
L4 BIT P1.3 ; 定义L4为P1.3引脚(LED4)
ORG 0 ; 程序从地址0开始
; ===== 初始化 =====
MOV ID, #00H ; 初始化状态计数器ID为0
; ===== 主程序开始 =====
START: JB SP1, REL ; 检测按键SP1是否按下
; 如果SP1=1(未按下),跳转到REL
; 如果SP1=0(按下),顺序执行
LCALL DELAY10MS ; 调用10ms延时去抖动
JB SP1, REL ; 再次检测按键,如果SP1=1(抖动误判),跳回REL
; 如果SP1=0(真的按下),顺序执行
; ===== 按键处理:状态切换 =====
INC ID ; 状态计数器加1(ID: 0→1→2→3→4...)
MOV A, ID ; 将ID值送入累加器A
CJNE A, #04, REL ; 比较A是否等于4
; 如果A≠4,跳转到REL
; 如果A=4,顺序执行(需要重置)
MOV ID, #00H ; 重置状态计数器为0(实现0-3循环)
; ===== 等待按键释放 =====
REL: JNB SP1, $ ; 等待按键释放(SP1变为高电平)
; 如果SP1=0(仍按下),在此循环等待
; 如果SP1=1(已释放),顺序执行
; ===== 根据状态ID执行相应的LED控制 =====
MOV A, ID ; 将当前状态值送入A
CJNE A, #00H, IS0 ; 如果A≠0,跳转到IS0
; 如果A=0,顺序执行(控制LED1)
CPL L1 ; 取反LED1的状态(亮变灭,灭变亮)
LCALL DELAY ; 调用延时,保持当前状态
SJMP START ; 跳回START,继续检测按键
IS0: CJNE A, #01H, IS1 ; 如果A≠1,跳转到IS1
; 如果A=1,顺序执行(控制LED2)
CPL L2 ; 取反LED2的状态
LCALL DELAY
SJMP START
IS1: CJNE A, #02H, IS2 ; 如果A≠2,跳转到IS2
; 如果A=2,顺序执行(控制LED3)
CPL L3 ; 取反LED3的状态
LCALL DELAY
SJMP START
IS2: CJNE A, #03H, IS3 ; 如果A≠3,跳转到IS3
; 如果A=3,顺序执行(控制LED4)
CPL L4 ; 取反LED4的状态
LCALL DELAY
SJMP START
IS3: LJMP START ; 如果A>3(理论上不会执行到这里),跳回START
; ===== 10ms延时子程序(用于按键消抖) =====
DELAY10MS:
MOV R6, #20 ; 外层循环计数器
LOOP1: MOV R7, #248 ; 内层循环计数器
DJNZ R7, $ ; R7减1,不为0则跳转到当前地址
DJNZ R6, LOOP1 ; R6减1,不为0则跳转到LOOP1
RET ; 返回
; ===== 长延时子程序(用于LED状态保持) =====
DELAY: MOV R5, #20 ; 外层循环计数器(20×10ms=200ms)
LOOP2: LCALL DELAY10MS ; 调用10ms延时
DJNZ R5, LOOP2 ; R5减1,不为0则跳转到LOOP2
RET ; 返回
END ; 程序结束
5.010-00-99计数器
/*
010-00-99计数器
一个典型的两位数计数器程序
初始显示:00
第1次按键:显示01
第2次按键:显示02
...
第99次按键:显示99
第100次按键:显示00(重新开始)
*/

5.1 电路仿真

5.2 仿真程序
c
/*
010-00-99计数器
一个典型的两位数计数器程序
初始显示:00
第1次按键:显示01
第2次按键:显示02
...
第99次按键:显示99
第100次按键:显示00(重新开始)
*/
Count EQU 30H ; 定义计数器变量,使用内部RAM的30H单元
SP1 BIT P3.7 ; 定义SP1为P3.7引脚(按键)
ORG 0 ; 程序从地址0开始
START: MOV Count, #00H ; 初始化计数器为0
; ===== 主显示循环 =====
NEXT: MOV A, Count ; 将计数值送入累加器A
MOV B, #10 ; 除数10送入B寄存器
DIV AB ; A除以B:A=商(十位数),B=余数(个位数)
; 例如:Count=25 → A=2(十位), B=5(个位)
; ===== 显示十位数 =====
MOV DPTR, #TABLE ; 数据指针指向七段码表
MOVC A, @A+DPTR ; 查表获取十位数的七段码
MOV P0, A ; 十位数送到P0口(十位数码管)
; ===== 显示个位数 =====
MOV A, B ; 将个位数从B寄存器送入A
MOVC A, @A+DPTR ; 查表获取个位数的七段码
MOV P2, A ; 个位数送到P2口(个位数码管)
; ===== 按键检测 =====
WT: JNB SP1, WT ; 等待按键按下(SP1=0)
; 如果SP1=1(未按下),在此循环等待
; 如果SP1=0(按下),顺序执行
WAIT: JB SP1, WAIT ; 等待按键释放(SP1=1)
; 如果SP1=0(仍按下),在此循环等待
; 如果SP1=1(已释放),顺序执行
LCALL DELY10MS ; 调用10ms延时去抖动
JB SP1, WAIT ; 再次确认按键是否真的释放
; 如果SP1=1(已释放),顺序执行
; 如果SP1=0(抖动),跳回WAIT
; ===== 计数处理 =====
INC Count ; 计数器加1
MOV A, Count ; 计数值送入A
CJNE A, #100, NEXT ; 比较计数值是否等于100
; 如果A≠100,跳转到NEXT继续计数
; 如果A=100,顺序执行(需要归零)
LJMP START ; 跳回START,计数器归零重新开始
; ===== 10ms延时子程序 =====
DELY10MS: MOV R6, #20 ; 外层循环计数器
D1: MOV R7, #248 ; 内层循环计数器
DJNZ R7, $ ; R7减1,不为0则跳转到当前地址
DJNZ R6, D1 ; R6减1,不为0则跳转到D1
RET ; 返回
; ===== 七段数码管码表 =====
TABLE:
DB 3FH ; 数字0: 0011 1111
DB 06H ; 数字1: 0000 0110
DB 5BH ; 数字2: 0101 1011
DB 4FH ; 数字3: 0100 1111
DB 66H ; 数字4: 0110 0110
DB 6DH ; 数字5: 0110 1101
DB 7DH ; 数字6: 0111 1101
DB 07H ; 数字7: 0000 0111
DB 7FH ; 数字8: 0111 1111
DB 6FH ; 数字9: 0110 1111
END ; 程序结束
三、总结
今天通过几个简单的51汇编仿真,我们亲眼看到了代码如何驱动硬件。算是为"更深层次了解单片机工作原理"这个目标迈出了一小步。感谢观看,后续继续分享!
再次感谢你的观看!
