文章目录
文章目录
一、如图有一个LED周期性闪烁的汇编程序。
1.在12MHz晶振(时钟频率)的条件下,请查阅汇编指令 “MOV R6,#250”和“DJNZ R6,D2”的指令周期数,计算其对应的时钟周期 us值;然后计算 DELAY函数中总的循环次数和对应的时间us, 说明这个LED灯大约每隔多少毫秒(ms)或秒(s)才变化一次亮灭状态;
(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 us;DJNZ指令的时钟周期为: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的平方,在R0和ACC中的值为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口上的。这使我认识到仿真与实践亦有差别。
做实验的过程的确很费时间,但是我相信学习没有“快车道”,只有踏踏实实、认认真真自己动手去做了,去和老师同学交流讨论了,得出来的经验结论才会更加深刻。
由于作者才疏学浅,文中不足之处还请各位大佬包涵、指出。