第4章程序设计基础•4.1MCS-51单片机汇编语言的伪指令MCS-51单片机中除了前面讲述的指令系统中111条指令外,还有另一类指令,这类指令称为伪指令。例如在程序的开头处有ORG,程序结束处END,等等。这些指令在汇编后不产生机器码,只是在程序进行汇编时,向汇编软件提供程序中的一些特殊信息。比如,程序起止,定义的数据,表格存放位置等。下面介绍单片机汇编语言程序设计中,常用的伪指令有以下几种:•1.起始伪指令ORGORG伪指令的功能是规定这条下面源程序和数据的起始地址格式为:ORGAddrl6.例如:ORG2000HSTART:MOVA即规定标号START所在地址为2000H,也就是第一条指令从2000H开始存放。一般说来,ORGAddrl6,表示了一个源程序或数据块的起始地址。在一段程序中允许多次使用ORG指令,以规定不同的程序段或数据块的起始位置,并规定地址从小到大,不允许重叠。2.结束伪指令END•END伪指令用来指示源程序到此全部结束,在汇编时,当汇编程序检测到源程序中的该指令时,它就认为源程序已经到此为止。对END后面的指令都不予汇编。因此一个源程序只能有一个END语句,且放在整个程序的末尾。3.赋值伪指令EQU•格式:字符名称EQU常数或汇编符号EQU伪指令的功能是将一个常数和汇编符号赋给字符名称在使用中要注意:(1)字符名称不等于标号,不用“:”来作分隔符:(2)和MOV传送指令不一样,用EQU赋值的常数和汇编符号,其值在整个程序中有效;(3)使用EQU伪指令时必须先赋值,后使用:(4)用EQU赋过值的符号名称可以用作数据地址,代码地址,位地址或是一个立即数。可以是8位或16位的。例如:ABCEQU30HDLYEQU8034HMOVA,ABCLCALLDLY这里ABC代表了30H的内存单元。DLY定义16位地址,实际是一个子程序的入口地址。4.定义字符指令DB•格式:DB8位二进制常数表DB伪指令的功能是从ROM的指定的地址单元开始存入DB后面的数据,这些数据可以是用逗号隔开的字节串或括在单引号中的ASCII字符串。例如:ORG2000HDB54HTAB:DB0B7H34H96HSTR:DB‘6AB’4.定义字符指令DB经汇编后:(2000H)=54H(2001H)=B7H(2002H)=34H(2003H)=96H(2004H)=36H(2005H)=41H(2006H)=42H其中36H,41H,42H分别是6、A、B的ASCII编码值。5.定义字伪指令DW•格式:DW16位数据表该指令用于从指定地址开始,在程序储存器的连续单元中定义双字节的数据。例如:ORG1000HTAB:DW3456H,7BH,10H汇编后(1000H)=34H(1001H)=56H(1002H)=00H(1003H)=7BH(1004H)=00H(1005H)=10H6.定义储存空间伪指令DS•格式:DS表达式•在汇编时,从指令地址开始保留DS之后表达式的值所规定的储存单元以备后用。例如:•ORG1000H•DS08H•DB30H汇编后,从1000H保留8个单元,然后(1008H)=30H。•注意:以上DB,DW,DS只对程序储存器起作用,它们不能对数据储存器进行初始化。7.位地址符号命令BIT•格式:字符名BIT位地址•其功能是将BIT之后的位地址值赋给字符名。例如:•A1BITP1.0•A2BITP1.1•SETBA1;(P1.0)←1•CLRA2;(P1.1)←04.2汇编语言程序的基本结构•汇编语言程序一般有4种结构形式:顺序结构,分支结构,循环结构利子程序结构。1.顺序结构•顺序结构是最简单的程序结构,其特点:程序中的语句由前向后顺序执行,直到最后,这种程序中的无分支、循环和子程序调用。2.分支程序•分支程序是通过条件转移指令实现的,根据程序执行中的条件对程序进行判断,满足条件则进行程序转移,不满足条件就顺序执行。对于MCS-51单片机,能实现分支转移的指令有JZ,JNZ,CJNE,DJNZ和位状态条件判断指令JC,JNC,JB,JNB等。这些指令条件判断在指令系统中已经作了说明。分支程序又分为单分支和多分支结构:2.分支程序(1)单分支程序。单分支程序都是使用前述的条件转移指令实现的。例4.1:假设内部RAM40H与41H单元中有两个无符号数,现要求将其大者存入40H中,小者存入41H中。2.分支程序•源程序如下:•MOVA,40H•CLRC•SUBBA41H•JNCWAIT•MOVA,40H•XCHA,41H•MOV40H,A•WAIT:SJMPWAIT2.分支程序•例4.2:设变量X存于内部RAM20H单元,函数值Y存于21H单元,试按照下式要求对Y赋值。•X+3X0•Y=20X=0•XX0•流程图如图4.2所示:2.分支程序源程序如下:MOVA,20HJZZERO;(A)=0JBACC.7,STOREADDA,#03HSJMPSTOREZERO:MOVASTORE:MOV21H(2)多分支程序•例4.3在某单片机应用系统中,接有一键盘,键值(代表哪个键被按下)存放在内部RAM的40H单元内。设计一段程序实现如下功能:如果(40H)=00H,调用子程序SUBl;如果(40H)=01H,调用子程序SUB2:如果(40H)=02H,调用子程序SUB3;如果(40H)=03H,调用子程序SUB4;如果(40H)=04H,调用子程序SUB5。•解:先画出本例的示意流程图如图4.3:(2)多分支程序•程序如下:•MOVA,40H•MOVDPTR,#TABLE•RLA;(A)←(A)×2•ADDA,40H•JMP@A+DPTR•………•TABLE:LCALLSUBl•LCALLSUB2•LCALLSUB3•LCALLSUB4•LCALLSUB5•上例的这种结构通常又称为散转结构。在本例中,由于LCALL指令是3字节指令,为保证JMP指令执行后能正确调用相应子程序,所以JMP指令前要将(40H)乘3并存入A中。3.循环结构•程序设计中,经常需要连续重复执行某段程序,解决这种问题最好采用循环结构的程序来完成,这种设计方法可大大地简化程序。循环程序一般如下四部分组成:•①.置循环初值•用来设置循环初值,如:预置变量,计数器数据指针初值等,为循环作准备。•②.循环体•循环体指要求重复执行的程序段,通过它完成对数据进行实际处理的任务。3.循环结构•③.修改控制变量一般用一个工作寄存器Rn作为对循环次数的计数,每循环一次计数器减一,即修改循环控制变量。•④.循环控制部分控制循环次数,当循环一定的次数后当满足循环结束条件时,停止循环。(1).循环次数已知的单循环•例4.4:设计一段程序,统计累加器A的8位数中1的个数,把结果存入30H单元中。•解题思路:用RLC指令把A带上Cy循环左移8次,每移一次,判移入Cy的是否为1,若为1,(30H)+1。流程图如图4.5。•MOV30H,#00H;循环初值•MOVR2,#08H•LOOP:RLCA•JNCNEXT•INC30H•NEXT:DJNZR2(2).条件控制的单重循环•例4.5•设字符串存放在RAM31H开始的单元中,以“$”作为结束标志,现要求计算该字符串的长度,并将其存放在20H中。•程序如下:•CRLA•MOV@R0,#31H•LOOP:CJNER0,#24H,NEXT;与“$”(ASCII值为十六进制24)比较•SJMPCOMP;找到“$”结束•NEXT:INCA;不为“$”则计数器加1•INCR0;修改地址指针•SJMPLOOP•COMP:MOV20H,A;存结果4.子程序•(1)子程序的概念•在程序设计中,经常发生一些程序段被频繁使用,如一些数学函数的计算,二十进制转换,显示程序,延时程序等,为了避免重复节省内存,常把这些程序作为一种独立的,标准化的通用程序段,供需要时调用,这些独立程序段称为子程序。•主程序可以通过专门的指令来调用子程序,称子程序调用。当子程序执行完毕后,再由返主指令,返回到原程序,并带回结果。(2)子程序设计几点注意l子程序的第一条指令地址称为子程序的入口地址。该指令前必须要有标志,标号最好以子程序功能命名,以便主程序调用,这样会一目了然,例如延时程序常以DELAY作为标号。l主程序调用子程序是通过LCALL、ACALL指令完成的。例如LCALLDELAY执行调用延时子程序,子程序返回主程序是通过执行RET指令来完成的。l子程序内部如果有控制转移指令,最好使用相对转移指令,以便汇编时生成不随子程序的存放地址改变而改变的代码,子程序可以存放ROM空间的任意位置。l在子程序开始部分要注意现场保护,在退出子程序之前要恢复现场。保护现场和恢复现场操作一般用PUSH和POP指令完成,要遵守“先进后出、后入先出”的原则。(2)子程序设计几点注意•在子程序运行时,不可避免地要改变一些寄存器或数据存储器的内容,有时这些内容是主程序不可缺少的,因此,在子程序调用时,应该先将有关寄存器或存储器的内容保护起来,子程序返回前再恢复原来的内容,这一过程称为现场的保护与恢复。现场保护通常由堆栈来完成,在子程序的开始部分使用压栈指令,将需要保护的内容压入堆栈;在子程序返回前通过出栈指令,将原有的内容弹出堆栈,送到原来的寄存器或存储器单元中,从而实现了现场保护与恢复。(3)子程序参数传递•调用子程序时,主程序应把子程序使用的有关参数送入约定的位置,子程序运行时,可以从约定的位置得到有关的参数,这类由主程序提供给予子程序的参数叫做入口参数。同样,在子程序结束前,也应把运算结果送到指定的位置,返回主程序后,主程序可以从指定的位置得到需要的结果,这类由子程序返回主程序的参数叫做入口参数。实现参数传递有多种方法,常用的方法有以下三种:(3)子程序参数传递•1)用工作寄存器或累加器传递参数。数据通过R0-R7或累加器A来传递。在调用前,先将数据送入寄存器或者累加器,供子程序使用。子程序执行后结果参数仍由寄存器和累加器送回。其优点是程序简单,速度快。缺点是传递的参数不能太多。2)用指针寄存器传递参数。为了能传递较多的数据,可以使用指针寄存器传递参数。由于数据通常是存放在存储器中,可用指针来指示数据的位置,并可实现变长度运算。若数据在内部RAM中,可以用R0或R1作为数据指针,若参数在外部RAM中,可以使用DPTR作指针。3)用堆栈传递参数。使用堆栈进行参数传递时,主程序使用PUSH指令把参数压入堆栈,进入子程序后可以通过堆栈指针POP指令来间接访问堆栈中的参数。同样,子程序的出口参数也可用堆栈传递给主程序。请注意,在调用子程序时,断点地址自动进栈,占用两个单元,在于程序中弹出参数时,不要把断点地址也弹出。另外,在执行RET指令时,要自动弹出断点地址,以便正确返回。(3)子程序参数传递•例4.6•利用累加器传递参数•设有一个从21H开始存放的数据块,每个单元均有一个十六进制数(0-F),数据块长度存放在20H中,编程将他们转化为相应ASCII码,并存放在41H开始的单元中。根据ASCII码表,0-9的ASCII码为30H-39H,即只要加上30H就可得到相应ASCII码,而A-F的ASCII码为41H-46H,即只要加上37H也可得到相应的ASCII码。4.3实用程序设计举例•4.3.1数制转换程序•1.二进制转换成BCD码十进制•例4.7:将RAM中30H单元中二进制数转换为3位BCD码格式的十进制数,2位BCD码占用2个字节单元,即百位数BCD码放在32H单元中,十位和个位BCD码放在31H单元中。2.将二进制数转成ASCII码•例4.8:将A中的二进制数(0-F)转化为相应的ASCII码•CNV:ADDA,#90H•DAA•ADCA,#40H•DAA•RET•如果执行ADDA,#90H和DAA两条指令后产生进位,则说明被转换二进制数不大于09H,否则小于0AH,后两条指令在于产生正确的高位ASCII代码;二进制数大于09H时为4H;二进制数大于0AH时为3H。3.BCD码