【51单片机】汇编程序编写流水灯延迟函数

一、任务目标

  1. 深入了解汇编指令和常用程序结构,以周期性点亮LED灯的延时函数为例,掌握:
    1)通过软件多重循环软件计数的定时方法 ;
    2)通过 循环+ nop 指令的方法;
  2. 掌握汇编语言用查表法完成 求平方数的程序;
  3. 掌握普通单片机实验开发板的开发使用方法,在板子上完成LED周期性点灯的C程序实验。

*思考题
1)请查阅汇编指令 “MOV R6,#250”和“DJNZ R6,D2”的指令周期数,计算其对应的时钟周期 us值;然后计算 Delay函数的总的循环次数和对应的时钟周期总数us, 说明这个LED灯大约每隔多少毫秒(ms)或秒(s)才变化一次亮灭状态;

2)如果要求实现准确的LED 每隔1s亮灭的周期性变化,程序如何修改?请给出完整代码,并在edsim51中进行实践练习。

二、具体过程

2.1 流水灯延迟函数

2.1.1 通过软件多重循环软件计数

简单来说就是通过设置一个计数器变量,随着其逐渐递减实现延迟的效果,当计数器值为0时循环结束。也就是说,计数器变量的数值决定了延迟时间

MOV R1,#0FEH    	;给R1赋值254
LOOP:				;LOOP英文为循环的意思
MOV P1,#0FEH    	;1111 1110,LED1点亮
DJNZ R1,LOOP    	;R1--,当R1=0时,跳出循环,执行下一条指令
MOV P1,#0FDH    	;1111 1101,LED1灭,LED2点亮
;ps:由于Edsim电路原理图中LED共阳极,所以是低电平点亮

请添加图片描述

注意看R1的数值变化以及指令的执行情况,可以观察到,当R1的值等于0后,LED2亮,即第五行指令执行。

2.1.2 循环+ nop 指令

NOP(No Operation)指令是一个空操作指令,它不做任何实质性的操作,只是简单地占用一个指令周期的时间。通过在循环中添加一定数量的nop指令,可以创建一个简单的延时。

MOV R1,#0FEH
LOOP:			;LOOP英文为循环的意思
MOV P1,#0FEH 
DJNZ R1,LOOP 
NOP
NOP
MOV P1,#0FDH    

请添加图片描述
注意看上图中时间变化,一个nop指令1微秒,以上代码加入两个NOP后延长了2微秒的时间。

2.2 查表法求平方数

查表法是一种常用的优化技术,它通过预先计算并存储结果来减少实时计算的时间。对于求平方数这样的操作,我们可以创建一个平方数表,其中存储了0到某个上限的整数的平方值。然后,当我们需要求一个数的平方时,我们只需要查找这个表即可。

ORG 0000H       			 ; 程序起始地址  

SQUARE_TABLE: DB 0, 1, 4, 9, 16, 25, 36  ;建立一个表格用于存储平方数,这里存了1~6的平方数表
    MOV DPTR, #SQUARE_TABLE 	; 设置数据指针DPTR指向平方数表  
    MOV R1, #6             ;6放入R1寄存器  
    CALL SQUARE_FUNCTION   ; 调用求平方函数  

SQUARE_FUNCTION:  
    MOV A, R1        	; 将待求平方的数移动到累加器A中  
    MOVC A, @A+DPTR 	; 使用MOVC指令和DPTR寄存器从SQUARE_TABLE中查表  
    MOV R2, A        	; 将查找到的结果移动到R2寄存器中  
    RET              	; 返回  
  
END     
;注意,在仿真过程中发现无法存储超过7个的平方数值,否则程序不往下运行     

请添加图片描述
仿真后我们可以在R2中查找到所求平方数的数值。这里别看R2显示的是0x24便以为哪里出了问题,其实是读取数据的时候将十进制数转化为十六进制数。0x24对应0010 0100,即十进制的36。
在这里插入图片描述
查表法的核心思想在于利用数据指针DPTR,通过间接寻址指令MOVC A, @A+DPTR,计算出从中移动字节的代码存储器地址,然后读取相应的值。

2.3 点亮流水灯

掌握普通单片机实验开发板上的开发使用方法。详情可以参考b站江协科技51单片机入门教程的P2,安装好STC-ISP。
主要步骤如下:
请添加图片描述请添加图片描述
请添加图片描述
值得注意的是,型号别选错了。 根据开发板上主芯片的型号选择。

#include <REGX52.H>
#include<intrins.h>
void Delay_ms(int ms)//延时函数
{
	int i,j;
	for(i=0;i<50;i++)
	{
		for(j=0;j<ms;j++)
		{
			_nop_();//intrins头文件里的函数,无意义指令,占一个指令周期,起到延时的作用
		}
	}
}
void main()
{
	while(1)
	{
		P2=0xFE;//1111 1110
		Delay_ms(500);//想延时多久只需更改括号里的数值
		P2=0xFD;//1111 1101
	 	Delay_ms(500);
	  	P2=0xFB;//1111 1011
	  	Delay_ms(500);
	  	P2=0xF7;//1111 0111
	  	Delay_ms(500);
		P2=0xEF;//1110 1111
		Delay_ms(500);
		P2=0xDF;//1101 1111
		Delay_ms(500);
		P2=0xBF;//1011 1111
		Delay_ms(500);
		P2=0x7F;//0111 1111
		Delay_ms(500);
	}
}
//流水灯从LED1开始左往右亮灭
#include <REGX52.H>
#include<intrins.h>
void Delay_ms(int ms)
{
	int i,j;
	for(i=0;i<50;i++)
	{
		for(j=0;j<ms;j++)
		{
			_nop_();
		}
	}
}
void main()
{
	int i,temp=0xFE;
	P2=temp;
	while(1)
	{
		for(i=0;i<8;i++)
		{
			P2=~(0x01<<i);
			//0x01即0000 0001,<<是左移1位,即0000 0010,~是取反。即意味着从1111 1110变为1111 1101
			Delay_ms(50);
		}
	}
}

流水灯的几种代码思路可以参考http://t.csdnimg.cn/lPKpg,以上两个分别是我觉得最易懂和最简洁的代码

三、思考拓展

3.1 延时时间计算

MOV R6,#250占一个指令周期,对应1us;
DJNZ R6,D2也占两个指令周期,对应2us。
以以下代码为例,下面是一个嵌套循环。

DEL: MOV R7,#200			
DEL1: MOV R6,#125 			
DEL2: DJNZ R6,DEL2			
      DJNZ R6,DEL2          
      RET

类比C语言代码如下:

for(int i=200;i>0;i--)
{
	for(int j=125;j>0;j--)
	{
	
	}
}

已知一个DJNZ指令对应2us,总共进行200*125次,即50000us即0.5s。从而实现延时0.5ms的效果。

ORG 0000H
LJMP MAIN

ORG 0100H
MAIN:
MOV A,#254
LOOP:        ;循环标签
MOV P1,A     ;将累加器A的内容移动到端口P0
LCALL DELAY  ;长调用指令,它调用DELAY子程序
RL A         ;将累加器A的内容左旋一位
LJMP LOOP

DELAY:
DEL: MOV R7,#200
DEL1: MOV R6,#125
DEL2: DJNZ R6,DEL2
      DJNZ R7,DEL1
      RET
END

通过仿真我们可以观察到这个LED灯大约每隔50毫秒(ms)才变化一次亮灭状态。

3.2 控制延时时长

要求实现准确的LED 每隔1s亮灭的周期性变化,则需要将DELAY函数中的参数变化。由于上面DELAY延时了50ms,想要控制1s亮灭,将嵌套循环整体增大20倍即可,但是值得注意的是!
51内存只有256,存不下大于255的数字,因此我们不能给R7\R6的赋值翻倍,而是再加入一层嵌套
具体修改如下:

ORG 0000H
LJMP MAIN

ORG 0100H
MAIN:
MOV A,#254
LOOP:        ;循环标签
MOV P1,A     ;将累加器A的内容移动到端口P0
LCALL DELAY  ;长调用指令,它调用DELAY子程序
RL A         ;将累加器A的内容左旋一位
LJMP LOOP

DELAY:
DEL: MOV R7,#200
DEL1: MOV R6,#125
DEL2: MOV R5,#20
DEL3: DJNZ R5,DEL3
	  DJNZ R6,DEL2
      DJNZ R7,DEL1
      RET
END

以上,基本实现1s控制LED亮灭操作,因为delay函数中mov语句也耗费了一些时钟周期,所以存在一定偏差。

四、总结反思

汇编指令的内核与C语言是相似的。在使用汇编语言的过程中,可以首先尝试用C语言的思考逻辑思考,再转化为汇编语言。
当然,在仿真的过程中会遇到很多疑问,这些主要受限于硬件原因,例如51的内存只有256B,超过255的数无法计入,超出内存的指令也无法执行。
其次就是数值转换的问题,虽然我们输入的是十进制数,但内存存储的是十六进制数,因此我们在观察寄存器数值的时候,不要因为寄存器的值与输入的值不一样就直接否认,而是要思考一下进制转换的问题。

以上是我的个人学习记录,欢迎各位大佬指正其中的错误

在这里插入图片描述

相关链接

  • 汇编指令大全
    链接:https://pan.baidu.com/s/1CvWQBjs0bJxp0j__Nzruvw?pwd=82i5
    提取码:82i5
  • 34
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值