第5章程序设计.docx
- 文档编号:2525360
- 上传时间:2023-05-03
- 格式:DOCX
- 页数:45
- 大小:88.75KB
第5章程序设计.docx
《第5章程序设计.docx》由会员分享,可在线阅读,更多相关《第5章程序设计.docx(45页珍藏版)》请在冰点文库上搜索。
第5章程序设计
第5章MCS—51程序设计
程序设计是为了解决某一个问题,将指令有序地组合在一起。
程序有简有繁,有些复杂程序往往是由简单的基本程序所构成。
本章将通过一些基本程序,介绍部分常用程序设计方法。
程序设计的过程大致可以分为以下几个步骤:
1、编制说明要解决问题的程序框图。
2、确定数据结构、算法、工作单元、变量设定。
3、根据所用计算机的指令系统,按照已编制的程序框图用汇编语言编制出源程序。
4、将编制出的程序在计算机上调试,直至实现预定的功能。
程序编写是一个较复杂艰难的过程,要有较强的抽象思维和逻辑思维能力,学习编程
一般先看程序,分析程序。
程序看懂了,再编一些短的,容易的程序,特别是一些专用语句的编程方法要记下,慢慢逐步编长程序,编多了,熟能生巧。
编好的程序要用软件仿真或硬件仿真检验其正确性。
以下程序为了学习的方便都可全软件仿真,每一个程序可在仿真软件中检验它的正确性。
5.1简单程序设计
简单程序又称顺序程序。
计算机是按指令在存储器中存放的先后次序来顺序执行程序的。
除非用特殊指令让它跳转,不然它会在PC控制下执行。
例1:
编写1+2的程序
首先用ADDA,Rn指令,该指令是将寄存器Rn中的数与累加器A中的数相加,结果存于A中,这就要求先将1和2分别送到A中和寄存器Rn中,而Rn有四组,每组有八个单元R0~R7,首先要知道Rn在哪组,默认值(不设定值)是第0组,在同一个程序中,同组中的Rn不能重复使用,不然会数据出错,唯独A可反复使用,不出问题。
明确了这些后,可写出程序如下:
ORG0000H;定下面这段程序在存储器中的首地址,必不可少的。
MOVR2,#02;2送R2
MOVA,#01;1送A
ADDA,R2;相加,结果3存A中
END;程序结束标志,必不可少的。
程序到此编写完成,然后在仿真软件中调试、验证,若不对,反复修改程序,直到完全正确为止。
该程序若用ADDA,direct指令编程时,可写出如下程序:
ORG0000H
MOV30H,#02
MOVA,#01
ADDA,30H
END
该程序若用ADDA,@Ri指令编程时,可写出如下程序:
ORG0000H
MOVR0,#02
MOVA,#01
ADDA,@R0
END
注意间接寻址方式的用法,Ri(i=0,1)即Ri只有R0和R1
该程序若用ADDA,#data指令编程时,可写出如下程序:
ORG0000H
MOVA,#01
ADDA,#02
END
从以上例子可见,同一个程序有多种编写方法,思路不同编出来的程序不同,但结果都一样,但我们认为最后一个程序较好。
以上加法程序是最简单的形式,加法有多种:
有无进位加法、有进位加法、有有符号加法、有无符号加法,还有浮点数的加法、单字节加法、双字节加法、多字节加法等等。
一般编写程序时,编成通用的程序。
在调用通用程序之前,先判断是哪一种类型,再调相应的子程序。
如以上1+2的程序,也可以这样写,先将加数和被加数分别送入40H、41H单元,加完后和送入42H单元。
它的完整程序是:
ORG0000H
MOV40H,#01H
MOV41H,#02H
AD1:
MOVR0,#40H;设R0为数据指针
MOVA,@R0;取N1
INCR0;修改指针
ADDA,@R0;N1+N2
INCR0
MOV@R0,A;存结果
END
流程图如图5-1所示。
此程序也可这样写,用子程序调用的方法写。
将加的这一部分写成通用程序:
AD1:
MOVR0,#40H;设R0为数据指针
MOVA,@R0;取N1
INCR0;修改指针
ADDA,@R0;N1+N2
INCR0
MOV@R0,A;存结果
RET
使用这个程序之前,先将加数、被加数送入40H、41H单元,完整的程序如下:
ORG0000H
MOV40H,#01H
MOV41H,#02H
ACALLAD1
AD1:
MOVR0,#40H;设R0为数据指针
MOVA,@R0;取N1
INCR0;修改指针
ADDA,@R0;N1+N2
INCR0
MOV@R0,A;存结果
RET
END
标号AD1到RET的这段程序就为子程序,图5-1例1流程图
有时将这些专用的子程序存入ROM中,不可改写。
我们使用时用下面比较简单的程序,
ORG0000H
MOV40H,#01H
MOV41H,#02H
ACALLAD1
END
这样使用起来很方便,向40H、41H单元送数,叫入口参数。
将和送入42H单元,称为出口参数。
一般的教科书讲通用子程序,理解起来,有些困难。
我们在看书的时候要适应这种思路。
下面的程序都是讲的通用子程序。
例2:
将两个半字节数合并成一个一字节数
设:
内部RAM40H,41H单元中分别存放着8位二进制数。
要求取出两个单元中的低半字节、合并成一个字节后,存42H单元。
流程图如图5-2所示
程序如下:
ORG0000H
START:
MOVR1,#40H
MOVA,@R1
ANLA,#0FH;取第一个半字节
SWAPA
INCR1
XCHA,@R1;取第二字节
ANLA,#0FH;取第二个半字节
ORLA,@R1;拼字
INCR1
MOV@R1,A;存放结果图5-2例2流程图
RET
END
上面程序先要在RAM的40H、41H单元中输入两个数,(输入法见第二章软件仿真部分)例如输入08和06,再看86是否送入42H单元。
此例相反的过程是将字节拆开分成两个半字节:
例如将40H单元中的内容拆开后分别送41H、42H单元中。
例3拆字程序
ORG0000H
START:
MOVR1,#40H
MOVA,@R1
MOVB,A;暂存B中
ANLA,#0FH;取第一个半字节
INCR1
MOV@R1,A;存放第一个半字节
MOVB,A
SWAPA
ANLA,#0FH;取第二个半字节
INCR1
MOV@R1,A;存放第二个半字节
RET
END
上面程序要在40H中输入两个数,例如86再看6是否送入41H单元,8是否送入42H单元。
5.2分支程序设计
在处理实际事务中,只用简单程序设计的方法是不够的。
因为大部分程序总包含有判断、比较等情况。
根据判断、比较的结果转向不同的分支。
下面举两个分支程序的例子。
例4:
两个无符号数比较大小
设两个连续外部RAM单元ST1和ST2中存放不带符号的二进制数,找出其中的大数存入ST3单元中。
流程图见图5-3
程序如下:
ORG8000H
ST1EQU8040H
START1:
CLRC;进位位清零
MOVDPTR,#ST1;设数据指针
MOVXA,@DPTR;取第一数
MOVR2,A;暂存R2
INCDPTR
MOVXA,@DTPR;取第二个数
SUBBA,R2;两数比较
JNCBIG1
XCHA,R2;第一数大
BIG0:
INCDPTR
MOVX@DPTR,A;存大数
SJMP$
BIG1:
MOVXA,@DPTR;第二数大
SJMPBIG0
END
图5-3例4流程
上面程序中,用减法指令SUBB来比较两数的大小。
由于这是一条带借位的减法指令,在执行该指令前,先把进位位清零。
用减法指令通过借位(CY)的状态判两数的大小,是两个无符号数比较大小时常用的方法。
设两数X,Y,当X≥Y时,用X-Y结果无借位(CY)产生,反之借位为1,表示X<Y。
用减法指令比较大小,会破坏累加器中的内容,故作减法前先保存累加器中的内容。
执行JNC指令后,形成了分支。
执行SJMP指令后,实现程序的转移。
例5:
将ASCII码表的ASCII码转换为十六进制数,如果ASCII码不能转换成十六进制数,用户标志位置1。
由ASCII码表可知,30H~39H为0~9的ASCII码,41H~46H为A~F的ASCII码。
在这一范围内的ASCII码减30H或37H就可以获得对应的十六进制数。
设ASCII码放在累加器A中,转换结果放回A中。
流程图见图5-4
程序为:
ORG0000H
START:
CLRC
SUBBA,#30H
JCNASC;(A)<0,不是十六进制数
CJNEA,#0AH,MM
MM:
JCASC;0≤(A)<0AH,是十六进制数
SUBBA,#07H
CJNEA,#0AH,NN
NN:
JCNASC
CJNA,#10H,LL
LL:
JCASC
NASC:
SETBF0
ASC:
RET
END
例6:
求单字节有符号二进制数的补码。
解:
正数补码是其本身,负数的补码是其反码加1。
因此,程序首先判断被转换数的符号,负数进行转换,正数即为补码。
设二进制数放在累加器A中,其补码放回到A中。
程序为:
图5-4:
例5流程图
ORG0000H
CMPT:
JNBACC.7,NCH;(A)>0,不需转换
CPLA
ADDA,#1
SETBACC.7;保存符号
NCH:
RET
END
分支程序在实际使用中用处很大,除了用于比较数的大小之外,常用于控制子程序的转移。
5.3循环程序设计
在程序设计中,只有简单程序和分支程序是不够的。
因为简单程序,每条指令只执行一次,而分支程序则根据条件的不同,会跳过一些指令,执行另一些指令。
它们的特点是,每一条指令至多执行一次。
在处理实际事务时,有时会遇到多次重复处理的问题,用循环程序的方法来解决就比较合适。
循环程序中的某些指令可以反复执行多次。
采用循环程序,使程序缩短,节省存储单元。
重复次数越多,循环程序的优越性就越明显。
但是程序的执行时间并不节省。
由于要有循环准备、结束判断等指令,速度要比简单程序稍慢些。
循环程序一般由五部分组成:
1、初始化部分:
为循环程序做准备。
如:
设置循环次数计数器的初值,地址指针置初值,为循环变量赋初值等。
2、处理部分:
为反复执行的程序段,是循环程序的实体。
3、修改部分:
每执行一次循环体后,对指针作一次修改,使指针指向下一数据所在位置,为进入下一轮处理作准备。
4、控制部分:
根据循环次数计数器的状态或循环条件,检查循环是否能继续进行,若循环次数到或循环条件不满足,应控制退出循环,否则继续循环。
通常2、3、4部分又称为循环体。
5、结束部分:
分析及存放执行结果。
循环程序的结构一般有两种形式:
(1)先进入处理部分,再控制循环。
即至少执行一次循环体。
如图5-5(a)所示。
(2)先控制循环,后进入处理部分。
即先根据判断结果,控制循环的执行与否,有时可以不进入循环体就退出循环程序。
如图5-5(b)所示。
循环结构的程序,不论是先处理后判断,还是先判断后处理,其关键是控制循环的次数。
根据需要解决问题的实际情况,对循环次数的控制有多种,循环次数已知的,用计数器来控制循环,循环次数末知的,可以按条件控制循环,也可以用逻辑尺控制循环。
循环程序又分单循环和多重循环。
下面举例说明循环程序的使用。
1、单循环程序
(1)循环次数已知的循环程序。
(a)(b)
图5-5循环流程图
例7:
工作单元清零
在程序设计时,有时需要将存储器中的部分地址作为工作单元,存放程序执行的中间值和结果,此时常需要对这些工作单元清零。
如:
将40H为起点的8个单元清“0”
ORG0000H
CLEAR:
CLRA;A清0
MOVR0,#40H;确定清0单元起始地址
MOVR7,#08;确定要清除的单元个数
LOOP:
MOV@R0,A;清单元
INCR0;指向下一个单元
DJNZR7,LOOP;控制循环
END
此程序的前2-4句为设定循环初值,5-7句为循环体。
以上是内部RAM单元清零,也可清外部RAM单元。
例如:
设有50个外部RAM单元要清“0”,即为循环次数存放在R2寄存器中,其首址存放在DPTR中,设为2000H。
程序如下:
ORG0000H
MOVDPTR,#2000H
CLEAR:
CLRA
MOVR2,#32H;置计数值
LOOP:
MOVX@DPTR,A
INCDPTR;修改地址指针
DJNZR2,LOOP;控制循环
END
本例中循环次数是已知,用R2作循环次数计数器。
用DJNZ指令修改计数器值,并控制循环的结束与否。
此程序也可写成通用子程序形式:
CLEAR:
CLRA
LOOP:
MOVX@DPTR,A
INCDPTR;修改地址指针
DJNZR2,LOOP;控制循环
RET
使用时只要给定入口参数及被清零单元个数,调用此子程序就行:
ORG0000H
MOVDPTR,#2000H
MOVR2,#50
ACALLCLEAR
SJMP$
CLEAR:
CLRA
LOOP:
MOVX@DPTR,A
INCDPTR;修改地址指针
DJNZR2,LOOP;控制循环
RET
END
入口参数是由实际需要而定,若要清4000H为起点的100个单元,只要改动前面两句就行。
例8:
多个单字节数据求和
已知有n个单字节数据,依次存放在内部RAM40H单元开始的连续单元中。
要求把计算结果存入R2,R3中(高位存R2,低位存R3)。
程序如下:
ORG8000H
SAD:
MOVR0,#40H;设数据指针
MOVR5,#NUN;计数值0AH→R5
SAD1:
MOVR2,#0;和的高8位清零
MOVR3,#0;和的低8位清零
LOOP:
MOVA,R3;取加数
ADDA,@R0
MOVR3,A;存和的低8位
JNCLOP1
INCR2;有进位,和的高8位+1
LOP1:
INCR0;指向下一数据地址
DJNZR5,LOOP
RET
NUNEQU0AH
END
上述程序中,用R0作间址寄存器,每作一次加法,R0加1,数据指针指向下一数据地址,R5为循环次数计数器,控制循环的次数。
(2)循环次数末知的循环程序。
以上介绍的几个循环程序例子,它们的循环次数都是已知的,适合用计数器置初值的方法。
而有些循环程序事先不知道循环次数。
不能用以上方法。
这时需要根据判断循环条件的成立与否,或用建立标志的方法,控制循环程序的结果。
例9:
测试字符串长度
设有一串字符依次存放在从50H单元开始的连续单元中,该字符串以回车符为结束标志,测得的字符串长度存入R2中。
测字符串长度程序是将该字符串中的每一个字符依次与回车符相比,若比较不相等,则统计字符串长度的计数器加1。
继续比较,若比较相等,则表示该字符串结束,计数器中的值就是字符串的长度。
程序如下:
ORG0000H
CONT:
MOVR2,#00H;初始长度设置
MOVR0,#50H;数据指针R0置初值
NEXT:
CJNE@R0,#0DH,LOOP
RET
LOOP1INCR0
INCR2
SJMPNEXT
END
待测字符以ASCII码形式存放在RAM中,回车符的ASCII码为0DH,程序中用一条CJNE@R0,#0DH,LOOP指令实现字符比较及控制循环的任务,当循环结束时,R2的内容为字符串长度。
2、循环程序在数据传送方面的应用
数据传送在程序编写中占有很重要的位置,编程离不开数据的处理和传送,初编程时往往是不知道数据怎么处理。
例如:
一个简单的1+2程序,首先应将1与2送到加法指令所规定的单元中,再用一个加法指令将两数相加,加后的结果存放在哪里,全由编写程序时安排好存储单元,若程序运行成功,结果一定在你编程时规定的那个单元,若那个单元被用或被其它的程序占了,数就存不进,读不出。
传数的种类很多,数据传送指令也较多,除了用不同的寻址方式,采用不同的传送方法外,就传送的最终目的而言可分为:
内部RAM传到内部RAM(简称内传内)、内部RAM传送到外部存储器(简称内传外)、外部存储器传到内部RAM(简称外传内)、外部存储器传到外部存储器(简称外传外)。
数据传送程序的编写都离不开循环程序,下面讲解数据传送程序的编写方法。
例10:
将内部RAM以40H为起始地址的8个单元中的内容传到以60H为起始地址的8个单元中。
此程序的编写要用到间接寻址方法,它的基本编程思路是先读取一个单元的内容,将读取的内容送到指定单元,再循环送第二个,反复送,直到送完为止,程序如下:
ORG0000H
MOVR0,#40H;定内部RAM取数单元的起始地址
MOVA,@R0;读出数送A暂存
MOVR1,#60H;定内部RAM存数单元的起始地址
MOV@R1,A;送数到60H单元
MOVR7,#08;定送数的个数
LOOP:
INCR0;取数单元加1,指向下一个单元
INCR1;存数单元加1,指向下一个单元
MOVA,@R0;读出数送A暂存
MOV@R1,A;送数到新单元
DJNZR7,LOOP;8个送完了吗?
未完转到LOOP继续送
END送完了顺序执行,结束。
根据此程序,自已编出程序流程图,编程与程序流程图有一定距离,编程开始前弄清程序要完成的任务是什么?
然后安排好数据存储单元,采用什么指令,理顺思路。
开始编程序要全力以赴,不让任何人打扰,不中断思路,一气呵成。
编程有很大的灵活性,很高的技巧,首先应该多看实例,有些指令的用法比较特殊专一,要记下来,如以上程序,还有查表程序,散转程序等等。
常用的就是那么几条,编程方法也就那么几种,掌握了编程基本方法可脱离书本自己编,先编简单程序,再编复杂程序,先编短程序,后编长程序。
编好的程序又可作为以后程序中的子程序,持之以恒,你就会成为编程高手。
编完的程序,是否正确,要在仿真软件中调试,调试方法在第二章中已作全面介绍再不多述。
调试此程序时要打开Franklin仿真软件的数据窗口(DataView),在窗口中的40H为起点的8个单元中送数,首先全速运行程序,看所有数据是否传到60H为起点的8个单元中,若不正确,单步运行程序,看程序的每一步是否正确,若还不正确,再反复修改,直到正确为止。
例11:
将内部RAM以40H为起始地址的8个单元中的内容传到外部存储器以2000H为起始地址的8个单元中。
此程序与例10的区别就是传到外部存储器,注意外部存储器的地址是16位地址,传送16位地址的数有专门的指令,读(外传内)外部存储器单元的方法是:
MOVDPTR,#2000H
MOVXA,@DPTR
写(内传外)外部存储器单元的方法是:
MOVDPTR,#2000H
MOVX@DPTR,A
这是专用语句要记牢,不能错,有了这些知识后我们可编写程序如下:
ORG0000H
MOVR0,#40H;定内部RAM取数单元的起始地址
MOVA,@R0;读出数送A暂存
MOVDPTR,#2000H;定外部存储器存数单元的起始地址
MOVX@DPTR,A;送数到2000H单元
MOVR7,#08;定送数的个数
LOOP:
INCR0;取数单元加1,指向下一个单元
INCDPTR;存数单元加1,指向下一个单元
MOVA,@R0;读出数送A暂存
MOVX@DPTR,A;送数到新单元
DJNZR7,LOOP;8个送完了吗?
未完转到LOOP继续送
END送完了顺序执行,结束。
程序初步编好后,在仿真软件中调试,调试时先打开Franklin仿真软件的数据窗口(DataView),在窗口中的40H为起点的8个单元中任意送数,再打开Franklin仿真软件的外部数据窗口(XdataView),首先全速运行程序,看所有数据是否传到2000H为起点的8个单元中,若不正确,再单步运行程序,看程序的每一步是否正确,若不正确,再反复修改,直到正确为止。
例12:
将外部存储器以2000H为起始地址的8个单元中的内容传到内部RAM以40H为起始地址的8个单元中。
仿照例2我们可写出程序如下:
ORG0000H
MOVDPTR,#2000H;定外部存储器取数单元的起始地址
MOVXA,@DPTR;读出数送A暂存
MOVR0,#40H;定内部RAM存数单元的起始地址
MOV@R0,A;送数到40H单元
MOVR7,#08;定送数的个数
LOOP:
INCR0;取数单元加1,指向下一个单元
INCDPTR;存数单元加1,指向下一个单元
MOVA,@DPTR;读出数送A暂存
MOVX@R0,A;送数到新单元
DJNZR7,LOOP;8个送完了吗?
未完转到LOOP继续送
END送完了顺序执行,结束。
程序初步编好后,在仿真软件中调试,调试时先打开Franklin仿真软件的外部数据窗口(XdataView),在外部数据窗口(XdataView)中的2000H为起点的8个单元中任意送数,再打开Franklin仿真软件的内部数据窗口(DataView)。
首先全速运行程序,看所有数据是否传到40H为起点的8个单元中,若不正确,再单步运行程序,看程序的每一步是否正确,若不正确,再反复修改,直到正确为止,若正确说明此程序编好了。
例13:
将外部存储器以2000H为起始地址的8个单元中的内容传到外部存储器以4000H为起始地址的8个单元中。
编程时可以沿用以上编程思路,但在循环时要将DPTR分成两个字节,即DPH和DPL,只要这样改动就可编程如下:
ORG0000H
MOVR2,#00H;定外部存储器取数单元的起始地址低字节
MOVR3,#20H;定外部存储器取数单元的起始地址高字节
MOVR4,#00H;定外部存储器存数单元的起始地址低字节
MOVR5,#40H;定外部存储器存数单元的起始地址高字节
MOVR7,#08;定送数的个数
LOOP:
MOVDPL,R2
MOVDPH,R3
MOVA,@DPTR;读出2000单元的数送A暂存
MOVDPL,R4
MOVDPH,R5
MOVX@DPTR,A;送数到4000H单元
INCR2;取数单元加1,指向下一个单元
INCR4;存数单元加1,指向下一个单元
DJNZR7,LOOP;8个送完了吗?
未完转到LOOP继续送
END送完了顺序执行,结束。
程序初步编好后,在仿真软件中调试,调试时先打开Franklin仿真软件的外部数据窗口(XdataView),在外部数据窗口(XdataView)中的2000H为起点的8个单元中任意送数,再又打开一个Franklin仿真软件的外部数据窗口(XdataView)。
首先全速运行程序,看所有数据是否传到4000H为起点的8个单元中,若不正确,再单步运行程序,看程序的每一步是否正确,若不正确,再反复修改,直到正确为止。
此程序传数的最大个数
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第5章 程序设计