51汇编仿真proteus8.15学习篇二(附源码)

文章目录


一、 前言

汇编语言是二进制指令的文本形式,与指令是一一对应的关系。比如,加法指令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汇编仿真,我们亲眼看到了代码如何驱动硬件。算是为"更深层次了解单片机工作原理"这个目标迈出了一小步。感谢观看,后续继续分享!

再次感谢你的观看!

相关推荐
a程序小傲11 分钟前
得物Java面试被问:方法句柄(MethodHandle)与反射的性能对比和底层区别
java·开发语言·spring boot·后端·python·面试·职场和发展
saoys15 分钟前
Opencv 学习笔记:一文掌握四种经典图像滤波(均值 / 高斯 / 中值 / 双边)
笔记·opencv·学习
独自破碎E18 分钟前
比较版本号
java·开发语言
●VON21 分钟前
可信 AI 认证:从技术承诺到制度信任
人工智能·学习·安全·制造·von
zimoyin25 分钟前
浅浅了解下0拷贝技术
java·linux·开发语言
AI架构师易筋29 分钟前
AIOps 告警归因中的提示工程:从能用到可上生产(4 阶梯)
开发语言·人工智能·llm·aiops·rag
你的冰西瓜37 分钟前
C++中的array容器详解
开发语言·c++·stl
逑之1 小时前
C语言笔记16:文件操作
c语言·笔记·单片机
随丶芯1 小时前
IDEA安装leetcode-editor插件
java·开发语言
一瞬祈望1 小时前
⭐ 深度学习入门体系(第 11 篇): 卷积神经网络的卷积核是如何学习到特征的?
深度学习·学习·cnn