80C51汇编语言程序设计.docx
- 文档编号:26890861
- 上传时间:2023-06-23
- 格式:DOCX
- 页数:56
- 大小:107.26KB
80C51汇编语言程序设计.docx
《80C51汇编语言程序设计.docx》由会员分享,可在线阅读,更多相关《80C51汇编语言程序设计.docx(56页珍藏版)》请在冰豆网上搜索。
80C51汇编语言程序设计
第四章 80C51汇编语言程序设计
一、基本要求
1.了解汇编语言源程序的格式
2.熟悉常用的伪指令
3.掌握简单程序、分支程序、循环程序、查表程序和子程序等结构程序的设计
二、知识点导学
4.3汇编语言程序举例
用汇编语言进行程序设计的过程跟用高级语言进行程序设计很相似。
对于比较复杂的问题可以先根据题目的要求作出流程图,然后再根据流程图来编写程序。
对于比较简单的问题则可以不用流程图而直接编程。
当然,两者的差别还是很大的。
一个很重要的差别就在于用汇编语言编程时,对于数据的存放位置,以及工作单元的安排等都要由编程者自己安排。
而用高级语言编程时,这些问题都是由计算机安排的,编程者则不必过问。
例如MCS-51中有八个工作寄存器R0-R7,而只有R0和R1可以用于变址寻址指令。
因此,编程者就要考虑哪些变量存放在哪个寄存器,以及R0和R1这样可变址寻址的寄存器若不够用又如何处理等等。
这些问题的处理和掌握,将是编程的关键之一。
希望通过实践注意掌握。
这一节中将介绍一些汇编语言设计的实例及一些程序设计的方法。
一、简单程序
简单程序是指一种无分支的直接程序,即从第一条指令开始依次执行每一条指令,直到最后一条,程序就算完毕。
这种程序虽然比较简单,但也能完成一定的功能,并且往往也是构成复杂程序的基础。
例1将一个字节内的两个BCD十进制数拆开并变成相应的ASCII码,存入两个RAM单元。
解:
设两个BCD数已放在内部RAM的20H单元,变换后的ASCII码放在21H和22H单元并让高位十进制BCD数存放在21H单元。
在上一章中曾举例用SWAP指令来将两个BCD数合在一个字节内。
拆字时也可以用SWAP指令,并且借助于半字节交换指令XCHD,就不难完成所规定的功能。
数字0~9的ASCII码为30H~39H。
完成拆字转换只需将一个字节内的两个BCD数拆开放到另两个单元的低4位,并在其高4位赋以0011即可。
为此,可以先用XCHD指令将个位的BCD数和22H单元的低4位交换,在22H单元高4位添上0011完成一次转换。
再用SWAP指令将高4位与低4位交换,并将高4位变为0011,完成第二次转换。
为了减少重复操作和使程序精炼,应先使22H单元清零。
工作寄存器选用R0,这样可便于使用变址寻址指令。
MOVR0,#22H;R0←22H
MOV@R0,#00H;22H清零
MOVA,20H;两个BCD数送A
XCHDA,@R0;低位BCD数送至22H单元
ORL22H,#30H;完成低位转换
SWAPA;高位BCD放至低4位
ORLA,#30H;完成高位转换
MOV21H,A;存数
RET
这种转换也可借用除法指令一次完成。
将一个两位BCD数除以10000B(
),就相当于右移4位而得到商,即在A中留下高位BCD,而余数即低位BCD则进入B寄存器,从而完成拆字,然后再在高位添上0011,就可完成转换。
读者可以自己完成上述程序。
例2将20H单元中8位无符号二进制数转换成三位BCD码,并存放在FIRST(百位)和SECON(十位、个位)两个单元中。
解:
对于没有除法指令的系统,完成这样的转换并不太容易,必须通过连续相减来完成。
这时要先将原数与100相减,够减的次数就是转换后的百位数,然后再与10相减,等等。
而在MCS—51中有除法指令,转换就方便了:
先将原数除以100,得百位数;余数再除以10得十位数;最后的余数就是个位数。
并分别设法存入指定的单元即可。
FIRSTDATA30H
SECONDATA31H
ORG1000H;开始
MOVA,20H;取数
MOVB,#64H;除数为100
DIVAB;确定百位数
MOVFIRST,A;百位数送FIRST
MOVA,B;余数送A
MOVB,#0AH;除数为10
DIVAB;确定十位数
SWAPA;十位数移至高4位
ORLA,B;并入个位数
MOVSECON,A;十位,个位存SECON
RET
END
另外一种除法则是连续除以10:
先除以10余数为个位数,再将余数除以10可得百位数(商)和十位数(余数)。
例316位二进制数求补程序。
设16位二进制数存放在R1R0,求补以后的结果则存放于R3R2。
解:
二进制数的求补可归结为“求反加1”的过程。
求反是容易做到的,因为有CPL指令。
加1则略有问题,因为是16位的加1操作,因此要考虑进位问题。
即不仅最低位要加1,高8位也要加上低位的进位。
还要注意这里的加1不能用INC指令,因为在MCS-51中,这个指令不影响标志。
MOVA,R0;低8位送A
CPLA;取反
ADDA,#1;加1
MOVR2,A;送回
MOVA,R1;高8位送A
XRLA,#7FH;符号位不变,其余位取反
ADDCA,#0;加进位
MOVR3,A;结果送回
RET
以上程序的编写并不困难,但在次序的安排上要有一些考虑:
由于MCS-51中取反指令并不影响标志
,因此可以低位取反后立即加1,然后再高位取反加进位。
若是CPL指令影响标志,则应先16位取反,然后再加1和加进位,所需的数据往复传送数要增多。
读者可以按这种方法写出程序并进行比较。
二、分支程序
分支程序就是条件分支程序,即根据不同的条件,执行不同的程序段。
在编写分支程序时,关键是如何判断分支的条件。
在MCS-51中直接用来判断分支条件的指令不是很多,只有累加器判零指令JZ、JNZ,比较转移指令CJNE等,但它还提供了位条件转移指令如JC、JB等。
把这些指令结合在一起使用,就可以完成各种各样的条件判断,如正负判断、溢出判断、大小判断等。
例4设变量X存放于VAR单元,函数值Y存放在FUNC单元。
试按照下式的要求给Y赋值。
解:
X是有符号数,因此可以根据它的符号位来决定其正负,判别符号位是0还是1则可利用JB或JNB指令。
而判别X是否等于0则可以直接使用累加器判零指令。
把这两种指令结合使用就可以完成本题的要求。
VARDATA30H
FUNCDATA31H
MOVA,VAR;取出X
JZCOMP;X=0则转移到COMP
JNBACC.7,POSI;X>0则转移到POSI
MOVA,#0FFH;X<0则Y=-1
SJMPCOMP
POSI:
MOVA,#1;X>0则Y=1
COMP:
MOVFUNC,A;存函数值
RET
YY
N
N
Y
Y
N
N
(a)(b)
图3.1流程图
这个程序是按照图3.1(a)的流程图编写的。
其特征是先比较判断,然后按比较结果赋值。
这实际是个三分支而归一的流程图,因此,至少要用两个转移指令。
对于初学者很容易犯的一个错误是漏掉了其中的SJMPCOMP语句,因为流程图中没有明显的转移迹象。
但这种理解是错误的,必须注意克服。
这个程序还可以按照图3.1(b)的流程图来编写。
其特征是先赋值,后比较判断,然后修改赋值并结束。
即预先认为VAR<0,先让FUNC=-1。
若比较以后证明VAR确实小于零,则赋值不变,否则就改为+1。
这样就是一个两分支归一的流程,可以少用一次转移指令。
但这时要借用一个寄存器(如R0)来存放结果,因为A不能同时又放X的值,又放中间结果。
MOVA,VAR;取X到A
JZCOMP;X=0则转移
MOVR0,#0FFH;先设X<0,R0=FFH
JBACC.7,NEG;若X<0则转移
MOVR0,#1;X>0,R0=1
NEG:
MOVA,R0
COMP:
MOVFUNC,A;存结果到FUNC
RET
END
例5两个带符号数分别存于ONE和TWO单元,试比较它们的大小,将较大者存入MAX单元中。
两数相等则任存入一个即可。
解:
两个带符号数的比较可将两数相减后的正负和溢出标志结合在一起判断,即:
若X-Y为正,则OV=0,X>Y
OV=1,X 若X-Y为负,则OV=0,X OV=1,X>Y 两个正数相减或者两个负数相减都不会溢出(OV=0),若差为正则X>Y;若差为负则X 正数肯定大于负数,它们的差若为正,是正常运算,无溢出(OV=0);若差为负,则不正常,一定溢出(OV=1)。 负数肯定小于正数,它们的差若为负,是正常运算,无溢出(OV=0);若差为正,则不正常,一定溢出(OV=1)。 由此可以得到程序的流程图3.2。 CLRC MOVA,ONE;取X到A SUBBA,TWO;X-Y JZXMAX;X=Y JBACC.7,NEG;X-Y为负转NEG JBOV,YMAX;X-Y>0,OV=1,Y>X Y N N Y Y N N 图3.2例5的流程图 SJMPXMAX;X-Y>0,OV=0,X>Y NEG: JBOV,XMAX;X-Y<0,OV=1,X>Y YMAX: MOVA,TWO;Y>X SJMPRMAX XMAX: MOVA,ONE;X>Y RMAX: MOVMAX,A;送较大值至MAX RET ONEDATA30H TWODATA31H MAXDATA32H END 例6128分支程序。 根据R3的值(00H-7FH),分支到128个不同的分支入口。 解: 多分支程序根据分支数目的不同,可由不同的设计方法。 若分支的入口在2kB范围内分布,可使用AJMP指令,参考程序如下: MOVA,R3 RLA;A←A 2 MOVDPTR,#BRTAB JMP@A+DPTR BRTAB: AJMPROUT00 AJMPROUT01 : AJMPROUT127 END 可以看出,从BRTAB开始存放着一系列AJMP指令。 程序的工作是两次转移的方式: 先根据R3的值,用JMP指令转移到从BRTAB开始的某一条AJMP指令,然后再用这条AJMP指令转移到相应的分支入口ROUTnn。 当然,各个分支入口地址ROUTnn要通过伪指令或其它方式来定义。 由于AJMP是双字节指令,因此提前使偏移量A乘以2,以便转向正确的位置。 每个分支的入口地址(ROUT00-ROUT127)必须和其相应的AJMP指令在同一个2k存贮区内。 也就是说,分支入口地址的安排仍有相当的限制。 如改用长转移LJMP指令,则分支入口就可以在64kB的范围内任意安排,但程序要作相应的修改。 请读者自行设计。 三、循环程序 循环程序也是一种程序的组织形式。 在程序执行时,往往同样的一组操作需要重复许多次,当然可以重复使用同样的指令来完成,但若使用循环程序,重复执行同一条指令许多次来完成重复操作,就大大简化了程序。 例如要做1到100的加法,没有必要去写100条加法指令,而可以只写一条加法指令并使之执行100次,每次执行时操作数也作相应的变化,同样能完成原来规定的操作。 循环程序一般由四部分组成: 1.1. 置循环初值,即确定循环开始时的状态,如使工作单元请0,计数器置初值等。 2.2. 循环体部分,即要求重复执行的部分。 这部分程序应特别注意,因为它要重复执行许多次(如100次),因此,若能少写一条指令,实际上就是少执行100条指令。 反之亦然。 3.3. 循环修改部分,循环程序必须在一定的条件下结束,否则就要形成死循环。 因此,每循环一次就要注意是否需要修改达到循环结束的条件,若需要修改时,一定不要忘记,以便在一定情况下能结束循环。 4.4. 循环控制部分,根据循环结束条件,判断是否结束循环。 以上四部分可以有两种组织形式,如图3.3所示。 为了构成循环程序,DJNZ指令是很有用的,特别是在根据计数器的值决定循环是否结束时可以直接使用。 但也可以根据其它条件来判断循环结束条件。 例7从BLOCK单元开始存放一组无符号数,一般称为一个数据块。 数据块长度放在LEN单元,编写一求和程序,将和存入SUM单元,设和不超过8位二进制数。 解: 这是一个典型的循环程序例子。 在置初值时,将数据块长度置入一个工作寄存器,将数据块首地址送入另一个工作寄存器,一般称它为数据块地址指针。 每做一次加法之后,修改地址指针,以便取出下一个数来相加。 并且使计数器减1,至计数器减到0时,求和结束,把和存入SUM即可。 参考程序如下: N Y 退出循环 Y N 图3.3循环程序组织方式 LENDATA20H SUMDATA21H BLOCKDATA22H CLRA;清累加器 MOVR2,LEN;数据块长度送R2 MOVR1,#BLOCK;数据块首地址送R1 LOOP: ADDA,@R1;循环做加法 INCR1;修改地址指针 DJNZR2,LOOP;修改计数器并判断 MOVSUM,A;存和 RET 以上程序在计数器初值不为零时是没有问题的。 但若是数据块的长度有可能为零,则将出现问题,当R2初值为零,减1之后将为FFH,故要做256次加法之后才会停止,显然和原意不符。 若考虑到这种情况,则可按图3.3(b)的方式来编写程序: 在做加法之前,先判断一次R2的初值是否为零。 整个程序仍基本套用原来的形式: CLRA MOVR2,LEN MOVR1,#BLOCK INCR2 SJMPCHECK LOOP: ADDA,@R1 INCR1 CHECK: DJNZR2,LOOP MOVSUM,A RET 例8多字节加法程序。 有10组三字节的被加数和加数,分别存在两个数据块中,首地址分别存于寄存器R0和R1中,求这10组数的10组和,各组的和仍送回以R0为指针的单元。 解: 单字节无符号数和的最大值不能超过255,在实际应用时显然是很不够的,因此常用若干个字节来表示一个数.若是双字节无符号数,则最大值可为65535,若用三字节表示,则最大值可为16777215,这在很多场合已是足够了。 这个问题要用双重循环程序来完成。 因为多字节加法本身就需要用循环来完成,而10组多字节数的求和又需通过10次循环来计算,这样就出现了循环嵌套的情况。 这个题目算完以后,原有的被加数都被冲掉,由各组的和来代替。 编程时,设两个三字节数的和仍为三字节。 由于是多字节相加,所以相加时要用ADDC指令。 MOVTEMP,R0;保留指针的起始值 MOVTEMP+1,R1 MOVR3,#10;R3存几组数相加 LOOP: MOVR2,#3;R2存字节数 CLRC;清 准备相加 LOOP1: MOVA,@R0;取被加数 ADDCA,@R1;加一个字节 MOV@R0,A;存部分和 INCR0;指向下一字节 INCR1 DJNZR2,LOOP1;组内循环 DJNZR3,LOOP;10组数循环 MOVR0,TEMP;恢复指针到起始值 MOVR1,TEMP+1 RET TEMPDATA20H END 程序执行以后,10组和存于原来10个被加数的位置,为便于调用,将指针R0和R1恢复成起始的位置。 以上程序作些修改以后就可用来求若干组多字节数的总和,这个问题作为习题留给读者。 四、查表程序 在很多情况下,通过查表比通过计算解决问题要简便得多。 在编程序时也有类似的情况: 有时通过查表程序比通过运算程序要简单得多,编程也较为容易。 在MCS—51中查表时的数据表格是存放在程序ROM而不是数据RAM,在编程时可以很方便地通过DB伪指令把表格的内容存入ROM。 用于查表的指令有两条: MOVCA,@A+DPTR MOVCA,@A+PC 使用DPTR作为基地址查表比较简单,可通过三步操作来完成: (1) (1) 将所查表格的首地址存入DPTR数据指针寄存器; (2) (2) 将所查表的项数(即在表中位置是第几项)送到累加器A; (3)(3) 执行查表指令MOVCA,@A+DPTR,进行读数,查表的结果送回累加器A。 若用PC内容作为基地址来查表,所需操作有所不同,但也可以分为三步: (1) (1) 将所查表的项数(即在表中是第几项)送到累加器A,在MOVCA,@A+PC指令之前先写上一条ADDA,#data指令,data的值待定; (2) (2) 计算MOVCA,@A+PC指令执行后的地址到所查表的首地址之间的距离,即算出这两个地址之间其它指令所占的字节数,把这个结果作为A的调整量取代加法指令中的data值; (3)执行查表指令MOVCA,@A+PC进行查表,查表结果送到累加器A。 在用DPTR作为基址进行查表时,可以通过传送指令让DPTR的值和表的首地址一致。 但在用PC作为基址时,却不大可能做到这一点,因为PC的值是由MOVCA,@A+PC指令所在的地址加1以后的值所决定的。 因此,必须要作上面步骤中规定的地址调整。 用PC作为基址虽然稍微麻烦一些,但可以不占用DPTR寄存器,所以仍是常用的一种查表方法。 例9将十六进制数转换为ASCII码。 设十六进制数存放在R0寄存器的低4位,转换后的ASCII码仍送回R0寄存器。 解: 作为对比,这个问题用两种方法来解,一种是计算求解,一种是查表求解。 0~9的ASCII码为30H~39H,而A~F的ASCII码为41H~46H。 计算求解的思路是当R0≤9时,加上30H就变成相应的ASCII码。 若R0>9,则加上37H才能完成变换。 以上思路可以用分支程序来实现。 下面介绍的则是不用分支的一种解法。 先让R0加上90H,并作十进调整,然后再用ADDC指令使R0再加上40H,并作十进制调整,所得结果就是转换后的结果。 当原来的R0≤9时,以上运算不影响十进制个位的值,而十位上是9+4=13,1留在 之中,在十位上只留下3,即相当于加30H; 当原来的R0>9时,个位十进制数就要调整,结果为R0-10(加6调整和减10等价),同时有半进位加到十位上,使十位为9+1=10,即调整后十为0, 为1,下一次再用ADDC加上40H时,实际就是加上了41H,从而完成了十六进制到ASCII码的转换。 MOVA,R0;取转换值到A ANLA,#0FH;屏蔽高4位 ADDA,#90H DAA ADDCA,#40H DAA MOVR0,A;转换结果送回R0 RET 若使用查表程序,则整个程序更为简单,也很容易理解: MOVA,R0 ANLA,#0FH ADDA,#2;地址调整 MOVCA,@A+PC MOVR0,A RET ASCTAB: DB‘0,1,2,3,4’ DB‘5,6,7,8,9’ DB‘A,B,C,D,E,F’ END 在这个查表程序中,从MOVC指令到表的首地址ASCTAB之间只有两条一字节指令,所以PC的调整量为2。 例10一组十六进制数转换为ASCII码。 每个字节内存放两个十六进制数。 十六进制数据块首地址存于R0寄存器,存放ASCII码区域的首地址存于R1寄存器,数据块长度存于R2寄存器。 程序执行后R0和R1仍应指向原来的位置。 解: 由于每个字节存放两个十六进制数,因此要拆开转换两次,每次都通过查表求相应的ASCII码。 由于两次查表所用的MOVC指令在程序的不同位置,因此,两次对PC地址调整的值不同的。 可以先将整个程序写完,两条加法指令中的加数待程序写完后再填入。 MOVTEMP,R0;暂存指针值 MOVTEMP+1,R1 LOOP: MOVA,@R0;取二个十六进制数 ANLA,#0FH;保留低4位 ADDA,#19;第一次地址调整 MOVCA,@A+PC;第一次查表 MOV@R1,A;存第一次转换结果 INCR1 MOVA,@R0;重新取出被转换数 SWAPA;准备处理高4位 ANLA,#0FH ADDA,#10;第二次地址调整 MOVCA,@A+PC;第二次查表 MOV@R1,A;存第二次转换结果 INCR1;修改指针 INCR0 DJNZR2,LOOP;R2≠0再循环 MOVR0,TEMP;恢复指针原值 MOVR1,TEMP+1 RET ASCTAB: DB‘0,1,2,3,4’ DB‘5,6,7,8,9’ DB‘A,B,C,D,E,F’ TEMP: DATA20H END 利用查表程序还可以完成BCD—七段码的转换,从而取代硬件七段译码电路。 查表程序本身并无复杂之处,需要注意的是七段码的取值。 因为七段发光显示器有共阳极和共阴极两种,共阳极是低电平为有效输入,共阴极是高电平为有效输入,因此不同的器件会有不同的码值。 另外管脚信号与码位的对应关系也会影响码值,即管脚可以由高到低排列(7~1),也可以由低到高排列(1~7)。 下面一组0~9的七段代码是针对共阳极显示管,管脚信号由高到低排列,例如对于0的代码为: 01000000(0的七段码) 即40H。 这组10个代码为: 40H,79H,24H,30H,19H,12H,02H,78H,00H,18H。 具体程序参见第七章。 五、子程序 在用汇编语言编写程序时,也应考虑恰当地使用子程序,从而使整个程序的结构清楚,阅读和理解都方便。 使用子程序还可以减少源程序和目标程序的长度。 在多次调用同样的程序段时,采用子程序就不必每次重复书写同样的指令,而只需书写一次,翻译成目标码时,也只需翻译一次。 当然从程序的执行来看,每调用一次子程序都要附加保护断点、进栈、出栈等操作,增加程序的执行时间。 但一般来说,付出这些代价总是值得的。 在汇编语言源程序中使用子程序,要注意两个问题,即参数传递和现场保护问题。 在调用汇编语言子程序时会遇到一个参数如何传递的问题。 在调用高级语言子程序时参数的传递是很方便的。 通过调用语句中的实参数以及子程序语句中的形式参数之间的对应,很容易完成参数的往返传递。 而用指令调用汇编语言子程序并不附带任何的参数,参数的互相传递靠编程者自己安排。 其实质就是如何安排数据的存放以及工作单元的选择问题。 参数传递一般可采用以下方法: (1)传递数据。 将数据通过工作寄存器R0-R7或者累加器来传送。 即在调用子程序之前把数据送入寄存器或者累加器。 调用以后就用这些寄存器或者累加器中的数据来进行操作。 子程序执行以后,结果仍由寄存器或累加器送回。 (2)传递地址。 数据存放在数据寄存器中,参数传递时只通过R0、R1、DPTR传递数据所存放的
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 80 C51 汇编语言 程序设计