|
3.5 单片机汇编语言的程序结构 |
程序结构就是程序指令的组织方式。单片机汇编语言程序大致可以分为5种程序结构,即顺序结构、分支结构、循环结构、子程序结构和查表结构。通过这些结构,读者可以设计更复杂的程序,完成更多的任务。
顺序结构程序是按照指令的书写顺序来执行的程序结构,相当于人们在做事情时,严格按照一个计划表来执行。顺序结构是一种无分支的直线型程序结构,是一种最简单、最基本的程序。
【范例3-2】 示例代码3-2是一个顺序结构的单片机汇编语言程序。
示例代码3-2
【运行结果】使用Keil μVision3编译程序可以完成该程序的编译和仿真操作。该程序运行时,将一个单字节十六进制数转换成BCD码的形式,并保存在通用寄存器中。
【代码解析】在程序中,单字节十六进制数据在0~255之间,存放在单片机RAM的32H中。首先将其除以100后,商为百位数,余数除以10,商为十位数,余数为个位数。程序转换后,百位数存放于R5中,十位和个位分别存放于R6的高位和低位字节中。
分支结构是一种判断形式的程序结构,相当于人们在做事情时,根据判断的结果来决定后面做哪件事。分支结构的程序判断条件的满足与否,产生一个或多个程序分支,以实现不同的程序走向。分支结构按照采用指令的不同而分为两类:
●双分支结构。这类分支结构主要采用有条件转移指令JC、JB等,比较条件转移指令CJNE等和累加器A判断指令JZ等。当给定的条件成立时,执行分支程序1,否则执行分支程序2。
●多分支结构。这类结构主要采用散转指令JMP,根据运算的结果值在多个分支中选择一个执行的程序结构。
【范例3-3】 示例代码3-3是一个双分支结构的单片机汇编语言程序。
示例代码3-3
【运行结果】使用Keil μVision3编译程序可以完成该程序的编译和仿真操作。该程序运行时,计算16位二进制数求补,并保存在RAM中。
【代码解析】在程序中,16位双字节数存放在通用寄存器R3和R4中。首先,对低字节数取补,然后判断其结果是否为0,如果为0,则对高字节数进行取补,即取反加1,否则直接取反就可以了。程序将求补以后的结果存放于地址21H、22H中。
多分支结构使用了散转指令JMP,散转指令的使用格式如下:
这里,数据指针DPTR为存放转移指令串(S0~Sn)的首地址,由累加器A的内容动态选择对应的转移指令。这样,便可以产生多达256个分支程序。多分支结构除了使用散转指令JMP外,还需要无条件转移指令AJMP和LJMP和完成程序的跳转。例如,采用AJMP的多分支程序结构如下:
分支结构是一种重复执行某段代码的程序结构,相当于人们在做事情时,在一段时间内进行重复性的工作。一个典型的循环程序由4部分组成,即循环初始化部分、循环处理部分、循环次数控制部分和循环结束部分。下面分别进行介绍。
●循环初始化部分:主要用于设置循环的次数、有关的工作单元清零、变量设置和地址指针设置等循环初始参数。
●循环处理部分:也称为循环体,这是循环结构的主要代码段,在程序运行时将重复执行。
●循环次数控制部分:主要用于控制循环的次数,防止出现死循环。循环次数控制部分一般由两个单元组成,修改控制变量和判断循环结束。循环控制变量可以采用循环递减计数法,即每循环一次,控制变量减1,并判断是否为0,若不为0,则继续执行循环体程序,否则结束循环体的执行;也可以采用条件控制,即判断结束条件是否成立,如果不成立,则继续执行循环体,否则,结束循环。
●循环结束部分:当循环处理部分执行完毕后,需要对计算结果进行处理和保存,以供后面的程序使用。
在51系列单片机的指令系统中,提供了如下两条循环转移指令。
●DJNZ Rn,LOOP:这里采用工作寄存器Rn为控制寄存器。控制寄存器的计数方式一般都是减1计数,即每循环一次,Rn自动减1计数,同时判断寄存器Rn是否为0,若不为0,继续执行循环;若为0,则结束循环程序的执行。
●DJNZ Direct,LOOP:这里采用直接寻址单元Direct作为控制寄存器。控制寄存器的计数方式一般都是减1计数,即每循环一次,Direct单元自动减1计数,同时判断Direct单元是否为0,若不为0,继续执行循环;若为0,则结束循环程序的执行。
【范例3-4】 示例代码3-4是一个循环结构的单片机汇编语言程序。
示例代码3-4
【运行结果】使用Keil μVision3编译程序可以完成该程序的编译和仿真操作。该程序运行时,计算10个符号整数的最大值,并保存在通用寄存器R2中。
【代码解析】在程序中,采用了比较和交换的方法来依次对比各个数据。数据块的首地址为10H,先读取第一个数据与第二个数据,把第一个数据作为基准数送入累加器A,进行比较。如果基准数大,则不做交换,再取下一个数进行比较;如果基准数小,则将数值大的取代原来的基准数,即相当于做一次数据交换。然后,再以新的基准数与下一个数进行比较,直至全部比较完毕。这里的基准数始终保持为最大的数值,因此,全部比较完毕后,累加器A中的基准数即是数据块中的最大值。最后将最大值保存在通用寄存器R2中。
注意: 在循环结构中,循环次数的范围为1~255。如果实际问题中需要超过255个循环,则可以采用多重循环来实现。在多重循环中,对循环嵌套的次数没有限制,但是用户应该尽量保持循环结构的清晰性。
子程序结构是一种模块化的程序设计思想,将某些运算和操作设计成可被其他程序调用的子程序段,需要的时候直接调用这些程序段即可。一般来说,调用子程序的程序称为主程序,调用子程序的过程称为子程序调用。子程序执行完后返回主程序的过程称为子程序返回。使用子程序可以使代码的结构清晰,也便于程序的移植和重复使用。
51系列单片机指令集中提供了两个指令可以用来调用子程序,其使用格式如下。
●LCALL ADDR16:这条指令称为长调用指令,指令的操作数ADDR16给出了子程序的16位入口地址。
●ACALL ADDR11:这条指令称为绝对调用指令,其中的操作数ADDR11提供了子程序的低11位入口地址,这个地址和程序计数器PC的高5位并在一起构成16位的子程序调用地址,即子程序的入口地址。
子程序在执行时,需要经过如下几步:
首先将程序计数器PC中的内容压入堆栈,即断点保护。程序计数器PC中保存子程序的地址,称为断点地址。
然后,将调用地址送入程序计数器PC中,使程序跳转到子程序的入口地址处开始执行。
子程序执行完毕后,通过返回指令RET返回。指令RET将堆栈中存放的返回地址(即断点地址)弹出堆栈,送回到程序计数器PC中,使程序返回到主程序的断点处继续向下执行。
【范例3-5】 示例代码3-5是一个子程序结构的单片机汇编语言程序。
示例代码3-5
【运行结果】这是一个子程序,需要在主程序中调用才可以使用。该子程序运行时,将计算两个多字节BCD码的加法。
【代码解析】在子程序中,两个BCD码数据均按照高位字节数存放于低地址单元。运算时,相加运算则从低位字节数开始,R0、R1指针在运算前均指向最高字节数的地址。因此,需要转换成指向最低字节数的地址,然后进行相加运算。相加的结果BCD码和数存入R0指针的内部RAM中,即原被加数单元。
查表程序结构是在一个已知的数据表中查找数据的程序,和我们在打电话前需要查阅电话本类似。在查表程序中,一般把数据按照一定的顺序排列成表格,存放在单片机的程序存储器中,程序中根据被测数据,查出最终所需结果。查表程序结构一般应用于如下几类计算中:
●在一个无序表格中查找数据,因为是无序的,所以只能逐个查表寻找。
●对于一些复杂的运算,用汇编程序难于计算,而且会占用很长的CPU时间,此时可以将常用的运算结果事先存在单片机中。通过查表获得运算结果。
●对于一些非线性的运算,用汇编语言几乎无法处理,此时,只能用查表获得运算结果。
【范例3-6】 示例代码3-6是一个查表程序结构的单片机汇编语言程序。
示例代码3-6
【运行结果】使用Keil μVision3编译程序可以完成该程序的编译和仿真操作。该程序在执行时,在数据表中查处一个字符。
【代码解析】在该程序中,数据表位TAB,待查找的字符存放在寄存器B中。程序中将查找次数11送入通用寄存器B。然后逐个查找表,如果找到则记录地址,否则将通用寄存器R5和R6清零。