首页 理论教育 汇编语言基本程序设计:基于单片机原理与应用

汇编语言基本程序设计:基于单片机原理与应用

时间:2023-10-23 理论教育 版权反馈
【摘要】:顺序结构程序是汇编语言程序设计中最基本、最单纯的程序,在整个程序设计所占比例最大,也是程序设计的基础。

汇编语言基本程序设计:基于单片机原理与应用

一个应用系统的汇编语言源程序,无论其系统功能的要求简单还是复杂,其程序结构总是由顺序程序、分支程序、循环程序、子程序、查表程序等结构化程序块组合而成。这是汇编语言源程序的设计基础。下面就按这几种基本程序结构来介绍汇编语言的程序设计。

3.2.6.1 顺序结构(简单结构)的程序设计

顺序结构程序又称简单结构程序,它是一种无分支的直接程序,是按照逻辑操作顺序,从第一条指令开始逐步条顺序执行,直到最后一条指令为止。实际上,顺序结构程序是指其组成结构简单,程序逻辑的逻辑流向是一维的。但程序的具体内容不一定简单,在实际编程中,如何正确选择指令(常用指令MOV、ADD、ANL等),合理使用工作寄存器、节省存储单元等,是编写好程序的基本功。

顺序结构程序是汇编语言程序设计中最基本、最单纯的程序,在整个程序设计所占比例最大,也是程序设计的基础。顺序结构程序设计举例如下。

【例3-37】 将片内RAM的30 H单元中的两位压缩BCD码转换成二进制数送到片内RAM的40H单 元中。

两位压缩BCD码转换成二进制数的算法为:(a1 a0BCD=10×a1+a0。其程序流程图如图3-27所示。

图3-27 例3-37程序流程图

参考程序为:

3.2.6.2 分支结构的程序设计

很多复杂的实际问题,总是伴随着逻辑判断,从而选择不同的处理路径,即程序的走向,从而使计算机能实现某种智能的基础。

分支程序的主要特点是程序的流向从一个入口,两个或多于两个出口,根据给定的条件,确定程序的走向。编程的关键是如何确定判断或选择的条件以及如何选择合适的分支指令,MCS-51的指令集提供了丰富的多种分支指令,特别是条件转移、比较转移和位转移指令(如JB/JNB、JBC、JC/JNC、JZ/JNZ、CJNE等),给复杂问题尤其是测控系统的程序设计提供了方便。

一个源程序如果包含有无数个分支,每个分支均有不同的处理程序段,分支中又包含分支,这就使程序的流向十分复杂。因此,程序设计时需要借助程序流程图,把复杂的程序流向展现在平面图上,使之一目了然。为减少程序的复杂性,应尽力少用分支结构程序。

分支程序的设计要点为:先建立可供条件转移指令测试的条件,再选用合适的条件转移指令,最后在转移的目的地址处设定标号。

分支结构程序程序中一定含有转移指令,而转移指令分为无条件转移和带条件转移,因此分支程序也可分为无条件分支转移程序和带条件分支转移程序。带条件分支转移程序按结构类型,又分为单分支转移结构和多分支转移结构。

1.单分支转移结构

单分支结构在程序设计中应用最广,拥有的分支指令也多,其结构一般为一个入口两个出口。常用的单分支结构程序的形式有3种,其流程图如图3-28所示。单分支结构程序的选择条件一般由运算或检测的状态标志提供,选用对应的条件转移指令来实现。

图3-28 单分支结构程序的典型形式

2.多分支转移结构

在实际应用中,常常需要从两个以上的流向(出口)中选一。例如,两个数相比较,必然存在大于、等于、小于三种情况,这时就需从三个分支中选一;再如,多分支跳转(又称散转)将根据运算结果值在多分支中选一。这就形成了多分支结构。常见的多分支结构有两种形式,其流程图如图3-29所示。MCS-51单片机指令系统提供了CJNE(三分支)、JMP@A+DPTR(多分支,即散转)两种多分支选择指令。

分支结构程序允许嵌套,即一个分支接着一个分支,形成树根式多级分支程序结构。汇编语言程序本身并不限制这种嵌套层次数,但过多的嵌套层次将使程序结构变得十分复杂和臃肿,以致造成逻辑上的混乱和错误,因而应尽力避免。

【例3-38】 求符号函数y=sgn(x)的值。已知片内RAM的40 H单元内有一自变量X,编制程序按如下条件求函数Y的值,并将其存入片内RAM的41H单元中。

图3-29 多分支结构程序的典型形式

此题有3个条件,所以有3个分支程序。这是一个3分支归1的条件转移问题。X是有符号数,判断符号位是0还是1可利用JB或JNB指令。判断X是否等于0则直接可以使用累加器A的判0指令。程序流程图如图3-30所示。参考程序为:

图3-30 求符号函数的程序框图

3.2.6.3 循环结构的程序设计

循环结构程序是控制计算机多次、重复执行同一个程序段(称为循环体)的一种基本程序结构。从本质上讲,它是分支结构程序中的一个特殊形式。由于它在程序设计中的重要性,故而配以专用指令,单独作为一种程序结构的形式进行设计。因此循环结构程序可使程序大为缩短、程序所占的内存空间减少、程序结构紧凑且可读性好,从而使编程效率得到提高。

1.循环结构程序的组成

循环结构程序如图3-31所示,它由下面4个部分组成。

(1)循环初始化部分。位于循环程序开头,用于完成循环前的准备工作。例如,设置各工作单元的初始值以及循环次数。

图3-31 两种循环控制结构框图

(2)循环体部分。循环程序的主体,位于循环体内,是循环程序的工作程序,完成实际的处理工作,在执行中会被多次重复使用。要求编写得尽可能简练,以提高程序的执行速度。

(3)循环控制部分。位于循环体内,用于控制循环次数和修改每次循环时的参数,在重复执行循环体的过程中,不断修改和判断循环控制变量,直到符合结束条件,就结束循环程序的执行。它一般由循环次数修改、循环修改和条件语句等组成。

(4)循环结束部分。用于存放执行循环程序所得的结果,以及恢复各工作单元的初值。

2.循环结构的控制

循环结构的控制方法分为循环计数控制法和条件控制法两种。图3-31(a)、图3-31(b)分别为计数循环控制结构、条件控制结构。

(1)计数循环控制结构。这种结构是先循环处理,后循环控制(即先处理后控制)。计数器初值在初始化设定,根据计数器的值来决定循环次数。控制计数器的计数方式一般均为不断减1计数(递减方式),每循环执行一次,控制变量(即计数器)减1,并判是否减为0。若不为0,继续执行循环体程序;若为0,则结束循环程序后顺序往下执行,进入结束处理。这些工作可由MCS-51指令系统中的循环指令DJNZ自动完成。

计数控制只有在循环次数已知的情况下才适用,循环次数未知时不能用循环次数来控制,往往需要根据某种条件来判断是否应该终止循环;循环次数在初始化时预置,由于DJNZ指令采用8位数据来计数,因此,循环次数范围为1~256,如超过此范围,则应采用多重循环方式。

(2)条件控制结构。这种结构是先循环控制,后循环处理(即先控制后处理)。在循环控制中,设置一个结束条件,判别是否满足该条件,如满足,则循环结束。如不满足该条件则循环继续。例如,计算结果达到给定精度要求或达到某一给定条件时就结束循环,此时的循环次数是不固定的。常用条件转移指令来完成。

3.循环结构程序的结构形式

按结构形式,循环结构程序有单重循环与多重循环之分。

(1)单重循环结构:单重循环结构的循环体内部不包括其他循环的程序。

(2)多重循环结构:循环程序中包含循环程序或一个大循环中包含多个小循环程序,称多重循环程序结构,又称循环嵌套。某些复杂问题或者循环数超过256,则需采用多重循环的程序结构。

多重循环的循环重数原则上不受限制,可根据实际需要设计任意重循环,但每个循环必须层次分明,不能有相互交叉。

多重循环的执行过程是由内向外逐层展开。内层循环全部执行完后,外层则执行一次循环,依此类推。如内层循环次数为m,外层循规蹈矩环次数为n,则总的循规蹈矩环次数为m*n次。

4.循环程序时应注意的问题

(1)循环程序是一个有始有终的整体,它的执行是有条件的,所以要避免从循环体外直接转到循环体内部。

(2)多重循环程序是从外层向内层一层一层进入,循环结束时是由内层到外层一层一层退出的。在多重循环中,只允许外重循环嵌套内重循环。不允许循环相互交叉,也不允许从循环程序的外部跳入循环程序的内部。

(3)编写循环程序时,首先要确定程序结构,处理好逻辑关系。一般情况下,一个循环体的设计可以从第一次执行情况入手,先画出重复执行的程序框图,然后再加上循环控制和置循环初值部分,使其成为一个完整的循环程序。

(4)循环体是循环程序中重复执行的部分,应仔细推敲,合理安排,应从改进算法、选择合适的指令入手对其进行优化,以达到缩短程序执行时间的目的。

【例3-39】 编制程序将片内RAM的30 H~4FH单元中的内容传送至片外RAM的2000H开始的单元中。

图3-32 程序流程图

每次传送数据的过程相同,可以用循环程序实现。30H~4FH共32个单元,循环次数应为32次(保存在R2中),为了方便每次传送数据时地址的修改,送片内RAM数据区首地址送R0,片外RAM数据区首地址送DPTR。程序流程图如图3-32所示。

参考程序为:

【例3-40】 统计数据块的长度。内部RAM的40H开始的存储区有若干个数据,最后一个数据为字符0AH,结果存入80H单元。

采用逐个字符依次与“0AH”比较(设置的条件)的方法。设置一个累计字符串长度的长度计数器和一个用于指定字符串指针。如果字符与“0AH”不等,则长度计数器和字符串指针都加1;如果比较相等,则表示该字符为“0AH”,字符串结束,计数器值就是字符串的长度。

参考程序1为:

参考程序2为:

【例3-41】 编制50ms延时程序。

延时程序与MCS-51指令执行时间(机器周期数)和晶振频率f osc有直接的关系。当f osc=12MHz时,1个机器周期为1μs,执行一条DJNZ指令需要2个机器周期,时间为2μs。50ms/2μs>255,因此单重循环程序无法实现,可采用双重循环的方法编写50ms延时程序。参考程序为:

以上延时程序不太精确,它没有考虑到除“DJNZ R6,DEL2”指令外的其他指令的执行时间,如把其他指令的执行时间计算在内,它的延时时间为:

(250μs+1μs+2μs)*200+1μs=50.301ms

如果要求比较精确的延时,可按如下修改:

实际延迟时间为50.001ms,已经非常接近于50ms了。

注意:软件延时程序,不允许有中断,否则将严重影响定时的准确性。对于更长时间的延时,可用来多重循环。比如,20次循环例3-41,可得到1s的延时。

3.2.6.4 子程序结构的程序设计

在实际应用中常会遇到带有通用性的问题,且在同一个源程序中可能需多次用到。这就应该把它单独设计成通用子程序供随时调用。

将那些需多次应用的、完成相同的某种基本运算或操作的程序段从整个程序中独立出来,单独编成一个程序段,需要时进行调用。这样的程序段称为子程序。调用子程序的程序叫做主程序或称调用程序。

优点:采用子程序可使程序结构简单、紧凑,节省程序的存储空间,便于程序调试。

缺点:由于每调用一次需附加断点保护、参量进栈、出栈等开销时间,因此反而降低了程序的执行效率。

子程序在程序设计中非常重要,读者应熟练掌握子程序的设计方法。

1.子程序的基本结构

子程序的结构必须标明子程序的入口地址(又称首地址),以便主程序调用,另外,子程序应以返回指令RET或RETI结束。

在主程序中需要执行调用子程序的地方执行一条调用指令(LCALL或ACALL),从而转到子程序完成规定的操作,当执行完子程序时,再在子程序最后应用RET返回指令返回到主程序断点处,继续向下执行主程序。图3-33为子程序调用与返回过程示意图

图3-33 子程序调用与返回过程示意图

(1)子程序的调用。

1)子程序的入口地址:子程序的第一条指令地址称为子程序的入口地址,常用标号表示。

2)程序的调用过程:单片机收到ACALL或LCALL指令后,首先将当前的PC值(调用指令的下一条指令的首地址)压入堆栈保存(低8位先进栈,高8位后进栈),然后将子程序的入口地址送入PC,转去执行子程序。

(2)子程序的返回。

1)主程序的断点地址:子程序执行完毕后,返回主程序的地址称为主程序的断点地址,它在堆栈中保存。

2)子程序的返回过程:子程序执行到RET指令后,将压入堆栈的断点地址弹回给PC(先弹回PC的高8位,后弹回PC的低8位),使程序回到原先被中断的主程序地址(断点地址)去继续执行。

注意:中断服务程序是一种特殊的子程序,它是在计算机响应中断时,由硬件完成调用而进入相应的中断服务程序。RETI指令与RET指令相似,区别在于RET是从子程序返回,RETI是从中断服务子程序返回。

(3)典型的子程序的基本结构。

主程序

子程序

注意:上述子程序结构中,现场保护与现场恢复不是必需的,要根据实际情况而定。

2.调用子程序应注意两个问题

在汇编语言源程序中调用子程序时,一般应注意两个问题:参数传递和现场保护、恢复。

(1)子程序的参数传递。主程序在调用子程序时传递给子程序的参数和子程序结束后送回主程序的参数统称为参数传递。

入口参数:子程序需要的原始参数。主程序在调用子程序前将入口参数送到约定的存储器单元(或寄存器)中,然后子程序从约定的存储器单元(或寄存器)中获得这些入口参数。

出口参数:子程序根据入口参数执行程序后获得的结果参数。子程序在结束前将出口参数送到约定的存储器单元(或寄存器)中,然后主程序从约定的存储器单元(或寄存器)中获得这些出口参数。

在使用调用指令不附带任何参数时,参数的互相传递要由设计者通过程序安排。一般参数传递可采用以下方法:

1)采用工作寄存器Rn或累加器A传递参数。优点是程序简单、运算速度较快,缺点是工作寄存器有限。

2)将要传递的参数存放在数据存储器RAM中,采用指针寄存器R0、R1或DPTR传递参数。优点是能有效节省传递数据的工作量,并可实现可变长度运算。

3)在内部RAM中设置堆栈进行传递参数。优点是简单,能传递的数据量较大,不必为特定的参数分配存储单元

4)利用位地址传送子程序参数。

注意:子程序的参数传递方法一般也同样适用于中断服务程序的参数传送。

(2)现场保护和恢复。子程序(包括中断服务程序)是个独立的程序段,在子程序执行过程中常需用到通用单元。例如:工作寄存器R0~R7、累加器A、数据指针DPTR,以及有关标志、状态位等。主程序转入子程序后,保护主程序的信息不会在运行子程序时丢失的过程称为保护现场。在执行完子程序并返回继续执行主程序前应恢复其原内容,称为现场恢复。

在进入子程序前或后时,由堆栈完成保护现场,而在从子程序返回之前或之后将堆栈中保存的内容弹回各自的寄存器称为恢复现场。因此,一般有两种现场保护/恢复方式。

1)调用前保护、返回后恢复。这种方式是在主程序的逻辑调节器用指令前进行现场保护,在调用指令之后,即返回原断点处进行恢复现场。

这种结构灵活,可根据实际需要实现现场保护/恢复。

2)调用后保护、返回前恢复。这种结构是在子程序的开始部分进行现场保护,而在子程序的结束部分、返回指令前恢复。

这是子程序标准格式,现场保护/恢复内容固定,但程序规范、清晰。

上述两种方式可由设计者任选,通常采用第2种方式,即在子程序中完成现场的保护与恢复。

3.子程序特性

随着汇编语言程序设计技术的发展,子程序的应用越显重要。因此,对子程序的设计具有较高要求,除通常在程序设计中应遵循的原则外,还应具备以下特性:

(1)通用性。严格讲,子程序有通用和专用两种。前者如数制转换、浮点运算等子程序可广泛应用于同系列单片机的任何应用系统,而后者仅限用于同一个应用系统中。对于前者,子程序的资源要为所有调用程序共享,子程序在结构上应具有通用性。

子程序中某些可变的量称为参量,占用一定的变量单元,每次调用均由实际变量或数据赋值。因此,一个子程序可以对不同的变量或参数进行处理。为了使子程序具有通用性,在设计中要解决的一个重要问题,就是确定哪些变量作为参量以及如何传递参量。

(2)可浮动性。可浮动性是指子程序段可安置在程序存储器的任何区域。为此,在子程序中应避免选用绝对转移地址。

(3)可递归和可重入性。子程序能自己调用自己和同时能被多个任务(或多个用户程序)调用的特性,分别称之为子程序的可递归性和可重入性。这类子程序常在庞大而复杂的程序中应用,在单片机应用程序设计中较少用到。

(4)子程序说明文件。对于通用子程序,为便于各种应用程序的选用,要求在子程序编制、调试完成后提供一个说明文件。其内容应包含以下内容:

1)子程序名。标明子程序功能的名称。

2)子程序功能。简要说明子程序能完成的主要功能,包括重要算法、参量要求及有关存储单元配置等。

3)子程序调用。指明本子程序还需调用哪些子程序。

4)附子程序流程图及程序清单。

4.子程序的设计原则和应注意的问题

在编写子程序时应注意以下问题:

(1)子程序的入口地址一般用标号表示,标号习惯上以子程序的功能命名。例如,延时子程序常用DELAY作为标号。

(2)主程序调用子程序,是通过调用指令来实现。MCS-51单片机有两条子程序调用指令。

1)长调用指令LCALL addr16。指令长度3个字节,addr16为直接调用的目的地址,子程序可放在64KB程序存储器区任意位置。

2)绝对调用指令ACALL addr11。指令长度2个字节,addr11指出了调用的目的地址,被调用的子程序的首地址与绝对调用指令的下一条指令的高5位地址相同,即只能在同一个2KB区内。

(3)子程序返回主程序时,最后一条指令必须是RET指令,功能是把堆栈中的主调程序断点地址弹出送入PC指针中,从而实现子程序返回后从主程序断点处继续执行主程序。

(4)单片机通过调用指令LCALL/ACALL、返回指令RET能自动通过堆栈来保护、恢复主程序的断点地址。但对于各工作寄存器、特殊功能寄存器和内存单元的内容,一般都是自行编程通过堆栈进行保护现场和恢复现场。当然,也可采用主程序、子程序使用不同区段的R n来实现工作寄存器的保护。(www.xing528.com)

(5)子程序内部必须使用相对转移指令,以便子程序可以放在程序存储器64KB存储空间的任何子域并能被主程序调用,汇编时生成浮动代码。

(6)子程序可以嵌套,即主程序可以调用子程序,子程序又可以再调用另外的子程序。MCS-51单片机允许多重嵌套。

(7)在子程序调用时,还要注意参数传递的问题。

【例3-42】 延时子程序。编程使P1口连接的8个LED按下面方式显示:从P1.0连接的LED开始,每个LED闪烁10次,再移向下一个LED,同样闪烁10次,循环不止。

在前面的例子中,我们已经编了一些LED模拟霓虹灯的程序,按照题目要求画出本例的程序流程图如图3-34所示。图中两次使用延时程序段,因此我们把延时程序编成子程序。

参考程序为:

图3-34 例3-42程序流程图

修改上面的程序,将一个灯的闪烁过程也编成子程序形式。修改后的源程序为:

上面程序中,主程序调用了闪烁子程序FLASH,闪烁子程序中又调用延时子程序DELAY,这种主程序调用子程序,子程序又调用另外的子程序的程序结构,称为子程序的嵌套。一般来说,子程序嵌套层数理论上是无限的,但实际上,受堆栈深度的影响,嵌套层数是有限的。

3.2.6.5 查表结构的程序设计

在很多情况下,直接通过查表方式求得的值(变量的值)比通过计算解决要简单、方便得多,而且速度快,实时性强。待查的表格数据一般是一串有规律、按顺序排列的固定常量,MCS-51单片机中把它固化在程序存储器ROM的数据区域内。

查表:根据存放在ROM中数据表格的项数x来查找与它对应的表中值y,使y=f(x)。查表程序可避免复杂的运算或转换过程,完成数据补偿、修正、计算、转换等各种功能,它具有程序简单、执行速度快等优点。

适用场合:主要应用于数码显示、打印字符的转换、数据转换等场合。

MCS-51单片机的指令系统中设置了两条专门查访ROM程序存储器表格类数据的指令。两条查表指令分别是:

(1)MOVC A,@A+DPTR

(2)MOVC A,@A+PC

在执行查表MOVC指令时,发出读程序存储器选通脉冲img。两条指令的功能基本相同,具体使用有差别。两条指令的功能分别为:

(1)指令“MOVC A,@A+DPTR”把A中内容与DPTR中的内容相加,结果为某一程序存储单元的地址,然后把该地址单元的内容送到A中。

(2)指令“MOVC A,@A+PC”,PC的内容与A的内容相加后所得的数作为某一程序存储器单元的地址,根据地址取出程序存储器相应单元中的内容送到累加器A。指令执行后,PC的内容不发生变化,仍指向该查表指令的下一条指令。

指令“MOVC A,@A+DPTR”应用范围较广,使用该指令时不必计算偏移量,表格可以设在64KB程序存储器空间内的任何地方,且可供无限次查表;而“MOVC A,@A+PC”只设在PC下面的256个单元中,且需计算查表指令和函数表之间的偏移量(字节数)。此外,“MOVC A,@A+PC”基本上只能一次性查表,因为PC的当前值随程序的执行而改变。编程时应根据实际情况进行选择,一般选择DPTR为基址指针的查表指令,这样查表更加灵活方便,也可省去一些计算麻烦。

下面说明查表指令的用法和计算偏移量应注意的问题:

(1)采用MOVC A,@A+DPTR指令查表程序的设计方法。当选用DPTR作为基地址的查表指令时,其操作可分3步进行:

1)在程序存储器中建立相应的函数表(设表格具体项数值为自变量x);

2)计算出这个表中所有的函数值y。将这群函数值按顺序存放在起始(基)地址为TABLE的程序存储器中;

3)将表格首地址TABLE送入DPTR,x送入A,采用查表指令MOVC A,@A+DPTR完成查表,就可以得到与x相对应的y值,并存放在累加器A中。

(2)采用MOVC A,@A+PC指令查表程序的设计方法。当选用PC作为基址寄存器时,由于PC本身是一个程序计数器,与指令的存放地址有关,查表时其操作有所不同。其操作可分4步进行:

1)在程序存储器中建立相应的函数表(设自变量为x)。

2)计算出这个表中所有的函数值y。将这群函数值按顺序存放在起始(基)地址为TABLE的程序存储器中。

3)x送入A,使用ADD A,#data指令对累加器A的内容进行修正,偏移量data由公式data=函数数据表首地址PC+1确定,即data值等于查表指令和函数表之间的字节数。

4)采用查表指令MOVC A,@A+PC完成查表,就可以得到与x相对应的y值,并存放在累加器A中。

【例3-43】 利用查表的方法编写y=x 2(x=0,1,2,…,9)的程序。设变量x的值存放在内存30 H单元中,求得的y的值存放在内存31 H单元中。平方表存放在首地址为TABLE的程序存储器中。

方法1:采用MOVC A,@A+PC指令实现,查表过程如图3-35(a)所示。参考程序为:

图3-35 查表过程的示意图

指令“ADD A,#02 H”的作用是A中的内容加上“02H”,“02 H”即为查表指令与平方表之间的“MOV 31H,A”指令所占的字节数。加上“02H”后,可保证PC指向表首,累加器A中原来的内容仅是从表首开始向下查找多少个单元。

方法2:采用MOVC A,@A+DPTR指令实现,查表过程如图3-35(b)所示。参考程序为:

如果DPTR已被使用,则在查表前必须通过PUSH指令保护DPTR的内容,且查表结束后通过POP指令恢复DPTR。

3.2.6.6 实用汇编程序设计的其他例子

上面介绍了几种组成源程序设计的基本结构,一般单片机应用程序不管如何复杂,都是这几种基本程序结构的有机组合。在充分掌握这几种基本程序结构的基础上,结合具体算法,施展程序设计技巧,就能设计出符合要求的、正确可靠、具有较高水平的优秀程序。

下面再从实用角度,分类给出了一些在单片机应用系统软件设计中经常用到的汇编语言程序实例,以供大家学习查阅。

1.数据极值查找的程序设计

在指定的数据区中找出极值(最大值或最小值)。进行数值大小的比较,从一批数据中找出最大值(或最小值)并存于某一单元中。

【例3-44】 片内RAM中存放一批单字节无符号的数据,查找出最大值并存放在首地址中。设R0中存放首地址,R2中存放字节数,程序框图如图3-36所示。

参考子程序为:

2.统计数据块中正数、0和负数的个数的程序设计

图3-36 例3-44程序框图

【例3-45】 从片内RAM中30 H单元开始有100个数据,统计当中正数、0和负数的个数,分别存放在R5、R6、R7中。

设用R2作计数器,用DJNZ指令对R2减1转移进行循环控制,在循环体外设置R0指针,指向片外RAM 30 H单元,对R5、R6、R7清零,在循环体中用指针R0依次取出片外RAM中的100个数据,判断:如大于0,则R5中的内容加1;如等于0,则R6中的内容加1;如小于0,则R7中的内容加1。参考程序为:

3.查找的程序设计

在表中查找关键字的操作,也称为数据检索。有两种方法,即顺序检索和对分检索,本书限于篇幅,仅就顺序检索作介绍。

要检索的表是无序的,检索时只能从第1项开始逐项查找,判断所取数据是否与关键字相等。

【例3-46】 从ROM的50个字节无序表中查找一个关键字“xx H”。假设待查找的内容“xx H”存放在内部RAM的30 H单元中,50个字节的无序表在ROM中,表格首址在DPTR中,表格的字节数在R1中。找到时,OV=0且把地址送R2、R3;未找到时,OV=1且把R2、R3清0。

图3-37 例3-46流程图

参考子程序为:

4.排序的程序设计

将一批数由小到大(升序)排列,或由大到小(降序)排列。最常用的数据排序算法是冒泡法,是相邻数互换的排序方法,因其过程类似于水中气泡上浮,故称冒泡法。排序时,从前向后进行相邻两个数的比较,如果数据的大小次序与要求的顺序不符时,就将两个数互换;否则,顺序符合要求就不互换。

如果进行无符号数据的升序排序,应通过这种相邻数互换方法,使小数向前移,大数向后移。如此从前向后进行一次相邻数互换(冒泡),就会把这批数据的最大数排到最后,次大数排在倒数第二的位置,从而实现一批数据由小到大的排列。

假设有7个原始数据的排列顺序为6、4、1、2、5、7、3。第一次冒泡的过程是:

如此进行,各次冒泡的结果如下:

第1次冒泡结果:4、1、2、5、6、3、7

第2次冒泡结果:1、2、4、5、3、6、7

第3次冒泡结果:1、2、4、3、5、6、7

第4次冒泡结果:1、2、3、4、5、6、7;已完成排序

第5次冒泡结果:1、2、3、4、5、6、7

第6次冒泡结果:1、2、3、4、5、6、7

对于n个数,理论上应进行(n-1)次冒泡才能完成排序,实际上有时不到(n-1)次就已完成排序。例如,上面的7个数,应进行6次冒泡,但实际上第4次冒泡时就已经完成排序。

如何判定排序是否已经完成,就是看各次冒泡中是否有互换发生,如果有,则排序还没完成;否则就表示已经排好序。在程序设计中,常用设置互换标志的方法,用标志的状态表示是否有互换进行。

【例3-47】 排序程序设计(冒泡法)。设内部RAM起始地址为40H的数据块中共存有64个无符号数,编制程序使它们按从小到大的顺序排列。

下面采用冒泡法进行编程排序,其程序流程图如图3-38所示。

以R0为首地址指针,R2、R3中为内外循环的字节数,互换标志位2FH.7(位地址7FH),相比较的两个数据存放在20 H和21 H中。参考程序为:

图3-38 单字节无符号数冒泡法程序流程图(升序排序)

5.多字节算术运算的程序设计

【例3-48】 多字节求补运算。假设在片内RAM的30H单元开始有一个8字节数据,对该数据求补,结果放回原位置。

在MCS-51系统中没有求补指令,只有通过取反末位加1得到。而当末位加1时,可能向高字节产生进位。因而在处理时,最低字节采用取反加1,其余字节采用取反加进位,通过循环来实现。参考程序为:

【例3-49】 双字节无符号数乘法。设被乘数存放在R2、R3寄存器中(R2高位,R3低位),乘数存放在R6、R7中(R6高位,R7低位),乘积存放在R4、R5、R6、R7寄存器中(R4为高位,R7为低位)。

先用一个具体例子来分析乘法的具体过程:设被乘数=6,乘数=5,相乘公式为:

把乘数(c=c2c1c0=101)的每一位分别与被乘数(b=b2b1b0=110)相乘,操作过程为:

(1)相乘的中间结果称为部分积,假设为x,y(x保存低位,y保存高位),预设Cy=0,x=0,y=0。

(2)c0=1,x=x+b=b2 b1 b0。

(3)x、y右移1位,Cy→x→y→Cy,把x的低位移入y中,则x=0 b2 b1,y=b0。

(4)c1=0,x=x+000。

(5)Cy→x→y→Cy,x=00 b2,y=b1 b0。

(6)c2=1,x=x+b=00 b2+b2b1b0=111,Cy=0,y=b1b0=10。

(7)右移1次:Cy→x→y→Cy。

乘数的每一位都计算完毕,x和y中的值合起来即为所求乘积。

由以上分析可见,对于3位二进制乘法,部分积x、y也是3位的,这种方法称为部分积右移计算方法。部分积右移算法归纳如下:

(1)将存放部分积的寄存器清0,设置计数位数,用来表示乘数位数。

(2)从最低位开始,检验乘数的每一位是0还是1,若该位是1,部分积加上被乘数;若该位为0,就跳过去不加。

(3)部分积右移1位。

(4)判断计数器是否为0,若计数器不为0,重复步骤(2),否则乘法完成。

部分积右移算法的程序流程图如图3-39所示。

图3-39 部分积右移算法流程图

参考子程序为:

6.数据拼拆的程序设计

【例3-50】 设在30H和31H单元中各有一个8位数据:(30H)=x7x6x5x4x3x2x1x0、(31H)=y7y6y5y4y3y2y1y0。现在要从30 H单元中取出低5位,并从31 H单元中取出低3位完成拼装,拼装结果送40H单元保存,并且规定:(40H)=y2y1y0x4x3x2x1x0。

利用逻辑指令ANL、ORL、RL等来完成数据的拼拆。处理过程:将30 H单元的内容高3位屏蔽;31H单元内容的高5位屏蔽,高、低4位交换,左移1位;然后与30H单元的内容相或,拼装后放到40 H单元。参考程序为:

7.代码转换的程序设计

在计算机内部,任何数据最终都是以二进制形式出现的。但是人们通过外部设备与计算机交换数据采用的常常又是一些别的形式。例如标准的编码键盘和标准的CRT显示器使用的都是ASCII码;人们习惯使用的是十进制,在计算机中表示为BCD码等。因此,汇编语言程序设计中经常会碰到代码转换的问题,这里提供了BCD码、ASCII码与二进制数相互转换的基本方法和子程序代码。

【例3-51】 二进制数转换为BCD码。将累加器A中的二进制数0~FFH内的任一数转换为BCD码(0~255),转换后的BCD码存放在B(百位)和A(十位和个位)中。

BCD码是每4位二进制数表示一位十进制数,本例所要求转换的最大BCD码为255,表示成BCD码需要12位二进制数,超过了一个字节(8位),因此我们把高4位存放在B的低4位,高4位清零;低8位存放在A中。

即(BA)=0000 0010 0101 0101B。

转换的方法是将A中二进制数除以100、10,所得商即为百、十位数,余数为个位数。参考子程序为:

【例3-52】 ASCII码转换为二进制数。将累加器A中的十六进制数的ASCII码(0~9、A~F)转换成4位二进制数(0~F),转换后的结果还存放在A中。

在单片机汇编程序设计中主要涉及十六进制的十六个符号“0~F”的ASCII码同其数值的转换。ASCII码是按一定规律表示的,数字0~9的ASCII码即为该数值加上30 H,而对于字母“A~F”的ASCII码即为该数值加上37H。0~F对应的ASCII码如下:

参考子程序1为:

参考子程序2为:

【例3-53】 二进制数转换为ASCII码。将累加器A中的一位16进制数(A中低4位,x0 H~x FH)转换成ASCII码,还存放在累加器A中。参考程序为:

8.多分支转移(散转)的程序设计

散转程序是一种并行分支程序(多分支程序),它是根据某种输入或运算结果,分别转向各个处理程序。

散转程序可采用4种设计方法,下面分别举例说明:

(1)用查转移指令表法。采用分支转移指令AJMP与LJMP,并按分支号转移。对AJMP指令应将分支序号乘以2,转移范围为2KB;对LJMP指令应将分支序号乘以3,转移范围为64KB。

先用无条件转移指令(AJMP或LJMP)按顺序构造一个转移指令表,执行转移指令表中的第n条指令,就可以转移到第n个分支,将转移指令表的首地址装入DPTR中,将分支信息K装入累加器A形成变址值。然后执行散转指令JMP@A+DPTR实现多分支转移(散转)。转移的地址最多为256个。

【例3-54】 根据工作寄存器R2内容的不同,使程序转入相应的分支。

参考程序1为:

转移指令表中的转移指令是由AJMP指令构成。AJMP指令的转移范围不超出2KB字节空间,如各分支程序比较长,在2KB范围内无法全部存放,应改为LJMP(指令长度为3字节)。

参考程序2为:

(2)采用地址偏移量表实现的散转程序。它直接利用地址偏移量形成转移表。如果散转点较少,而且所有操作程序处在同一页(256B)内,则可以使用地址偏移量表的方法实现散转。其特点是程序简单、转移表短,转移表和处理程序可位于程序存储器的任何地方。数据见图3-40。

图3-40 数据表格

【例3-55】 有BR0、BR1、BR2和BR3共4个分支程序段,各分支程序段的功能依次是从内部RAM256B范围取数、从外部RAM低256B范围取数、从外部RAM4KB范围取数和从外部RAM64KB范围取数。并假定R0中存放取数地址低8位地址,R1中存放高8位地址,而R3中存放分支序号值,要求按R3的内容转向4个操作程序。假定以BRTAB作差值表首地址,BR0_BRTAB~BR3_BRTAB为差值。差值表=分支入口地址-该表首址。

参考程序为:

(3)采用转向地址表的散转程序。前面讨论的采用地址偏移量表的方法,其转向范围局限于一页之内,在使用时,受到较大的限制。若需要转向较大的范围,可以建立一个转向地址表,即将所要转向的二字节地址组成一个表,在散转时,先用查表的方法获得表中的转向地址,并将该地址装入数据指针DPTR中,然后清除累加器A,执行JMP@A+DPTR指令,便能转向到相应的操作程序中去。限于篇幅,举例从略。

(4)采用RET指令实现散转程序。用子程序返回指令RET实现散转。其方法是:在查找到转移地址后,不是将其装入DPTR中,而是将它压入堆栈中(先低位字节,后高位字节,即模仿调用指令)。然后通过执行RET指令,将堆栈中的地址弹回到PC中实现程序的转移。限于篇幅,举例从略。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈