【51单片机】汇编程序与周期性任务设计

一、51单片机的延时汇编函数

(一)指令周期  时钟周期  机器周期

       在51单片机的延时汇编函数中,涉及到指令周期、机器周期和时钟周期,它们之间存在一定的关系。在编写延时函数时,需要考虑它们之间的关系,以确保延时的准确性和稳定性。通常通过控制循环次数或者使用NOP指令来实现精确的延时。

1.指令周期:
(1)指令周期是执行一条指令所需的时间,通常由若干个机器周期组成。
(2)在51单片机中,每条指令执行的时间称为一个指令周期,其长度由单片机的晶振频率(晶振的振荡周期)决定。 

2.时钟周期:
(1)时钟周期是单片机时钟震荡的一个完整周期,通常等于晶振的振荡周期。
(2)时钟周期是最小的时间单位,用来衡量单片机执行指令的速度和延时的精度。
(3)时钟周期的长度取决于单片机所使用的晶振频率,通常以纳秒(ns)或微秒(μs)为单位。

3.机器周期:
(1)机器周期是单片机执行一条基本操作所需的时间。
(2)在51单片机中,一个机器周期由 12 个时钟周期组成。
(3)对于不同的指令,机器周期可能有所不同。例如,对于MOV指令,需要一个机器周期;而对于AJMP或SJMP指令,需要 2 个机器周期。

(二) 延时函数

1.单重循环延时函数(延时较短)

SingleLoopDelay:
    MOV R0, #10       ; 初始化计数器,较小的值,根据处理器速度调整
DelayLoop:
    NOP               ; 空操作,延时
    DJNZ R0, DelayLoop  ; 计数器减1并跳转,直到计数器为0
    RET               ; 返回

DJNZ指令周期是 CPU 执行指令的过程之一,它包括将目标寄存器的值减一、检查该值是否为零以及根据结果进行跳转操作。这个周期用于实现循环控制和条件跳转,允许程序根据寄存器的值来控制执行流程,从而实现灵活的程序控制逻辑。DJNZ的指令周期为两个机器周期,在12MHz的晶振单片机中,机器周期为1us,即执行DJNZ指令需要2us,每执行一次循环计数器递减,即R0的值减1,不断重复直至R7减到0,指令结束。即DelayLoop语句执行10次,延时10×2us。

代码指令解释:

(1)MOV R0, #0xFF:将寄存器 R0 初始化为 0xFF,即设置一个较大的初值作为延时计数器。
(2)NOP:空操作指令,用于延时。
(3)DJNZ R0, DelayLoop:将 R0 寄存器的值减1,并根据结果进行循环跳转,直到计数器为0时退出循环。
(4)RET:延时完成后返回。

 2.多重循环延时函数(延时较长)

MultiLoopDelay:
    MOV R1, #50       ; 外层计数器的初始值,较大的值
OuterLoop:
    MOV R0, #100      ; 内层计数器的初始值,较大的值
InnerLoop:
    NOP               ; 空操作,延时
    DJNZ R0, InnerLoop  ; 内层计数器减1并跳转,直到计数器为0
    DJNZ R1, OuterLoop  ; 外层计数器减1并跳转,直到计数器为0
    RET               ; 返回

代码指令解释:

(1)MultiLoopDelay:这是一个标签,用于标识代码的起始点或者特定的程序段。
(2)MOV R1, #50:将立即数 50 装载到寄存器 R1 中,作为外层循环的初始计数器值。
(3)OuterLoop:外层循环的标签,用于标识外层循环的起始点。
(4)MOV R0, #100:将立即数 100 装载到寄存器 R0 中,作为内层循环的初始计数器值。
(5)InnerLoop:内层循环的标签,用于标识内层循环的起始点。
(6)NOP:空操作指令,用于占用一个时钟周期,实现延时的目的。
(7)DJNZ R0, InnerLoop:递减寄存器 R0 的值,并根据结果跳转到 InnerLoop 标签处,直到 R0 的值为 0。
(8)DJNZ R1, OuterLoop:递减寄存器 R1 的值,并根据结果跳转到 OuterLoop 标签处,直到 R1 的值为 0。
(9)RET:返回指令,用于结束程序的执行并返回。

 示例: 

以周期性点亮LED灯的延时函数为例,采用了双重循环的方式实现:

DELAY: MOV R7,#250 
D1: MOV R6,#250 
D2: DJNZ R6,D2 		;250*2=500μs
	 DJNZ R7,D1		 ;250*500μs=0.125S
	 RET

MOV R6,#250 指令的执行周期数为 1 个,对应的时钟周期数为 1 个。因此,执行这条指令所需的时间为 1 微秒;DJNZ R6,D2 指令的执行周期数取决于 R6 寄存器的值。在本例中,R6 的初始值为 250,因此内层循环总共执行 250 次。每次执行 DJNZ R6,D2 指令需要 2 个周期。所以总的执行周期数为 250 * 2 = 500 个周期。

现在我们来计算 Delay 函数的总的循环次数和对应的时钟周期总数:

(1)外层循环总共执行了 250 次(因为 DJNZ R7,D1 指令的外层循环计数器 R7 的初始值为 250)。
(2)内层循环每次执行了 250 * 2 = 500 个时钟周期。
(3)因此,总的时钟周期数为 250 * 500 = 125,000 个周期。

将时钟周期数转换为微秒,使用时钟频率为 1 MHz:125,000 us = 125 ms=0.125s
因此,这个 LED 灯大约每隔 0.125s才变化一次亮灭状态。

代码解释如下:

(1)DELAY:延时函数的标签,标识延时函数的起始点。
(2)MOV R7,#250:将立即数 250 装载到寄存器 R7 中,作为外层循环的初始计数器值,控制延时的主要时长。
(3)D1:外层循环的标签,用于标识外层循环的起始点。
(4)MOV R6,#250:将立即数 250 装载到寄存器 R6 中,作为内层循环的初始计数器值,控制内层循环的次数。
(5)D2:内层循环的标签,用于标识内层循环的起始点。
(6)DJNZ R6,D2:内层循环,递减寄存器 R6 的值,并根据结果跳转到标签 D2 处,直到 R6 的值为 0。这个内层循环实现了 250*2=500 微秒的延时。
(7)DJNZ R7,D1:外层循环,递减寄存器 R7 的值,并根据结果跳转到标签 D1 处,直到 R7 的值为 0。这个外层循环控制了整个延时的时长,相当于将内层循环的延时乘以 250,即 250*500 微秒=0.125 秒
(8)RET:返回指令,用于结束延时函数的执行。

问题:

若要实现LED灯每隔1S周期性亮灭,那么是否还能使用二重循环延时函数实现?程序怎么修改? 

1)采用在原来双重循环的基础上,再叠加一层循环,达到三层循环延时的效果 

DELAY:MOV R5,#25
D1: MOV R6,#100
D2: MOV R7,#200
	DJNZ R7,$		;200*2=400μs
	DJNZ R6,D2		;400*100=40000μs
	DJNZ R5,D1		;40000*25=1000000μs
	RET

通过计算,1000000us=1000ms=1s。

2)NOP指令 

NOP 指令是汇编语言中的一种指令,它的全称是 "No Operation",意思是不执行任何操作。在大多数计算机体系结构中,NOP 指令被设计成一个空操作,它不会对寄存器、内存或其他状态做出任何更改,而是简单地在执行过程中占用一个时钟周期或一个指令周期。

NOP指令也为单周期指令即在12MHz单片机中,延时1us。如果在代码中加入NOP指令,效果如下(以延时1S为例):

DELAY:
    MOV R5, #40         ; 设置 R5 初始值为 40,总延时接近 1 秒,40 * 25 = 1000 微秒
D1:
    MOV R6, #200        ; 设置 R6 初始值为 200,总延时接近 1 秒,200 * 5 = 1000 微秒
D2:
    MOV R7, #250        ; 设置 R7 初始值为 250,总延时接近 1 秒,250 * 4 = 1000 微秒
    DJNZ R7, $          ; 循环 250 次,大约延时 500 微秒
    NOP                 ; 添加 NOP 指令增加延时
    DJNZ R7, $          ; 再次循环 250 次,大约延时 500 微秒
    NOP                 ; 添加 NOP 指令增加延时
    DJNZ R6, D2         ; 重复 D2 循环 5 次,延时约 2500 微秒
    NOP                 ; 添加 NOP 指令增加延时
    DJNZ R5, D1         ; 重复 D1 循环 4 次,延时约 10000 微秒
    RET

3.精确延时函数(了解):
这种延时函数通过根据单片机时钟频率计算出精确的延时时间,通常需考虑机器周期和指令周期。

PreciseDelay:
    MOV R1, #HIGH(DelayCount)  ; 将延时计数的高位地址存储到寄存器 R1
    MOV R2, #LOW(DelayCount)   ; 将延时计数的低位地址存储到寄存器 R2
DelayLoop:
    MOV A, R2                   ; 将延时计数的低位加载到累加器
    MOV R3, A                   ; 备份低位延时计数
    MOV A, R1                   ; 将延时计数的高位加载到累加器
    SUBB A, #0x01               ; 高位计数减1
    MOV R1, A                   ; 更新高位计数
    CJNE R1, #0xFF, Continue    ; 判断高位计数是否为0xFF
    DJNZ R3, DelayLoop          ; 如果高位计数为0xFF,则判断低位计数是否为0xFF,并跳转继续循环
Continue:
    DJNZ R1, DelayLoop          ; 高位计数不为0xFF时,继续循环
    RET                         ; 返回

DelayCount: DW 500             ; 延时计数值,根据单片机时钟频率和所需延时时间确定

这段代码通过嵌套的循环结构和空操作指令来实现延时效果,外层循环控制较长的延时,而内层循环则实现精确的计时。 

代码解释:

(1)首先,将延时计数的地址分别存储到寄存器 R1 和 R2 中。
(2)循环中,通过减法指令来实现精确的延时,根据机器周期和指令周期计算延时时间。
(3)DelayCount 是延时计数值,可以根据需要调整以实现不同的延时时间。

 二、汇编语言---用查表法完成求平方数

(一)采用查表法汇编程序

ORG 0000H      ; 定义下一条指令的存储地址为0000H

LJMP A1        ; 长跳转至A1处执行

ORG 0080H      ; 定义地址为0080H的指令

A1: NOP        ; 空操作
    NOP        ; 空操作
    MOV SP, #60H  ; 设置堆栈指针寄存器的值为60H
    MOV DPTR, #2000H  ; 将数据指针寄存器设置为内存地址2000H
    MOV A, #03H  ; 将寄存器A的值设置为03H
    MOVC A, @A+DPTR  ; 间接寻址,将DPTR指向的地址的内容加上A的值,并将结果存入A中
A2: SJMP A2     ; 无条件跳转到标签A2处

ORG 2000H      ; 定义地址为2000H的指令
    DB 01h, 04h, 09h, 10h, 19h, 24h, 31h, 40h, 51h  ; 数据段,存储1到9的平方数

END            ; 程序结束

(1)ORG 0000H: 这条指令定义了下一条指令的存储地址为0000H,即程序的起始地址。
(2)LJMP A1: 这是一条长跳转指令,用于跳转到标签A1处执行。
(3)ORG 0080H: 这条指令定义了地址为0080H的指令。
(4)A1: NOP: 这里是标签A1处的位置,NOP指令表示空操作,不做任何实际的操作。
(5)NOP: 又一条NOP指令,同样是空操作。
(6)MOV SP, #60H: 这条指令将堆栈指针寄存器的值设置为60H,用于设置栈的起始地址。
(7)MOV DPTR, #2000H: 这条指令将数据指针寄存器的值设置为2000H,用于指向数据存储的起始地址。
(8)MOV A, #03H: 这里将寄存器A的值设置为03H。
(9)MOVC A, @A+DPTR: 这是一条间接寻址指令,将DPTR指向的地址的内容加上A的值,并将结果存入A中。
(10)A2: SJMP A2: 这是一个无限循环的标签,通过无条件跳转指令实现了循环。
(11)ORG 2000H: 这条指令定义了地址为2000H的指令,即数据存储的起始地址。
(12)DB 01h, 04h, 09h, 10h, 19h, 24h, 31h, 40h, 51h: 这是一个数据段,存储了1到9的平方数。
(13)END: 程序结束。

 总体来说,这段代码首先通过ORG指令定义了程序的起始地址,然后使用LJMP指令实现了长跳转到标签A1处执行。在A1处,进行了一些操作,包括设置堆栈指针寄存器、数据指针寄存器,以及在指定的内存地址中存储寄存器A的值的平方数。然后通过SJMP指令实现了一个无条件跳转到标签A2处,从而形成一个无限循环。在数据段中,存储了1到9的平方数。

(二)在Edsim51中验证程序

将上述代码放入Edsim51当中进行验证:

(三)在Proteus中验证程序

 在Proteus中创建新项目,复制粘贴汇编代码后,点击调试:

点击单步运行,然后观察ACC的变化: 

最后得到下图结果: 

 三、普中单片机开发板验证实验

普中开发板实物图:

内部LED模块原理图:

 

普中开发板将我们的虚拟仿真实验投影到实物上,在正式操作之前可以先去了解和学习一下其原理和操作:--------------------------------------------------------------------普中51单片机开发板笔记-CSDN博客

那么我们如何将自己写好的程序通过一根连接线烧录到开发板当中呢?这时候就需要运用到一款专门给STC单片机烧录程序的软件----STC-ISP。可以到b站上江协科技的51单片机专题学习主页下下载,这里我直接给出方便大家下载:

下载链接百度网盘 请输入提取码https://jiangxiekeji.com/download.html百度网盘 请输入提取码
提取码7rx1
压缩包解压密码51

(一)LED灯周期性亮灭 

C语言: 

这段代码的主要作用是控制51单片机的P2口的第0个引脚,实现LED灯的周期性闪烁。 

#include <REGX52.H>
#include <INTRINS.H>
void Delay1ms(unsigned int xms)		//@12.000MHz
{
	unsigned char i, j;
	while(xms)
	{
		i = 2;
		j = 239;
		do
		{
			while (--j);
		} while (--i);
		xms--;
	}
}
void main()
{
	while(1)
	{
		P2_0=0;//1111 1110
		Delay1ms(1000);
		P2_0=1;//1111 1111
		Delay1ms(1000);
	}
}
 

(1)#include &lt;REGX52.H&gt;: 这是包含了51单片机的头文件,其中定义了51单片机的寄存器等信息。
(2)#include &lt;INTRINS.H&gt;: 这是包含了一些内部函数的头文件,其中包含了一些汇编实现的内联函数,如延时函数。
(3)void Delay1ms(unsigned int xms): 这是一个延时函数,用于实现毫秒级的延时,参数xms表示要延时的毫秒数。
(4)while(xms): 当要延时的毫秒数不为0时,执行循环。
(5)i = 2; j = 239;: 设置循环计数器的初始值,用于实现延时。
(6)do { ... } while (--i);: 这是一个嵌套的do-while循环,内部的while (--j);用于进行具体的延时操作,外部的while (--i);用于控制延时的次数,实现毫秒级的延时。
(7)xms--;: 每延时一次,将延时的毫秒数减1。
(8)void main(): 主函数的定义。
(9)while(1) { ... }: 无限循环,在循环中控制LED的闪烁。
(10)P2_0=0;: 将P2口的第0个引脚设置为低电平,使LED灯亮起。
(11)Delay1ms(1000);: 调用延时函数延时1秒。
(12)P2_0=1;: 将P2口的第0个引脚设置为高电平,使LED灯熄灭。
(13)Delay1ms(1000);: 再次调用延时函数延时1秒,形成LED周期性闪烁的效果。

汇编语言: 

这段汇编代码的主要功能是使用51单片机控制P2口的输出来实现LED灯的闪烁效果,并且使用延时函数来控制LED的亮灭周期。

ORG 0000h
LJMP MAIN
 
ORG 0100H
MAIN:
 
LOOP:
    MOV A,#0FEH //0111 1111
    MOV P2,A
    LCALL DELAY
    MOV A,#OFFH //11111111
    MOV P2,A
    LCALL DELAY
    LJMP LOOP
 
//延时1秒的函数
DELAY:MOV R5,#10
    D1: MOV R6,#200
    D2: MOV R7,#250
        DJNZ R7,$
        DJNZ R6,D2
        DJNZ R5,D1
        RET
END

(1)ORG 0000h: 这条指令定义了下一条指令的存储地址为0000h,即程序的起始地址。
(2)LJMP MAIN: 这是一条长跳转指令,用于跳转到标签MAIN处执行。
(3)ORG 0100H: 这条指令定义了地址为0100H的指令。
(4)MAIN: 这是一个标签,表示主程序的起始位置。
(5)LOOP: 这也是一个标签,用于定义一个循环体。
(6)MOV A, #0FEH: 将寄存器A的值设置为0FEH,即二进制的0111 1111,用于设置P2口的输出为低电平以点亮LED灯。
(7)MOV P2, A: 将寄存器A的值输出到P2口,控制LED灯点亮。
(8)LCALL DELAY: 调用延时函数,使LED灯保持亮起状态一段时间。
(9)MOV A, #OFFH: 将寄存器A的值设置为OFFH,即二进制的1111 1111,用于设置P2口的输出为高电平以熄灭LED灯。
(10)MOV P2, A: 将寄存器A的值输出到P2口,控制LED灯熄灭。
(11)LCALL DELAY: 再次调用延时函数,使LED灯保持熄灭状态一段时间。
(12)LJMP LOOP: 通过长跳转指令实现循环,使LED灯周期性地点亮和熄灭。
(13)DELAY: 这是一个延时函数的标签。
(14)MOV R5, #10: 将寄存器R5的值设置为10,用于控制延时函数的循环次数。
(15)D1: 延时函数的循环标签。
(16)MOV R6, #200: 将寄存器R6的值设置为200,用于内层延时循环的计数器。
(17)D2: 内层延时循环的标签。
(18)MOV R7, #250: 将寄存器R7的值设置为250,用于控制延时的具体时长。
(19)DJNZ R7, $: 使用减小并跳转指令来实现具体的延时操作。
(20)DJNZ R6, D2: 使用减小并跳转指令来实现内层循环。
(21)DJNZ R5, D1: 使用减小并跳转指令来实现外层循环。
(22)RET: 延时函数结束并返回。
(23)END: 程序结束。

在SCT-ISP中将程序烧录入单片机当中: 

效果图如下:

 

(二)LED流水灯 

C语言: 

这段代码的作用是控制单片机上的LED周期性地闪烁,通过控制引脚的输出状态和延时函数来实现。 

#include <REGX52.H>
#include <INTRINS.H>
void Delay1ms(unsigned int xms)		//@12.000MHz
{
	unsigned char i, j;
	while(xms)
	{
		i = 2;
		j = 239;
		do
		{
			while (--j);
		} while (--i);
		xms--;
	}
}
void main()
{
	while(1)
	{
		P2_0=0;//1111 1110
		Delay1ms(1000);
		P2_0=1;//1111 1111
		Delay1ms(1000);
	}
}
 

(1)#include &lt;REGX52.H&gt; #include &lt;INTRINS.H&gt:这两行代码是包含了51系列单片机的寄存器定义和相关函数的头文件。
(2)void Delay1ms(unsigned int xms)        //@12.000MHz:这是一个延时函数的声明,用于在12MHz的时钟频率下延时指定的毫秒数。
(3)unsigned char i, j;:声明了两个8位无符号整型变量 i 和 j,用于延时循环计数。
(4)while(xms):使用一个 while 循环,当延时的毫秒数 xms 不为零时执行循环体。
(5)do { ... } while (--i):这是一个嵌套的 do-while 循环,用于实现精确的延时。内层循环中,j 从 239 减小到 0,i 从 2 减小到 0,因此实现了较长的延时。
(6)xms--;:在每次循环结束时,减少 xms 的值,实现整体的延时。
(7)P2_0=0; 和 P2_0=1;:这两行代码分别将 P2 口的第一个引脚设置为低电平和高电平,从而控制LED的点亮和熄灭。
(8)Delay1ms(1000);:调用延时函数 Delay1ms,实现毫秒级别的延时,这里延时了1000毫秒,即1秒。
(9)void main():程序的入口函数,主函数从这里开始执行。
(10)while(1):一个无限循环,使程序持续运行。

汇编语言:

 总体上,这段汇编代码通过控制单片机的输出口 P2 来控制LED的点亮和熄灭,并通过延时函数来控制LED的闪烁频率。

ORG 0000h
LJMP MAIN
 
ORG 0100H
MAIN:
 
LOOP:
    MOV A,#0FEH //0111 1111
    MOV P2,A
    LCALL DELAY
    MOV A,#OFFH //11111111
    MOV P2,A
    LCALL DELAY
    LJMP LOOP
 
//延时1秒的函数
DELAY:MOV R5,#10
    D1: MOV R6,#200
    D2: MOV R7,#250
        DJNZ R7,$
        DJNZ R6,D2
        DJNZ R5,D1
        RET
END

1.ORG 0000h LJMP MAIN:将程序的起始地址设置为 0000h,然后跳转到主函数 MAIN 开始执行。
2.ORG 0100H MAIN::将程序的起始地址设置为 0100h,并定义了一个标签 MAIN,表示程序的入口点。
3.MOV A,#0FEHMOV P2,A:将寄存器 A 中的值设置为 0FEH(0111 1111),然后将其移动到 P2 口,控制LED点亮。
4.LCALL DELAY:调用延时函数 DELAY,实现一定时间的延时。
5.MOV A,#OFFHMOV P2,A:将寄存器 A 中的值设置为 OFFH(1111 1111),然后将其移动到 P2 口,控制LED熄灭。
6.LCALL DELAY:再次调用延时函数 DELAY,实现一定时间的延时。
7.LJMP LOOP:跳转回 LOOP 标签处,实现LED的循环闪烁。
8.DELAY 函数:这是一个延时函数,用来实现一定时间的延时。其中使用了多重循环来实现精确的延时。

烧录步骤和LED灯周期性亮灭的步骤相同,此处省略,直接呈现最终结果:

效果图如下:

  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值