汇编程序与周期性任务设计

本文详细解析了一个LED周期性闪烁的汇编程序,包括指令周期计算、DELAY函数的执行次数和时间,以及如何修改代码实现1s亮灭周期。同时介绍了用查表法求平方数的方法,并在普中单片机开发板上实践了流水灯和LED灯的控制。
摘要由CSDN通过智能技术生成

文章目录

一、如图有一个LED周期性闪烁的汇编程序。

1.在12MHz晶振(时钟频率)的条件下,请查阅汇编指令 “MOV R6,#250”和“DJNZ R6,D2”的指令周期数,计算其对应的时钟周期 us值;然后计算 DELAY函数中总的循环次数和对应的时间us, 说明这个LED灯大约每隔多少毫秒(ms)或秒(s)才变化一次亮灭状态;

img

(1)指令周期数及其对应的机器周期

(Ⅰ)指令周期数的判断:说人话就是一个指令中含有几个操作就有几个机器周期。太符合直觉了,指令中操作越多,(指令)周期数越多,时间越长。很好理解。

(Ⅱ)进行指令周期数的判断

①在8051指令集中查到对于MOV指令的描述如下:

在这里插入图片描述

翻译成中文即为:
描述:MOV将操作数2的值复制到操作数1。操作数2的值不受影响。操作数1和操作数2都必须在内部RAM中。除非指令正在移动一个位的值进入进位位,否则不会影响任何标志,或者除非指令正在移动一个值到PSW寄存器(包含所有程序标志)。

注意:在“MOV iram addr,iram addr”的情况下,指令的操作字节以相反的顺序存储。也就是说,由0x85、0x20、0x50组成的指令意味着“将内部RAM位置0x20的内容移到内部RAM位置0x50”,而通常会假设相反的情况。

指令周期的判断:从描述的第一句话我们不难看出,MOV只涉及到了一个立即数传送指令,说人话就是一个赋值操作,因此它是一个单周期指令

②在8051指令集中查到对于DJNZ指令的描述如下:

在这里插入图片描述

翻译成中文即为:
描述:DJNZ将寄存器的值减去1。如果寄存器的初始值为0,那么减法操作会使它重置为255(0xFF十六进制)。如果新的寄存器值不为0,则程序会跳转到由relative addr指示的地址。如果新的寄存器值为0,则程序流程将继续执行DJNZ指令之后的指令。

指令周期的判断:显然,DJNZ包含两个操作,Decrement(减一)和Jump(跳转),那么它是一个双周期指令

(2)计算指令的机器周期数和时间

因为在12MHz的时钟频率下,单个机器周期为:1*10^(-6) s,即为1 us

那么,MOV指令的时钟周期为:1 * 1=1 usDJNZ指令的时钟周期为:2 * 1=2us

###(2)计算 DELAY函数中总的循环次数和对应的时钟周期个数, 说明这个LED灯大约每隔多少毫秒(ms)或秒(s)才变化一次亮灭状态;

先单独分析DELAY函数

在这里插入图片描述

第一行和第二行:就是简单的赋值操作,很容易看懂。

第三行:第三行的R6中的数据减1,继续跳转到执行D2(还是第三行)。即R6中的数据要从250减到0,才能执行完第三行,继续下一行。

第四行:执行一次第四行R7中的数据减1,又跳转到执行D1(第二行)给R6赋值,然后第三行。

综合第三、四行分析,显然这是一个双重循环,只不过内层循环(第三行)写到了外层循环(第四行)的前面,这与C语言中的代码还是有所不同。下图是C语言对应的第三、四行循环,可以将两者进行参考比较。

在这里插入图片描述

第五行:返回到调用DELAY函数处,执行调用完DELAY函数的下一条语句。

①DELAY函数中总的循环次数

因此,DELAY函数中总的循环次数为:250*250=62500次

②DELAY函数中总的时钟周期

分析

第一行:给R7赋值,执行1次MOV。

第二三四行:双重循环中,R7共减250次(执行250次DJNZ),R6共减62500次(执行250次DJNZ)赋值250次(执行MOV次250)。即MOV执行250次,DJNZ执行250+62500=62750次。

综上在DELAY中共执行:MOV=251次,DJNZ=62750次。进行一次DELAY所需的机器周期个数为251 + 62750 * 2=125751个,时钟周期个数为125751 * 12=1509012个。

③说明这个LED灯大约每隔多少毫秒(ms)或秒(s)才变化一次亮灭状态

在LOOP中可以看出调用一次DELAY改变一次亮灭(不再考虑LOOP中的LCALL、CLR、AJMP,因为单个指令相对于DELAY中的几万个指令影响实在太小),即经过约125751*1=125751us≈125ms发生一次亮灭转换。

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

(1)通过软件多重循环软件计数的定时方法

思路:在1(2)③中我们已经计算出了LED一次亮灭转换的时间为125ms,双重循环所需要的时间为125ms。为了达到一次亮灭转换的时间为1s,那么我们需要在这个双重循环的外部再给它套一个循环,且该循环的循环次数为8即可。

代码部分如下:

在这里插入图片描述

运行结果

在这里插入图片描述

从手工测量可以测得一次亮灭转换的时间约为2s95ms÷2=1s48ms,即1.048s,在误差允许范围内接近1s(没有考虑中层循环和外层循环的指令运行时间,以及人为控制测量起止)。

(2)通过循环+nop指令的方法

首先在8051指令集中查到对于MOV指令的描述如下:
在这里插入图片描述

翻译成中文即为:

描述:NOP,正如它的名字所暗示的那样,在一个机器周期内不执行任何操作。NOP通常只用于计时目的。绝对没有任何标志或寄存器受到影响。

又查到NOP是一个单周期指令 ,即运行一次NOP需要的时间为1us

思路:由1可知目前已经延时125000us,且内层循环为62500次。那么只需多延时1000000-125000=875000us即可。即在内层循环中加875000÷62500÷1=14个NOP即可。

代码部分如下:
在这里插入图片描述

运行结果:

在这里插入图片描述

从手工测量可以测得一次亮灭转换的时间约为2s80ms÷2=1s40ms,即1.040s,在误差允许范围内接近1s

(3)在Keil的Debug Session中测试一个DELAY函数运行的时间

在这里插入图片描述

观察到一个DELAY用时确实1s左右,即灯泡亮暗状态转变1次为1s,设计代码正确。

二、用查表法完成求平方数的程序

1.说明

为了好阐述代码原理,我们先了解几个概念。

ORG:ORG 是汇编语言中的一个伪指令(Pseudo-opcode),主要用于指定程序的当前汇编地址。在汇编源代码中,ORG 指令用来改变程序计数器(Program Counter,PC)的值,指示汇编器从此处开始放置机器码。换句话说,它定义了程序段的起始地址或相对于上一个 ORG 命令的偏移地址。
A:Accumulator,即累加器。

DPTR:Data Pointer (or Data Pointer Register) ,即数据指针。在MCS-51系列单片机中,由于DPTR是一个16位寄存器,所以也称为Data Pointer Register Pair,即数据指针寄存器对,由高位字节DPH和低位字节DPL两个8位寄存器组成。

DB:Define Byte,即定义字节。是汇编语言中的一个伪指令。编写51单片机的汇编程序时,使用“DB”可以声明一组字节型数据变量或者初始化内存区域。表达式中可包含符号、字符串、或表达式等项,各个项之间用逗号隔开(每个项占8位),字符串应用引号括起来。

MOVC:MOVC指令将一个字节从代码存储器移动到累加器。要移动的字节的代码存储器地址是通过将累加器的值与DPTR或程序计数器(PC)相加计算得出的。在使用程序计数器的情况下,PC首先被增加1,然后才与累加器相加。

变址寻址:

在这里插入图片描述

2.代码部分

ORG 0000H
Square_Table: DB 1,4,9,16,25,36,49,64,81;建立一个平方数表存储1到9的平方值
MOV DPTR,#SQUARE_TABLE;标签名就是地址,将平方数表的地址赋值给数据指针。但是因为EDSIM51软件的原因,标签要加上#才能赋值给数据指针
MOV A ,#8;将8的值存入累加器A中
DEC A;因为从1开始,而不是从0,所以将累加器A的值减1
MOVC A,@A+DPTR;使用变址寻址的方式将寄存器A中的值和DPTR的值求和,再赋给寄存器A
MOV R0,A;将累加器A中的值赋给R0
END ; 结束程序

通俗解释

说人话:就是先把0到9的平方数存到自定义的平方数表Square_Table中,并将其首地址赋给数据指针DPTR。然后通过MOV A,# x (x为0到9的一个数),将输入x的值作为立即数存到累加器A中,然后通过变址寻址的方式,从Square_Table表中取出对应x的平方数即可。再简单一点说,就是先设计了一张0到9平方数的表并依次标注好位置,当你要查某个数的平方数时,你只需根据它对应在表上的位置去查即可,而不是通过计算所得。

结果验证
在这里插入图片描述
在这里插入图片描述

经过验证求8的平方,在R0ACC中的值为16进制的40,换算成10进制刚好为64,结果正确。

存在的问题:只要存入0到9的平方数表,那么你无论输入多少,R0的值仍保持上次的值不变。我试了很多方法,也没搞明白为什么。还请浏览鄙人博客的大佬指点。

三、在普中单片机开发板上重新完成点亮流水灯和LED灯周期性闪烁

1.点亮流水灯

(1)代码部分

//51单片机编程常用的头文件
#include <reg51.h>
#include <intrins.h>
//延迟函数
void delay_ms(int a)
{
int i,j;
for(i=0;i<a;i++)
{
for(j=0;j<1000;j++) nop();

  }
}

void main(void)
{
	while(1)
	{
		P0=0xfe;
		delay_ms(50);
		P0=0xfd;
		delay_ms(50);
		P0=0xfb;
		delay_ms(50);
		P0=0xf7;
		delay_ms(50);
		P0=0xef;
		delay_ms(50);
		P0=0xdf;
		delay_ms(50);
		P0=0xbf;
		delay_ms(50);
		P0=0x7f;
		delay_ms(50);
	}
}



(2)操作

①将普中单片机连接到电脑上。

②打开PZ-ISP软件。

在这里插入图片描述

④下载完成提示
在这里插入图片描述

(3)现象

在这里插入图片描述

在这里插入图片描述

流水灯成功运行!

2.LED灯周期性闪烁

(1)说明

有点坑,LED灯在STC-89C52RC板子上连接的接口是P2.0口。所以,得改变P2口的状态。

(2)代码部分
LOOP: SETB P2.0
LCALL DELAY
CLR P2.0
LCALL DELAY
AJMP LOOP


DELAY: MOV R0,#8
D0:MOV R7,#250
D1:MOV R6,#250
D2:DJNZ R6,D2
   DJNZ R7,D1
   DJNZ R0,D0
   RET
END
(3)操作

烧录过程和之前一样不再赘述。

(4)现象

在这里插入图片描述

观察到P2.0口的LED灯每隔1s闪烁一次。

三、总结

通过这次实验,我感触很多。首先,弥补了之前对汇编语言的认识不充分、不深刻,现在能理解到汇编语言与C语言其实本质上都差不多,二者能相互转化。Keil是一个很好的软件,它能用生成HEX文件,然后烧录进芯片中。其次,我自己学习领会了平方数表的本质是“查找”,而不是“计算”,然后自己写了一段更为简练的代码进行验证。另外,纸上得来终觉浅,绝知此事要躬行。我在用STC-89C52RC开发板点亮8个独立的LED灯之一时,发现始终点不亮,又检查了好几遍代码。最后通过询问同学才得知,此LED灯乃是连接在P2.0口上的。这使我认识到仿真与实践亦有差别。

做实验的过程的确很费时间,但是我相信学习没有“快车道”,只有踏踏实实、认认真真自己动手去做了,去和老师同学交流讨论了,得出来的经验结论才会更加深刻。

由于作者才疏学浅,文中不足之处还请各位大佬包涵、指出。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值