560 likes | 952 Views
第 4 章 汇编语言程序设计. 4.1 伪指令 4.1.1 为什么要使用伪指令 4.1.2 MCS - 51 单片机的伪指令 4.2 汇编语言程序设计 4.2.1 顺序结构 4.2.2 分支程序 4.2.3 循环结构 4.2.4 子程序设计和子程序调用. 4.1.1 为什么要使用伪指令.
E N D
第4章 汇编语言程序设计 4.1 伪指令 4.1.1 为什么要使用伪指令 4.1.2 MCS-51单片机的伪指令 4.2 汇编语言程序设计 4.2.1 顺序结构 4.2.2 分支程序 4.2.3 循环结构 4.2.4 子程序设计和子程序调用
4.1.1 为什么要使用伪指令 ◆ 存储空间分配问题:在利用高级语言(如C语言)编程时,用户无需考虑存储空间的分配,因为操作系统和软件编译器帮助用户解决了这些问题,使得存储空间分配对用户来说是透明的,但是对MCS-51单片机而言,存储空间分配是用户必须解决的一个问题,即存储空间分配对用户不再是透明的。但是在指令系统一章却没有介绍任何指令能够实现对存储空间的分配。
为什么要使用伪指令(续) ◆ 变量和常量的定义:在高级语言(C语言)编程中,首先介绍了常量和变量的定义(Define、int,char等),但是在指令系统一章却没有介绍任何指令能够实现对常量和变量的定义。 ◆ 数组的定义:在高级语言(C语言)编程中,可以定义数组,数组在编程中被大量使用,但是在指令系统一章却没有介绍任何指令能够实现对数组的定义。
4.1.2 MCS-51单片机的伪指令 伪指令不是真正的指令,无对应的机器码,在汇编时不产生可供CPU直接执行的机器码(即目标程序),只是用来对汇编过程进行某种控制。例如规定汇编生成的目标程序在程序存储器中的存放区域,给源程序中的符号或标号赋值以及指示汇编的结束等。MCS-51单片机常用伪指令有下面几种。
1 ORG(汇编起始命令) ORG伪指令称为汇编起始命令,常用于汇编语言源程序或数据块开头。 【标号:】 ORG 16位地址或标号 ORG伪指令的功能是将该伪指令后的程序机器码按照ORG伪指令提供的16位地址或标号存入相应的程序存储器中。即规定目标程序在程序存储器中存放的起始地址 【注】在用户程序中,ORG伪指令仅需要用于复位和中断服务入口地址、主程序入口地址设置,其他子程序不必使用ORG伪指令。
2 END(汇编结束命令) END伪指令的格式: 【标号:】 END 在END伪指令格式中,标号段为任选项,通常省略。 END伪指令的功能是通知汇编程序结束汇编。在END之后所有的汇编指令均不予以汇编成机器码。 【注】END伪指令必须放在所有用户程序的后面,不是放在主程序的后面。
3 EQU(赋值命令) EQU伪指令的格式: 字符名称 EQU 数据或汇编符号 在EQU伪指令的格式中,注意字符名称和标号的区别(标号后面有冒号,字符名称后没有冒号),经汇编编译后,字符名称和数据或汇编符号完全等价,在用户程序中,字符名称可以代替数据或汇编符号,在程序中字符名称可以作为数据(常量)、地址(变量)、位地址(一般情况下不使用EQU定义位变量,而是采用BIT定义位变量)。其中常量和变量可以是8位的,也可以是16位的。
EQU伪指令的功能是给常量赋值或给变量赋地址。EQU伪指令的功能是给常量赋值或给变量赋地址。 那么如何区分此时字符名称是常量还是变量呢?现举例说明。 例:XX EQU 30 ………. Main: ………… MOV A, #XX ;将XX作为常量使用 MOV A, XX ;将XX作为变量使用,XX表示片内30H单元地址 MOV A, 30H ;此指令和上面一条指令等效
4 DATA(数据地址赋值命令) DATA伪指令的格式为: 字符名称 DATA 表达式 DATA伪指令的功能与EQU类似,这两条伪指令的主要区别在于EQU定义的字符名称必须先定义后使用,而DATA定义的字符名可以先使用后定义。 【注】根据C语言和其他编程语言的思维定势,对变量都是采用“先定义、后使用”原则,所以DATA伪指令很少被使用,都是采用EQU伪指令来定义变量和常量。
5 DB(定义字节命令) DB伪指令的格式为: 【标号:】 DB 项或项表 在DB伪指令的格式中,标号为任选项,项或项表可以是一个8位二进制数据、用逗号分开的一串8位二进制数据、括在单引号中的ASCII字符串。 DB伪指令的功能是将汇编程序从标号对应的程序存储器地址(若没有标号,则从该DB伪指令的汇编地址)开始,存入DB伪指令后的项或项表数据,类似于C语言中数组的定义。
例:TABLE: DB 0A3H ;项或项表为一个8位二进制数据 DB 26H, 03H ;项或项表为用逗号分开的一串8位二进制数据 DB ‘ABC’ ;项或项表为括在单引号中的ASCII字符串 ┇ 经汇编后从TABLE对应的程序存储器地址开始依次存放 A3H,26H,03H,41H,42H,43H,其中41H,42H,43H分别为A,B,C的ASCII码。 【注】实际应用中当一串8位二进制数据超过一行后,下一行必须以DB开始,对ASCII字符串可以采用‘abc’或‘a’, ‘b’ ,‘c’
6 DW(定义字命令) DW伪指令的格式为: 【标号:】 DW 16位数据项或项表 DW伪指令的功能是把DW后的16位数据项或项表从当前地址连续存放。16位数据项或项表采用高8位在前,低8位后在后原则存放。
例:TABLE:DW 1234H,23H,10H 假设经汇编后,TABLE对应的程序存储器地址单元为1000H,那么从1000H单元开始依次存放如下: (1000H)=12H (1001H)=34H (1002H)=00H (1003H)=23H (1004H)=00H (1005H)=10H 采用DW伪指令,两个逗号之间的数据表示16位二进制数,即23H是表示0023H,所以(1002H)=00H,而不是(1002H)=23H 【注】因为MCS-51单片机为8位单片机,所以DB指令经常被使用,而DW指令并不常用,常用于带字符的液晶显示程序。与DB伪指令一样,当16位数据项或项表超过一行后,下一行同样以DW开始。
7 DS 定义存储空间命令 DS伪指令的格式为: 【标号:】 DS 表达式 DS伪指令的功能是指在汇编时,从指定地址开始保留DS之后表达式的值所规定的存储单元以备后用。 例: TABLE: DS 08H DB 30H,8AH 假设经汇编后,TABLE对应的程序存储器地址单元为1000H,从1000H保留8个单元,然后从1008H按DB命令给内存赋值,即(1008H)=30H,(1009H)=8AH 【注】在单片机编程中,极少需要预先开辟存储空间,所以DS伪指令很少被使用
8 BIT ( 位地址符号命令) BIT伪指令的格式为: 字符名 BIT 位地址 BIT伪指令的功能是把BIT后的位地址值赋给字符名。其中字符名不是标号,其后没有冒号。 例如: LED1 BIT P1.0 FLAG BIT 02H 汇编后,P1口第0位的位地址90H就赋给了LED1,而位寻址空间02H定义为FLAG。 【注】与EQU指令功能类似,两种区别在于EQU伪指令是对字节常量或变量的定义,BIT伪指令是对位变量的定义。注意BIT伪指令不能定义位常量,因为MCS-51单片机没有提供位常量操作指令,只提供位变量操作指令。
4.2 汇编语言程序设计 4.2.1 顺序结构 4.2.2 分支程序 4.2.3 循环结构 4.2.4 子程序设计和子程序调用
4.2.1 顺序程序 顺序结构是最简单的一种程序结构,这种程序中即无分支,也无循环,也不调用子程序,从第一条指令开始按顺序一条一条执行指令,直到结束。典型的顺序结构程序流程如图4.1所示。 图4.1 顺序结构程序流程图
例4-1:试编制单字节的乘法程序,假设被乘数放在R0,乘数放在R1中,结果高8位放在30H,低8位放在31H。例4-1:试编制单字节的乘法程序,假设被乘数放在R0,乘数放在R1中,结果高8位放在30H,低8位放在31H。 • 【编程思路】 • 单片机提供了乘法指令(MUL AB),该指令要求乘数和被乘数必须存放在累加器A和寄存器B中,同时两数相乘的结果也存放在累加器A和寄存器B中。根据题目要求和单片机乘法指令的定义,程序应包括以下部分: • ◆ 首先必须将乘数(R0)和被乘数(R1)分别取出存放到累加器A和寄存器B中, • ◆ 利用乘法指令将存放在A和B中的数相乘 • ◆ 相乘后的结果,按题意要求进行存储。 • ◆ 利用SJMP $ 指令实现程序的死等待,即单片机执行该语句后,PC指针将不再改变,一直执行该语句,主要为用户在仿真环境下调试程序方便。 • ◆ 死等待指令常用下面两种方式:SJMP $ 和 HERE: LJMP HERE
【例4-1源程序】 ORG 0000H LJMP MAIN ORG 0030H MAIN: MOV A,R0 MOV B,R1 MUL AB MOV 31H,A MOV 30H,B SJMP $
例4-2:将单字节十六进制无符号数转换成非压缩的BCD码,设被转换数放在R0,BCD码百位放在30H,十位放在31H,个位放在32H中。例4-2:将单字节十六进制无符号数转换成非压缩的BCD码,设被转换数放在R0,BCD码百位放在30H,十位放在31H,个位放在32H中。 【编程思路】 ◆ 非压缩的BCD码是指一个字节中低四位存放一个BCD码,高四位为零,压缩BCD码是指一个字节存放两个BCD码,即高四位和低四位分别存放一个BCD码。 ◆ 单字节十六进制无符号数最大为255,即其BCD码需要三位来表示,即百位、十位、个位。 ◆ 本题编程的关键在于需要将单字节十六进制无符号数的百位、十位、个位依次求出,这里采用将单字节十六进制除以100得到百位、余数再除以10得到十位,个位即是除以10后的余数。 ◆ 按照题意将百位、十位、个位依次存储在题目要求的存储单元。
【例4-2源程序】 ORG 0000H LJMP MAIN ORG 0030H MAIN: MOV B,#64H MOV A,R0 DIV AB ; 将单字节十六进制除以100 MOV 30H,A XCH A,B MOV B,#0AH ; 将单字节十六进制除以10 DIV AB MOV 31H,A MOV 32H,B SJMP $
例4-3:试编制双字加法程序,设被加数的高8位放在30H中,低8位放在31H中,加数的高8位放在32H,低8位放在33H中。加法结果的高8位放在34H中,低8位放在35H中。例4-3:试编制双字加法程序,设被加数的高8位放在30H中,低8位放在31H中,加数的高8位放在32H,低8位放在33H中。加法结果的高8位放在34H中,低8位放在35H中。 【编程思路】 ◆ 多字节加法程序其算法都是将最低8位相加,然后依次将次低8位相加,但是必须考虑进位,一直到最高8位相加完。 ◆ 对于双字加法程序同样如此,首先将两个被加数的低8位相加,还是在将高8位进行相加,必须考虑加法操作的进位。 ◆ 加法操作其目的操作数只能为累加器ACC,故首先必须将两个加数中的一个预先存放到累加器ACC中。注意在指令中利用A来表示累加器ACC
【例4-3源程序】 ORG 0000H LJMP MAIN ORG 0030H MAIN: CLR C ;利用ADDC指令前一定要考虑CY位 MOV A,31H ;进行加法运算一定要将其中一个加数 存放到A中 ADD A,33H MOV 35H,A ;低8位相加后存放在35H单元 MOV A,30H ADDC A,32H ;高位相加必须考虑低位相加后的进位 MOV 34H,A ;高8位相加后存放在34H单元 SJMP $
例4-4:用查表法编一子程序,将R3中的BCD码转换成 ASCII码例4-4:用查表法编一子程序,将R3中的BCD码转换成 ASCII码 【编程思路】 ◆ 本例目的要求掌握两个知识点:如何造表、如何访问表 ◆ MCS-51单片机造表都是使用伪指令DB或DW实现,8位数据采用DB,16位数据采用DW,造表的时候一定要给表起个名字,本例直接用TABLE作为表名。本例中,TABLE表的内容为0~9的ASCII码 ◆ 由于表数据存储在程序存储器中,访问表实际上就是对程序存储器的读操作,对程序存储器的读操作一般都使用MOVC A,@A+DPTR,即通过改变A+DPTR的值来实现对程序存储器不同地址空间的访问。
【例4-4源程序】 MAIN: MOV A,R3 ;待转换的数送A MOV DPTR,# TABLE ;表首地址送DPTR MOVC A,@A+DPTR ;查ASCII码表 MOV R3,A ;查表结果送R3 RET TABLE: DB 30H,31H,32H,33H,34H DB 35H,36H,37H,38H,39H 【例4-4源程序】 MAIN: MOV A, R3 ;待转换的数送A ADD A, #2 MOVC A,@A+PC ;查ASCII码表 MOV R3,A ;查表结果送R3 RET TABLE: DB 30H,31H,32H,33H,34H DB 35H,36H,37H,38H,39H
4.2.2 分支程序 从编程中,经常会遇到根据不同的条件,程序作不同的处理,分支程序用于CPU执行指令过程中,对条件进行分析和判断,决定程序的走向,分支程序的结构如图4.2所示。 图4.2 分支程序结构图 通常用条件转移指令来实现分支结构,在MCS-51单片机指令系统中,提供了条件转移类指令主要有:JZ、JNZ(判断条件为操作数A是否为0),CJNE(判断两个操作数是否相等),DJNZ(判断操作数减1是否为零),JC、JNC、JBC(判断CY是否为0),JB、JNB(判断操作位是否为0)
例4-5:试编写比较三个数的大小程序,设待比较的三个数放在30H、31H、32H,比较后将三个数中最大的数放入R0例4-5:试编写比较三个数的大小程序,设待比较的三个数放在30H、31H、32H,比较后将三个数中最大的数放入R0 【编程思路】 1 依据题意需要找到三个数中的最大值,其程序算法将两个数比较,并将两个数中的最大数赋值给变量MAX,然后将MAX和第三个数比较,将最大值再赋值给MAX。 2 在本例编程中,将累加器ACC作为存储最大值MAX 3 比较两个数的大小,MCS-51单片机指令系统提供以下两种方式。 ◆ 利用SUBB指令,将两个数相减后,根据CY标志判断两个数的大小 ◆ 利用CJNE指令,首先判断两个数是否相等,然后根据CY标志判断两个数的大小 4 根据SUBB指令和CJNE指令的不同,分别给出两种方式下的源程序
例4-5方式1源程序 ORG 0000H LJMP MAIN ORG 0030H MAIN: MOV A,30H CLR C SUBB A,31H JC NEXT; 31H中的数据大 MOV A,30H SUBB A,32H;取大的和32H的数据比 JC NEXT1 ; 32H中的数据大 MOV R0,30H; 30H中的数据大 LJMP END1 NEXT1: MOV R0,32H LJMP END1 NEXT: MOV A,31H SUBB A,32H;取大的和32H的数据比 JC NEXT1; 32H中的数据大 MOV R0,31H END1: SJMP $
【例4-5方式2源程序】 ORG 0000H LJMP MAIN ORG 0030H MAIN: CLR C MOV A,30H CJNE A,31H,NEXT ;30H和31H比较大小 NEXT: JC NEXT1 ;30H小于31H,跳NEXT1 MOV A,30H ;将30H作为最大值保存 LJMP COMP2 NEXT1: MOV A,31H ;第一次比较结束,最大值存储在A中 COMP2: CJNE A,32H,NEXT2 ;30H和31H中最大值和32H比较大小 NEXT2: JC NEXT3 LJMP END1 NEXT3: MOV A,32H ;第二次比较结束,最大值存储在A中 END1: MOV R0,A ;依题意将最大值存在R0中 SJMP $
例4-6: 设变量X存放在30H单元中, 函数Y存放在31H单元。 试编程实现下式要求给Y赋值的程序。 【编程思路】 1 由于X为有符号数, 因此可以根据它的符号位来决定其正负。即根据X的最高位是0 还是1决定X的正负, 可利用JB或JNB指令。 2 而判别X是否为 0,可用CJNE指令或累加器判零指令JZ
【例4-6源程序】 ORG 0000H LJMP MAIN ORG 0030H MAIN:MOV A,30H CJNE A,#00H,NEXT ;将X和0进行比较 MOV 31H,#00H ;当X=0时,将Y赋值为0 LJMP END1 NEXT: JNB ACC.7,NEXT1 ;当X不为0时,判断X的正负 MOV 31H,#0FFH ;若X<0 则A=-1 LJMP END1 NEXT1: MOV 31H,#01H ;若X>0 则A=1 END1: SJMP $
例4-7:求双字节有符号数的补码,设字节高8位放在30H,低8位放在31H,将补码的高8位放在32H,低8位放在33H。例4-7:求双字节有符号数的补码,设字节高8位放在30H,低8位放在31H,将补码的高8位放在32H,低8位放在33H。 【编程思路】 1 补码运算的算法思路为对有符号数“取反加1” 2 对MCS-51单片机指令系统而言,提供取反指令CPL,加1指令则采用加法指令即可。 3 由于51单片机只能进行字节运算,对于双字节运算则 只能采用低8位和高8位分别运算的方式,对于双字节有符号数的补码运算则需要在低8位取反加1,高8位仅取反即可。
【例4-7源程序】 ORG 0000H LJMP MAIN ORG 0030H MAIN: MOV A,30H ;取高8位 JNB ACC.7,NEXT;正数不处理 CPL A SETB C MOV ACC.7,C MOV 32H,A MOV A,31H CPL A ADD A,#01H MOV 33H,A MOV A,32H ADDC A,#00H MOV 32H,A LJMP END1 NEXT: MOV 32H,30H MOV 33H,31H END1: SJMP $
例4-8:试实现16路分支转移程序,程序框图如图4.3所示例4-8:试实现16路分支转移程序,程序框图如图4.3所示 图4.3 多分支程序结构 试根据R0中的序号 (00H-0FH), 控制程序转移到不同的分支程序去,设有 16个分支程序分别为PROG0~PROG15。
【例4-8源程序】 ORG 0000H LJMP MAIN ORG 0030H MAIN: MOV A,R0 RL A MOV DPTR,#JMPTAB JMP @A+DPTR JMPTAB: LJMP PROG0 LJMP PROG1 ………. LJMP PROG15
4.2.3 循环结构 一般情况下,循环程序应包括以下几个部分: 1 循环初值:包括循环次数、循环体内工作单元的初值等 2 循环体:需要多次执行的程序段 3 循环控制:循环控制用于判断是否结束循环。判断条件有两种方式 ◆ 用户设置固定的循环次数,利用DJNZ指令对循环次数进行计数,循环程序结构如图4.4(a)所示。 ◆ 用户无法设置固定的循环次数,必须根据循环结束条件判断是否结束循环,循环控制部分每循环一次,检查结束条件,当满足条件时,就停止循环,往下继续执行其他程序,常用CJNE指令来实现循环结束条件判断。循环程序结构如图4.4(b)所示。
例4-9:试编写累加程序,设30H到37H存储的数进得累加,结果高8位放在40H,低8位放在41H。例4-9:试编写累加程序,设30H到37H存储的数进得累加,结果高8位放在40H,低8位放在41H。 【编程思路】 本题算法思路非常简单,即将30H到37H单元的数相加即可,但是如何连续将30H到37H值取出呢?一般情况下,对片内RAM空间的访问可以采用直接寻址和寄存器间接寻址方式,对于连续访问片内RAM单元,则只有采用寄存器间接寻址方式。 采用寄存器间接寻址方式连续访问片内RAM单元,一定需要三条指令 MOV R0,#30H ;设置访问RAM的首地址 ADD A,@R0 ;取出RAM单元数据 INC R0 ;指向下一个RAM单元
【例4-9源程序】 ORG 0000H LJMP MAIN ORG 0030H MAIN: MOV 40H,#00H ;对存放结果的存储单元初始化 MOV 41H,#00H MOV R0,#30H ;采用寄存器间接寻址必须在循环体前设置初值 MOV R3,#08H ;设置固定的循环次数初值 LOOP: MOV A,41H ;将低8位相加结果送到A中 ADD A,@R0 ;依次取出30H到37H单元内容和A相加 MOV 41H,A ;保存相加结果 MOV A,40H ADDC A,#00H ;保存相加进位,得到高8位的值 MOV 40H,A INC R0 ; 采用寄存器间接寻址,连续访问下一个单元 DJNZ R3,LOOP SJMP $
例4-10:试编程判断30H到37H中最大的数,并将存在40H中例4-10:试编程判断30H到37H中最大的数,并将存在40H中 【编程思路】 1、本例和例4-4的不同之处在于本例采用循环结构程序,即在多个数据中找到最大值 2、编程思路与例4-4相同,本例程序算法为采用循环结构将30H到37H依次取出和40H单元内部比较,并将两个数中的最大数赋值给变量MAX(40H)
【例4-10源程序】 ORG 0000H LJMP MAIN ORG 0030H MAIN: MOV R0,#30H MOV R2,#08H MOV 40H,#00H LOOP: MOV A,@R0 SUBB A,40H JC NEXT; 40H中的数据较大 MOV 40H,@R0;大数据放入40h NEXT: INC R0 DJNZ R2,LOOP SJMP $
例4-11:试编写排序程序,设将30H到37H单元中的8个数据从大到小的排列,最大值放在30H,最小值放在37H。例4-11:试编写排序程序,设将30H到37H单元中的8个数据从大到小的排列,最大值放在30H,最小值放在37H。 【编程思路】 排序程序又称为冒泡程序,其算法思路为将两个相邻的数据相比较,所有数据比较完后,找到最大的数,并存入30H单元,再次将剩余的7个数相互比较,找到最大的数,并存入31H单元,依次比较直到完成从大到小的排列。
LOOP1: MOV 50H,@R0 DEC R0 MOV A,@R0 INC R0 MOV @R0,A DEC R0 MOV @R0,50H INC R0 LOOP2: DJNZ R7,LOOP DJNZ R2,LOOP3 LJMP END1 LOOP3: MOV A,R2 MOV R7,A MOV A,R4 MOV R0,A LJMP LOOP END1: SJMP $ 【例4-11源程序】 ORG 0000H LJMP MAIN ORG 0030H MAIN: MOV R7,#08H MOV R2,#08H MOV R0,#30H MOV R4,#30H LOOP: MOV A,@R0 INC R0 CLR C SUBB A,@R0 JC LOOP1 LJMP LOOP2
例4-12:试编程将30H到40H存储单元清零 • 【编程思路】 • 1 存储单元清零可以直接采用数据传送类指令,直接将00H赋值给30H到40H即可实现,但是将需要清零的存储单元较多时,采用这种方式就比较繁琐,应采用循环结构。 • 2 采用循环结构将连续的存储单元清零只需要能够将存储单元连续取出,然后对相应的存储单元用00H赋值即可。
【例4-12源程序】 ORG 0000H LJMP MAIN ORG 0030H MAIN: MOV A,#00 MOV R0,#30H MOV R3,#10H LOOP: MOV @R0,A INC R0 DJNZ R3,LOOP SJMP $
例4-13:编程将片内RAM30H单元开始的15个单元的数据传送到片外RAM的3000H开始的单元中去。例4-13:编程将片内RAM30H单元开始的15个单元的数据传送到片外RAM的3000H开始的单元中去。 【编程思路】 1 本例目的是要求掌握对片外RAM的连续读写操作的编程方法 2 对片外RAM的访问都是采用MOVX指令,本例中主要是对片外RAM的写操作,MCS-51单片机提供了两条指令对片外RAM的写操作MOVX @DPTR, A MOVX @Ri, A 这两条指令的不同之处在于寻址范围不同,采用@DPTR可以访问片外64K的RAM,而采用@Ri仅能访问256字节的空间,本例中要求访问3000H单元开始的片外RAM空间,必然只能采用@DPTR形式。 3、MOVX指令只能采用 MOVX @DPTR, A,常用的错误形式为 MOVX 3000H, A ; MOV 3000H, A 4、既然访问片外RAM只能采用MOVX @DPTR, A,那么希望连续访问片外RAM,唯一的办法就只有不断改变DPTR的值,因为DPTR表示片外RAM的地址
【例4-13源程序】 ORG 0000H LJMP MAIN ORG 0030H MAIN:MOV R0,#30H MOV R7,#0FH MOV DPTR,#3000H ;给DPTR赋初值,即访问片外 RAM的首地址 LOOP: MOV A,@R0 MOVX @DPTR,A ;对片外RAM写操作 INC R0 INC DPTR ;片外RAM的地址加1,以访问下 一单元 DJNZ R7,LOOP HERE: AJMP HERE
4.2.4 子程序设计和子程序调用 在编程时,使用子程序设计应注意以下几点 1 子程序必须由用户起名,即子程序的第一条指令地址称为子程序的起始地址,该指令前必须有标号,该标号即为用户根据子程序所完成的功能所定义的程序名。 2 调用子程序是通过调用指令来实现的,即LCALL 子程序名。子程序的最后一条指令必须是返回指令(RET),在执行调用指令时单片机硬件自动保护PC指针,当执行RET指令时自动恢复PC值。 3 参数传递问题。子程序应注意它的入口参数和出口参数,入口参数是由主程序传给子程序的参数,出口参数是子程序运算完传给主程序的运算结果。即在调用子程序时,必须对子程序的入口参数赋值。
子程序设计应注意以下几点(续) 4 参数传递一般采用以下方法 ◆ 利用寄存器或片内RAM传递子程序的参数,这种方法常用 ◆ 利用堆栈来传递子程序的参数,这种方法是将参数压入堆栈,在子程序运行时从堆栈取参数,这种方法很少被使用,因为在子程序调用和中断返回时系统会改变堆栈的值,容易造成用户弹出错误数据。 ◆ 若参数为位变量,则利用位地址传送子程序参数。 5 在调用子程序时应注意现场保护问题,在子程序中可能会使用累加器和寄存器,在主程序调用子程序前,这些寄存器可以存放有主程序的中间结果,它们在子程序返回时还需要使用,这样就需要在进入子程序时,将这些累加器和寄存器中的数据保存起来。当子程序执行完,在恢复这些数据,一般采用堆栈方式保护这些数据。
例4-14:试编写将两个半字节数合并成一个单字节数子程序。两个半字节数分别存放在R0和R1中,合并后的单字节数存放在R2中。例4-14:试编写将两个半字节数合并成一个单字节数子程序。两个半字节数分别存放在R0和R1中,合并后的单字节数存放在R2中。 【编程思路】 R0作为合并后字节的高位,R1作为合并后字节的低位,即需要将R0的高低四位互换,同时与R1的低四位合并,并将结果存入R2。