《汇编语言及C语言程序设计.ppt》由会员分享,可在线阅读,更多相关《汇编语言及C语言程序设计.ppt(88页珍藏版)》请在三一办公上搜索。
1、第4章 汇编语言及C语言程序设计,4.1 程序设计概述4.2 汇编语言源程序的编辑和汇编4.3 汇编语言程序设计4.4 C51基本语法4.5 C51与汇编的结合,411 程序设计语言简介1 机器语言 机器语言就是用二进制(可缩写为十六进制)代码来表示指令和数据。也称为机器代码、指令代码。机器语言是计算机唯一能识别和执行的语言,用其编写的程序执行效率最高,速度最快,但由于指令的二进制代码很难记忆和辨认,给程序的编写、阅读和修改带来很多困难。所以,没有人使用机器语言来编写程序。2 汇编语言 用助记符表示的指令就是计算机的汇编语言,汇编语言与机器语言一一对应。用汇编语言编写程序,每条指令的意义一目了
2、然,给程序的编写、阅读和修改带来很大方便。而且用汇编语言编写的程序占用内存少,执行速度快,尤其适用于实时应用场合的程序设计。因此,在单片机应用系统中主要是用汇编语言来编写程序。,41 程序设计概述,汇编语言也有它的缺点:缺乏通用性,程序不易移植,是一种面向机器的低级语言。即使用汇编语言编写程序时,仍必须熟悉机器的指令系统、寻址方式、寄存器的设置和使用方法。每个计算机系统都有它自己的汇编语言。不同计算机的汇编语言之间不能通用。3 高级语言 高级语言是一种面向算法、过程和对象的程序设计语言,它采用接近人们自然语言和习惯的数学表达式及直接命令的方法来描述算法、过程和对象,如BASIC、C语言等。高级
3、语言的语句直观,易学,通用性强,便于推广、交流,但高级语言编写的程序经编译后所产生的目标程序大,占用内存多,运行速度较慢,这在实时应用中是一个突出的问题。,412 汇编语言程序设计步骤使用汇编语言设计程序大致上可分为以下几个步骤:(1)分析题意,明确要求。解决问题之前,首先要明确所要解决的问题和要达到的目的、技术指标等;(2)确定算法。根据实际问题的要求、给出的条件及特点,找出规律性,最后确定所采用的计算公式和计算方法,这就是一般所说的算法。算法是进行程序设计的依据,它决定了程序的正确性和程序的指令。(3)画程序流程图,用图解来描述和说明解题步骤。程序流程图是程序设计的依据,它直观清晰的体现了
4、程序的设计思路。流程图是用预先约定的各种图形、流程线及必要的文字符号构成的。标准的流程图符号如图4-1所示。,(4)分配内存工作单元,确定程序与数据的存放地址。(5)编写源程序。流程图设计后,程序设计思路就比较清楚了,接下来的任务就是选用合适的汇编语言指令来实现流程图中每一框内的要求,从而编制出一个有序的指令流,这就是源程序设计。(6)程序优化。程序优化的目的在于缩短程序的长度,加快运算速度和节省存储单元。如恰当的使用循环程序和子程序结构,通过改进算法和正确使用指令来节省工作单元及减少程序执行的时间。(7)上机调试、修改、最后确定源程序。只有通过上机调试并得出正确结果的程序,才能认为是正确的程
5、序。对于单片机来说,没有自开发的功能,需要使用仿真器或利用仿真软件进行仿真调试,修改源程序中的错误,直至正确为止。,421 伪指令指令能使CPU执行某种操作,能生成对应的机器代码。伪指令不能命令CPU执行某种操作,也没有对应的机器代码。它的作用仅用来给汇编程序提供某种信息。常用的伪指令如下:1汇编起始伪指令 ORG格式:标号:ORG 16位地址功能:规定程序块或数据块存放的起始地址。如:ORG 8000H START:MOV A,#30H 该伪指令规定第一条指令从地址8000H单元开始存放,即标号START的值为8000H。,42 汇编语言源程序的编辑和汇编,2汇编结束伪指令 END格式:标号
6、:END 表达式功能:结束汇编。汇编程序遇到END伪指令后即结束汇编。3定义字节数据伪指令 DB格式:标号:DB 8位字节数据表功能:从标号指定的地址单元开始,将数据表中的字节数据按顺序依次存入。数据表可以是一个或多个字节数据、字符串或表达式,各项数据用“,”分隔,一个数据项占一个字节单元。例如 ORG 1000HTAB:DB-2,-4,100,30H,A,C汇编后:(1000H)=FEH,(1001H)=FCH,(1002H)=64H,(1003H)=30H,(1004H)=41H,(1005H)=43H用单引号括起来的字符存其ASC码,负数存其补码。,4定义字数据伪指令 DW格式:标号:D
7、W 16位字数据表功能:从标号指定的地址单元开始,将数据表中的字数据按从左到右的顺序依次存入。应注意:16位数据存入时,先存高8位,后存低8位。ORG 1400HDATA:DW 324AH,3CH 汇编后:(1400H)=32H,(1401H)=4AH,(1402H)=00H,(1403H)=3CH5定义空间伪指令 DS格式:标号:DS 表达式功能:从标号指定的地址单元开始,保留若干个存储单元作为备用的空间。保留的数量由表达式指定。,例如 ORG 3000HBUF:DS 05H 汇编后,从地址3000H开始保留5个存储单元作为备用。应注意:DB、DW、DS伪指令只能对程序存储器进行定义,不能对
8、数据存储器进行定义;DB伪指令常用来定义数据,DW伪指令常用来定义地址。6赋值伪指令EQU(或=)格式:符号名 EQU 表达式 或 符号名=表达式功能:将表达式的值定义为一个指定的符号名。应注意:用EQU定义的符号不允许重复定义,用“=”定义的符号允许重复定义。,43 汇编语言程序设计,4.3.1 顺序程序设计4.3.2 分支程序设计4.3.3 循环程序设计4.3.4 子程序设计4.3.5 运算类程序设计汇编语言程序共有四种结构形式,即:顺序结构、分支结构、循环结构和子程序结构。,431 顺序程序设计 顺序结构程序是一种最简单、最基本的程序(也称为简单程序),它是一种无分支的直线型程序,按照程
9、序编写的顺序依次执行。编写这类程序主要应注意正确地选择指令,提高程序的执行效率。例4-1 编写16位二进制数求补程序。设16位二进制数存放在R1R0中,求补以后的结果则存放于R3R2中。解:二进制数的求补可归结为“求反加1”的过程。求反可用CPL指令实现;加1时应注意,加1只能加在低8位的最低位上。因为现在是16位数,有两个字节,因此要考虑进位问题,即低8位取反加1,高8位取反后应加上低8位加1时可能产生的进位。,ORG 0200H MOV A,R0;低8位送A CPL A;取反 ADD A,#01H;加1 MOV R2,A;存结果 MOV A,R1;高8位送A CPL A;取反 ADDC A
10、,#00H;加进位 MOV R3,A;存结果 END,例4-2 编程将20H单元中的8位无符号二进制数转换成三位BCD码,并存放在22H(百位)和21H(十位,个位)两个单元中。解:因8位二进制数对应的十进制数为0255,所以先将原数除以100,商就是百位数的BCD码,余数作为被除数再除以10,商为十位数的BCD码,最后的余数就是个位数的BCD码,将十位、个位的BCD码合并到一个字节中,将结果存入即可。ORG 1000H MOV A,20H;取数送A MOV B,#64H;除数100送B中 DIV AB;商(百位数BCD码)在A中,余数在B中 MOV 22H,A;百位数送22H MOV A,B
11、;余数送A作被除数,MOV B,#0AH;除数10送B中 DIV AB;十位数BCD码在A中,个位数在B中 SWAP A;十位数BCD码移至高4位 ORL A,B;并入个位数的BCD码 MOV 21H,A;十位、个位BCD码存入21H END432 分支程序设计在很多实际问题中,都需要根据不同的情况进行不同的处理。这种思想体现在程序设计中,就是根据不同条件而转到不同的程序段去执行,这就构成了分支程序。分支程序的结构有两种,如图4-2所示。,图(a)结构是用条件转移指令来实现分支。当给出的条件成立时,执行程序段A,否则执行程序段B。图(b)结构是用散转指令JMP来实现多分支转移。它首先将分支程序
12、按序号排列,然后按照序号的值来实现多分支转移。,例4-3 设变量X存放在30H单元,函数值Y存入31H单元。试编程,按照下式的要求给Y赋值。Y=解:X是有符号数,因此可以根据它的符号位来决定其正负,判别符号位是0还是1可利用JB或JNB指令。而判别X是否等于0则可以直接使用累加器判零JZ指令。把这两种指令结合使用就可以完成本题的要求。程序流程图如图4-3所示。,ORG 1000H MOV A,30H;取数X送A JZ COMP;X=0,则转 COMP JNB ACC.7,POSI;X0,则转POSI MOV A,#0FFH;X0,则Y=1COMP:MOV 31H,A;存函数值 END,例4-4
13、 设5AH单元中有一整数X,请编写计算下述函数式的程序,结果存入5BH单元。Y=解:根据题意首先计算X2,并暂存于R1中,因为X2最大值为225,故只用一个寄存器,然后根据X值的范围,决定Y的值。在判断A15时,采用CJNE、JC指令相结合以及CJNE、JNC指令相结合的方法进行判断。程序流程图如图4-4所示。R0用做中间寄存器。,源程序如下:ORG 2000H MOV A,5AH;取数X送A MOV B,A;X送B MUL AB;计算X2 MOV R1,A;X2暂存R1中 MOV A,5AH;重新把X装入A CJNE A,#0AH,L1;X与10比较L1:JC L2;若X15,41作为函数值
14、送R0 CJNE A,#10H,L3;X与16比较 L3:JNC L4;若X16,转L4,MOV A,R1;10X15,取X2送A中 ADD A,#08;计算X2+8 MOV R0,A;函数值送R0 SJMP L4;转L4L2:MOV A,R1;取X2送A中 CLR C;清CY,为减法作准备 SUBB A,#01H;计算X2-1 MOV R0,A;函数值送R0L4:MOV 5BH,R0;函数值存入指定单元 SJMP$END散转程序:就是用散转指令“JMP A+DPTR”来实现多分支程序的转移。,利用JMP指令实现多分支转移时,首先应在ROM中建立一个散转表,表中可以存放无条件转移指令、地址偏移
15、量或各分支入口地址。表中存放的内容不同,所编写的散转程序也就不同。在编写散转程序时,一般是将散转表的首地址送DPTR,分支序号送A,根据序号查找相应的转移指令或入口地址,从而实现多分支的转移。假设各分支入口地址:OPR0:操作程序0 OPR1:操作程序1 OPR2:操作程序2 OPR3:操作程序3 OPRn:操作程序n,对这种情况,可以先用无条件转移指令(如AJMP)按顺序组成一个转移表,将转移表首地址装到数据指针DPTR中,将分支序号装入A,执行“JMP A+DPTR”指令进入转移表后,再由“AJMP”指令转入对应程序段的入口。从而实现散转。(1)采用转移指令组成表 在许多实际应用中,往往要
16、根据某标志单元的内容(键盘输入或运算结果)是0,1,2,n,分别转向操作程序0,操作程序1,操作程序2,操作程序n。例4-5 试编程根据R7的内容,转向相应的操作程序段。若 R7=0,转入OPR0段 R7=1,转入OPR1段 R7=n,转入OPRn段,编写散转程序如下:JUMP1:MOV DPTR,#TAB1;送转移表首地址 MOV A,R7;序号送A ADD A,R7;序号2A(因AJMP指 令占2个字节,修正变址值)NOAD:JMP A+DPTR;转入转移表内TAB1:AJMP OPR0;转移指令组成转移表 AJMP OPR1 AJMP OPRn 此段散转程序适用n127,(2)采用地址偏
17、移量组成表当分支序号较少,各段程序较短,所有分支程序均处在256字节之内时,可使用地址偏移量组成表。例4-6 编程根据R7的内容,转向相应的操作程序。设(R7)=04编程如下:JUMP2:MOV DPTR,#TAB2;送表首地址 MOV A,R7;分支序号送A MOVC A,A+DPTR;根据序号查表,取地址偏移量送A JMP A+DPTR;表首地址地址偏移量形成目标地址 TAB2:DB OPR0-TAB2;分支入口地址与表首的DB OPR1-TAB2 偏移量定义到表中 DB OPR2-TAB2 DB OPR3-TAB2 DB OPR4-TAB2,(3)采用各分支入口地址组成表若需要转向较大的
18、范围,可以建立一个转移地址表,即将所要转移的各分支入口地址(16位地址),组成一个表。在散转之前,先用查表方法获得表中的转移地址,然后将该地址装入DPTR,最后按DPTR中的内容进行散转。例4-7 试编程根据R7的内容(即分支序号),转向相应的操作程序。设各分支转移入口地址同上,编写散转程序如下:JUMP3:MOV DPTR,#TAB3;首地址送DPTR MOV A,R7;取分支序号送A ADD A,R7;序号2(转移地址占2个字节)DADD:MOV R3,A;暂存2倍序号(即索引值)MOVC A,A+DPTR;根据序号查表,取转移地址高8位送A XCH A,R3;转移地址高8位与R3互换,I
19、NC A;2倍序号加1送A(索引值加1)MOVC A,A+DPTR;查表取转移地址低8位送A MOV DPL,A;转移地址低8位送DPL MOV DPH,R3;转移地址高8位DPH CLR A;A清0 JMP A+DPTR;按DPTR中的地址进行转移TAB3:DW OPR0;将各分支入口地址定义到表中 DW OPR1 DW OPRn这种方法可以实现64KB地址空间的转移,分支数最大为128。,433 循环程序设计循环程序:需多次重复执行的某段程序。循环程序一般由四部分组成:(1)置循环初值。如置循环次数、地址指针及工作单元清0等。(2)循环体。即循环的工作部分,完成主要的计算或操作任务,是重复
20、执行的程序段。这部分程序应特别注意,因为它要重复执行许多次,若能少写一条指令,实际上就是少执行某条指令若干次。因此,应注意优化程序。(3)循环修改。每循环一次,就要修改循环次数、数据及地址指针等。(4)循环控制。根据循环结束条件,判断是否结束循环。循环程序结构有两种,如图4-5所示。,图(a)结构是“先执行后判断”,适用于循环次数已知的情况。其特点是一进入循环,先执行循环处理部分,然后根据循环次数判断是否结束循环。图(b)结构是“先判断后执行”,适用于循环次数未知的情况。其特点是将循环控制部分放在循环的入口处,先根据循环控制条件判断是否结束循环。若不结束,则执行循环操作;若结束,则退出循环。上
21、述两种结构的程序设计在指令系统中做过介绍,见例3-21和例3-22。下面通过一些实际的例子,说明如何编制循环程序。,例4-8 编写多字节无符号数加法程序设有两个多字节无符号数分别存放在内部RAM的DAT1和DAT2开始的区域中,字节个数存放在R2中。求它们的和,并将结果存放在DAT1开始的区域中解:多字节首地址已知,应采用间接寻址方式,多字节的求和,应采用循环程序,流程图如图4-6所示。程序如下:,ORG 0200H MOV R0,#DAT1;数据块首地址送R0 MOV R1,#DAT2;数据块首地址送R1 CLR C;清进位CY LOOP:MOV A,R0;取一个数送A ADDC A,R1;
22、两个数相加 MOV R0,A;存结果 INC R0;修改地址指针 INC R1 DJNZ R2,LOOP;字节数减1,不为0,继续求和 CLR A;A清0 ADDC A,#00H;加进位 MOV R0,A;进位值存到高地址中 END,例4-9 编写查找最大值程序设从内部RAM30H单元开始存放着10个无符号数,找出其中的最大值送入内部RAM的MAX单元。解:寻找最大值的方法很多,最基本的方法是比较和交换依次进行的方法,即先取第一个数和第二个数比较,并把前一个数作为基准。若比较结果基准数大,则不作交换,再取下一个数来作比较;若比较结果基准数较小,则用较大的数来代替原有的基准数,即做一次交换。然后
23、再以基准数和下一个数作比较。总之,要保持基准数是到目前为止最大的数,比较结束时,基准数就是所求的最大值。流程图如图4-7所示。程序如下:,ORG 0200H MOV R0,#30H;数据区首地址送R0 MOV A,R0;取第一个数作基准数送A MOV R7,#09H;比较次数送计数器R7LP:INC R0;修改地址,指向下一地址单元 MOV 30H,R0;要比较的数暂存30H中 CJNE A,30H,CHK;两数作比较CHK:JNC LP1;A大,则转移 MOV A,R0;A小,则将较大数送ALP1:DJNZ R7,LP;计数器减1,不为0,继续 MOV MAX,A;比较完,存结果 END,例
24、4-10 编写数据检索程序假从内部RAM60H单元开始存放着32个数据,查找是否有“$”符号(其ASCII码为24H),如果找到就将数据序号送入2FH单元,否则将FFH送入2FH单元。解:数据检索就是在指定数据区中查找关键字。比如在考勤系统中,有时需要查找某个职工上下班情况,或磁卡就餐系统中将某个磁卡挂失或销户等就属于这类问题。实现数据检索的算法有很多,如顺序检索、对分检索等。对分检索需要先对数据排序;顺序检索是把关键字与数据区中的数据从前向后逐个比较,判断是否相等。本例采用顺序检索进行编程。流程图如图4-8所示。,ORG 0300H MOV R0,#60H;数据区首地址送R0 MOV R7,
25、#20H;数据长度送计数器R7 MOV 2FH,#00H;工作单元清0LP:MOV A,R0;取数送A CJNE A,#24H,LP1;与“$”比较,不等转移 SJMP HERE;找到,转结束LP1:INC R0;修改地址指针 INC 2FH;序号加1 DJNZ R7,LP;计数器减1,不为0,继续 MOV 2FH,#0FFH;未找到,标志送2FH单元HERE:AJMP HERE;程序结束(序号在2FH单元)END,例4-11 编写50ms软件延时程序解:软件延时程序一般都是由DJNZ Rn,rel 指令构成。执行一条DJNZ指令需要2个机器周期。所以,软件延时时间主要与机器周期和延时程序中的
26、循环次数有关,在使用12MHz晶振时,一个机器周期为1s,执行一条DJNZ指令需要2个机器周期,即2s。延时50ms需用双重循环,源程序如下:DEL:MOV R7,#125;执行时需1个机器周期DEL1:MOV R6,#200;DEL2:DJNZ R6,DEL2;2002=400s(内循环时间)DJNZ R7,DEL1;0.4ms125=50ms(外循环时间)RET 以上延时时间是粗略的计算,不太精确,它没有考虑到除DJNZ R6,DEL2指令外其它指令的执行时间,如把其它指令的执行时间计算在内,它的延时时间为:(400+1+2)125+1=50.375ms延时时间=(2TMR6+3TM)R7
27、+1TM(2R6R7+3R7)TM,如果应用系统中对延时时间的要求不是十分严格,可按粗略计算的方法进行计算和编程,如果系统要求比较精确的延时,可按如下修改:DEL:MOV R7,#125;DEL1:MOV R6,#198;NOPDEL2:DJNZ R6,DEL2;内循环时间1982+2=398s DJNZ R7,DEL;外循环时间=(398+2)125+1=50.001ms RET上述程序延时时间为50.001ms。应注意,用软件实现延时的系统,不允许有中断,否则将严重影响定时的准确性。对于更长时间的延时,可采用更多重的循环,如延时1秒时,可用三重循环。,例4-12 编写无符号数排序程序。假设
28、在片内RAM中,起始地址为40H的10个单元中存放有10个无符号数。试进行升序排序。解:数据排序常用方法是冒泡排序法。这种方法的过程类似水中气泡上浮,故称冒泡法。执行时从前向后进行相邻数的比较,如数据的大小次序与要求的顺序不符就将这两个数互换,否则不互换。对于升序排序,通过这种相邻数的互换,使小数向前移动,大数向后移动。从前向后进行一次冒泡(相邻数的互换),就会把最大的数换到最后。再进行一次冒泡,就会把次大的数排在倒数第二的位置。依此类推,完成由小到大的排序。流程图如图4-9所示。,编程中选用R7作比较次数计数器,初始值为09H,位地址00H作为冒泡过程中是否有数据互换的标志位,若(00H)=
29、0,表明无互换发生,已完成排序。若(00H)=1,表明有互换发生。ORG 0400HSTART:MOV R0,#40H;数据区首址送R0 MOV R7,#09H;各次冒泡比较次数送R7 CLR 00H;互换标志位清0LOOP:MOV A,R0;取前数送A中 MOV 2BH,A;暂存到2BH单元中 INC R0;修改地址指针 MOV 2AH,R0;取后数暂存到2AH单元中 CLR C;清CY SUBB A,R0;前数减后数,JC NEXT;前数小于后数,则转(不互换)MOV R0,2BH;前数大于后数,两数交换 DEC R0 MOV R0,2AH INC R0;地址加1,准备下一次比较 SETB
30、 00H;置互换标志NEXT:DJNZ R7,LOOP;未比较完,进行下一次比较 JB 00H,STAR;有交换,表示未排完序,进行下一轮冒泡 END,434 子程序设计1子程序的结构与设计注意事项子程序:能够完成一定功能、可以被其他程序调用的程序段。调用子程序的程序称为主程序或调用程序。子程序结构:与一般程序区别是在子程序末尾有一条子程序返回指令(RET),子程序开始处有入口地址。设计子程序时注意事项:(1)要给每个子程序赋一个名字,实际上是子程序入口地址的符号。(2)明确入口条件、出口条件。所谓入口条件,表明子程序需要哪些参数,放在哪个寄存器和哪个内存单元。出口条件则表明子程序处理的结果是
31、如何存放的。,(3)注意保护现场和恢复现场。在调用子程序之前,某些寄存器中可能存放有主程序的中间结果,这些中间结果在主程序中仍有用,这就要求在子程序使用累加器和这些寄存器之前,要将其内容保护起来,即保护现场。当子程序执行完毕,既将返回主程序之前,再将这些内容取出,送到累加器或原来的寄存器中,这一过程称为恢复现场。一般使用堆栈来保护现场。当需要保护现场时,要在子程序的开始使用压栈指令PUSH,把需要保护的寄存器内容压入堆栈。在返回指令RET前边再使用弹栈指令POP,把堆栈中保护的内容弹到原来的寄存器,要注意,由于堆栈操作是“先入后出”,先压入堆栈的参数应该后弹出,这样才能保证正确恢复原来的数据。
32、,2子程序的调用与返回主程序调用子程序是通过子程序调用指令 LCALL add16和ACALL add11来实现的。子程序返回是通过返回指令RET实现的。主程序在调用子程序时要注意以下问题。(1)在主程序中,要安排相应指令来满足子程序的入口条件,即提供子程序的入口数据。(2)在主程序中,要安排相应的指令,处理子程序提供的出口数据。(3)在主程序中,不希望被子程序更改内容的寄存器,也可以在调用前由主程序安排压栈指令来保护现场,然后子程序返回后再安排弹栈指令恢复现场。(4)在主程序中,要正确地设置堆栈指针。,3子程序嵌套 子程序嵌套(或称多重转子)是指在子程序执行过程中,还可以调用另一个子程序。子
33、程序嵌套过程见图3-9所示。堆栈在子程序调用中是必不可少的。因为断点地址均是自动存入堆栈区的。,例4-13 用程序实现C=a2+b2。设a、b 均小于10。a存在31H单元,b存在32H单元,把C存入33H单元。解:因本题二次用到平方值,所以在程序中采用把求平方编为子程序的方法。子程序名称:SQR。功能:求X2(通过查平方表来获得)。入口参数:某个数在A中。出口参数:某数的平方在A中。主程序是通过两次调用子程序来得到a2和b2,并在主程序中完成相加。依题意编写主程序和子程序如下:主程序:,ORG 2200H MOV SP,#3FH;设堆栈指针 MOV A,31H;取a值 LCALL SQR;第
34、一次调用,求a2 MOV R1,A;a2值暂存R1中 MOV A,32H;取b 值 LCALL SQR;第二次调用,求b2 ADD A,R1;完成a2+b2 MOV 33H,A;存结果到33H单元 SJMP$;暂停子程序:ORG 2400HSQR:ADD A,#01H;查表位置调整 MOVC A,A+PC;查表取平方值 RET;子程序返回TAB:DB 0,1,4,9,16,25,36,49,64,81,例4-14 求两个无符号数据块中的最大值。数据块的首地址分别为60H和70H,每个数据块的第一个字节都存放数据块的长度。结果存入5FH单元。解:本例可采用分别求出两个数据块的最大值,然后比较其大
35、小的方法。求最大值的过程可采用子程序。子程序名称:QMAX子程序入口条件:R1中存有数据块首地址。出口条件:最大值在A中。下面分别编写主程序和子程序:ORG 2000H MOV SP,#3FH;设堆栈指针 MOV R1,#60H;取第一数据块首地址送R1中 ACALL QMAX;第一次调用求最大值子程序 MOV 40H,A;第一个数据块的最大值暂存40H MOV R1,#70H;取第二数据块首地址送R1中 ACALL QMAX;第二次调用求最大值子程序,CJNE A,40H,NEXT;两个最大值进行比较NEXT:JNC LP;A大,则转LP MOV A,40H;A小,则把40H中内容送入A L
36、P:MOV 5FH,A;存最大值到5FH单元 SJMP$子程序:ORG 2200HQMAX:MOV A,R1;取数据块长度 MOV R2,A;R2作计数器 CLR A;A清0,准备做比较 LP1:INC R1;指向下一个数据地址 CLR C;0CY,准备作减法 SUBB A,R1;用减法作比较,JNC LP3;若A大,则转LP3 MOV A,R1;A小,则将大数送A中 SJMP LP4;无条件转LP4LP3:ADD A,R1;恢复A中值LP4:DJNZ R2,LP1;计数器减1,不为0,转继续比较 RET;比较完,子程序返回,例4-15 在50H单元存有两位16进制数,编程将它们分别转换成AS
37、CII码,并存入51H、52H单元。解法1:16进制数转换成ASCII码的过程可采用子程序。子程序名称:HASC 功能:把低4位16进制数转换成ASCII码(采用查表法)入口条件:A中存有待转换的16进制数;出口条件:转换后的ASCII码在A中。由于一个字节单元中有两位16进制数,而子程序的功能是一次只转换一位16进制数,所以50H单元中的两位16进制数要拆开、转换两次,因此,主程序需两次调用子程序,才能完成一个字节的16进制数向ASCII码的转换。编写主程序和子程序如下:主程序:ORG 2100H MOV SP,#3FH;设堆栈指针 MOV A,50H;取待转换的数送A中 ACALL HAS
38、C;第一次调用转换子程序,,MOV 51H,A;存转换结果 MOV A,50H;重新取待转换的数 SWAP A;高4位交换到低4位上,准备转换高4位 ACALL HASC;再次调用子程序,转换高4位 MOV 52H,A;存转换结果 END;结束 子程序:ORG 2500HHASC:ANL A,#0FH;只保留低4位,高4位清0 ADD A,#01H;查表位置调整 MOVC A,A+PC;查表取ASCII码送A中 RET;子程序返回TAB:DB 30H,31H,32H,33H,34H,35H,36H,37H DB 38H,39H,41H,42H,43H,44H,45H,46H,子程序在此采用的是
39、查表法,查表法只需把转换结果按序编成表连续存放在ROM中,用查表指令即可实现转换,查表法编程方便、且程序量小。16进制数转换成ASCII码,也可以采用计算法,计算法需判断16进制数是09、还是AF,以确定转换时是+30H、还是+37H,如果要求转换的不是某个单元的两位16进制数,而是一组数据,数据块的长度在R2中,数据块首地址在R0中,转换结果存放的首地址在R1中,则子程序不变,只修改主程序即可。由于数据块长度已知,源操作数首地址、目的操作数首地址已知,主程序可编成循环程序,故对上述主程序修改如下:,ORG 2100H MOV SP,#3FH;设堆栈指针LOOP:MOV A,R0;取待转换的数
40、送A ACALL HASC;调用转换子程序,MOV R1,A;存转换结果 INC R1;修改目的地址 MOV A,R0;重新取待转换的数 SWAP A;高4位交换到低4位上,准备转换高4位 ACALL HASC;再次调用子程序,转换高4位 MOV R1,A;存转换结果 INC R0;修改源操作数地址 INC R1;修改目的操作数地址 DJNZ R2,LOOP;一组数据未转换完,继续 END;转换完,结束,解法2:16进制数转换成ASCII码的过程仍采用子程序。子程序名称和功能同解法1,与解法1不同的是采用堆栈来传递参数。对应的主程序和子程序如下:主程序:ORG 2100H MOV SP,#3F
41、H;设堆栈指针 PUSH 50H;把50H单元内的数压入堆栈 ACALL HASC;调用转换子程序 POP 51H;把已转换的低半字节的ASCII码弹入51H单元 MOV A,50H;重取数送A SWAP A;准备处理高半字节的16进制数 PUSH ACC;参数进栈 ACALL HASC;再次调用子程序 POP 52H;把已转换的高半字节的ASCII码弹入52H单元 SJMP$,子程序:ORG 2500HHASC:DEC SP;修改SP指针到参数位置 DEC SP POP ACC;弹出参数到A中 ANL A,#0FH;只保留低4位 ADD A,#07;修正查表位置 MOVC A,A+PC;查表
42、,取表中的ASCII码送A PUSH ACC;把结果压入堆栈 INC SP;修改SP指针到断点位置 INC SP RET;子程序返回TAB:DB 30H,31H,32H,33H,34H,35H,36H,37H,38H DB 39H,41H,42H,43H,44H,45H,46H,本例中堆栈的操作示意图见图4-10。当主程序第一次执行PUSH 50H时,把50H中的内容压入40H单元内。执行ACALL HASC指令后,则主程序的断点地址高低位分别压入41H、42H单元。进入子程序后,二次执行DEC SP,则把堆栈指针修正到40H。此时执行POP ACC则把40H中的数据(即50H单元内容)弹入到
43、ACC中。当查完表以后,执行PUSH ACC,则已转的ASCII码值压入堆栈的40H单元,第二次执行INC SP,则SP变为42H,此时执行RET指令,则恰好把原断点内容又送回PC,SP又指向40H,所以返回主程序后执行 POP 51H,正好把40H的内容弹出到51H。第二次调用过程类似,不再赘述。,435 运算类程序例4-16 两个8位有符号数加法,和超过8位。编程说明:在计算机中,有符号数一律用补码表示,两个有符号数的加法,实际上是两个数补码相加,由于和超过8位,因此,和就是一个16位符号数,其符号位在16位数的最高位。在进行这样的加法运算时,应先将8位数符号扩展成16位,然后再相加。符号
44、扩展的原则:若是8位正数,则高8位扩展为00H;若是8位负数,则高8位扩展为FFH。经过符号扩展之后,再按双字节相加,则可以得到正确的结果。编程时,寄存器R2和R3作两个加数的高8位,并先令其为全0,即先假定两个加数为正数,然后判别符号位,根据符号位再决定是否将其高8位改为FFH。子程序入口:(R0)=存放加数的首地址,(R1)=存放和的首地址工作寄存器:R2作加数的高8位,R3作另一个加数的高8位,SBADD:MOV R2,#00H;高8位先设0 MOV R3,#00H MOV A,R0;取出第一个加数 JNB ACC.7,N1;若是正数,则转N1 MOV R2,#0FFH;若是负数,高8位
45、送全1N1:INC R0;修改R0指针 MOV B,R0;取第二个加数到B JNB B.7,N2;若是正数,则转N2 MOV R3,#0FFH;是负数,高8位送全1N2:ADD A,B;低8位相加 MOV R1,A;存和的低8位 INC R1;修改R1指针 MOV A,R2;取一个加数的高8位送A ADDC A,R3;高8位相加 MOV R1,A;存和的高8位 RET在调用该子程序时,只需把加数及和的地址置入R0和R1,就可以调用这个子程序。,例4-17 两个8位带符号数的乘法程序。编程说明:MCS-51的乘法指令是对两个无符号数求积,若是带符号数相乘,应作如下处理:(1)保存被乘数和乘数的符
46、号,并由此决定乘积的符号。决定积的符号时可使用位运算指令进行异或操作通过位的与、或运算来完成。(2)被乘数或乘数均取绝对值相乘,最后,再根据积的符号,冠以正号或者负号。正数的绝对值是其原码本身,负数的绝对值是通过求补码来实现的。(3)若积为负数,还应把整个乘积求补,变成负数的补码。子程序入口:(R0)=被乘数,(R1)=乘数出口:(R3)=积的高8位,(R2)=积的低8位程序流程图如图4-11所示。,通过上述编程实例,介绍了汇编语言程序设计的各种情况。从中可以看出,程序设计主要涉及两个方面的问题:一是算法,或者说程序的流程图;二是工作单元的安排。在以上例子中,8个工作寄存器已够用,有时也会出现
47、不够用的情况,特别是可以用于间接寻址的寄存器只有R0和R1,很容易不够用,这时,可通过设置RS1、RS0,以选择不同的工作寄存器组,这一点在使用上应加以注意。,4.4 C51基本语法,1、数据类型2、存储类型3、存储模式4、指针5、再入函数6、中断函数,、数据类型,C51的数据类型分为基本数据类型和组合数据类型,情况与标准C中的数据类型基本相同,但其中char型与short型相同,float型与double型相同,另外,C51中还有专门针对于MCS-51单片机的特殊功能寄存器型和位类型。,一字符型char 有signed char和unsigned char之分,默认为signed char。
48、长度均为单字节,用于存放单字节的数据。对于signed char,它用于定义有符号字节数据,其字节的最高位为符号位,“0”表示正数,“1”表示负数,补码表示,所能表示的数值范围是-128+127;对于unsigned char,它用于定义无符号字节数据或字符,可以存放一个字节的无符号数,其取值范围为0255。unsigned char可以用来存放无符号数,也可以存放西文字符,一个西文字符占一个字节,在计算机内部用ASCII码存放。二int整型 分singed int和unsigned int。默认为signed int。它们的长度均为双字节,用于存放双字节数据。对于signed int,用于存
49、放两字节带符号数,补码表示,数的范畴为-32768+32767。对于unsigned int,用于存放双字节无符号数,数的范围为065535。,三long长整型 分singed long和unsigned long。默认为signed long。长度均为四个字节,用于存放四字节数据。对于signed long,用于存放四字节有符号数,补码表示,数的范畴为-2147483648+2147483647。对于unsigned long,用于存放四字节无符号数,数的范围为04294967295。四float浮点型 float型数据的长度为四个字节,格式符合IEEE-754标准的单精度浮点型数据,包含指
50、数和尾数两部分,最高位为符号位,“1”表示负数,“0”表示正数,其次的8位为阶码,最后的23位为尾数的有效数位,由于尾数的整数部分隐含为“1”,所以尾数的精度为24位。五*指针型 指针型本身就是一个变量,在这个变量中存放的指向另一个数据的地址。这个指针变量要占用一定的内存单元,对不同的处理器其长度不一样,在C51中它的长度一般为13个字节。,六特殊功能寄存器型 这是C51扩充的数据类型,用于访问MCS-51单片机中的特殊功能寄存器数据,它分sfr和sfr16两种类型,其中sfr为字节型特殊功能寄存器类型,占一个内存单元,利用它可以访问MCS-51内部的所有特殊功能寄存器;sfr16为双字节型特