12第6章C51与汇编语言混合编程6.1、C51与汇编语言混合编程概述6.2、C51和A51程序接口基础6.3、混合编程的实现36.1C51与汇编语言混合编程概述•在一个应用程序中,根据每个任务的具体特点和要求,用不同的编程语言编写源程序,最后通过编译/连接器生成一个可执行的完整程序,这种编程方式称为混合编程。•汇编语言特点:优点:执行速度快、效率高、实时性强、与硬件结合紧密。缺点:编程难度大、可读性差,不便于移植、开发时间长。•C语言特点:优点:编程容易、可移植性强、支持多种数据类型,能直接对硬件进行操作,效率高。缺点:实时处理弱于汇编语言,无法准确定时。•混合编程特点:效率高、速度快、易于编程、可读性、可移植性好,使用范围广4单片机程序的编译过程:无论是C语言还是汇编语言,源程序都要被转换成目标代码(机器语言),单片机才能识别。在Keil中程序的编译过程如图6-1所示。图6-1程序编译连接过程在单片机混合编程中,C模块与汇编模块的接口简单,分别用C51和A51对源文件进行编译,然后经连接定位器L51产生可下载到程序存储器的十六进制可执行文件。56.2C51和A51程序接口基础•C语言与汇编语言程序的连接,在技术上有两个问题:一个是C语言程序与汇编语言程序如何相互调用;另一个是C语言程序和汇编语言程序如何实现相互之间的数据传递。•混合编程中,必须约定两个规则,即命名规则和参数传递规则。6.2.1C51函数名的转换及其命名规则•C51程序模块编译成目标文件后,其中的函数名要依据其定义的性质转换为相应不同的函数名。因此,在C和汇编程序的相互调用中,要求汇编程序必须服从这种函数名的转换规则,否则将无法调用到所需的函数甚至出现错误。•C51中函数名的转换规则如表6-1所示,其汇编符号名全部转换为大写。6表6-1C51中函数名的转换规则C51函数声明汇编符号名说明voidfunc1(void)FUNC1无参数传递或不含寄存器的函数,名称不作改变直接转入目标文件中。voidfunc2(char)_FUNC2通过寄存器传递参数,函数名加前缀“_”voidfunc3(void)reentrant_?FUNC3重入函数,通过堆栈传递参数,函数名加前缀“_?”76.2.2C51函数及其相关段的命名规则•一个C51源程序模块被编译后,其中的每个函数以“?PR?函数名?模块名”为命名规则被分配到一个独立的CODE段。•例如,如果模块“FUNC51”内包含一个名为“func”的函数,则其CODE段的名字是“?PR?FUNC?FUNC51”。•如果一个函数包含有data或bit类型的局部变量,编译器将按“?函数名?BYTE”或“?函数名?BIT”命名规则建立一个data或bit段,它们代表所要传递参数的起始位置,其偏移量为0。这些段是公开的,因而它们的地址可以被其它模块访问。•这些段被编译器赋予“OVERLAYABLE”标志,可被L51连接/定位器作覆盖分析。依据所使用的存储器模式,这些段按表6-2所列规则命名,在相互调用时,汇编语言必须服从C51有关段名的命名规则。8表6-2各种存储模式下C51函数段名的命名规则数据段类型段名程序代码CODE?PR?函数名?模块名(所有存储器模式)局部变量DATA?DT?函数名?模块名(SMALL模式)PDATA?PD?函数名?模块名(COMPACT模式)XDATA?XD?函数名?模块名(LARGE模式)局部bit变量BIT?BI?函数名?模块名(所有存储器模式)96.2.3C51函数的参数传递规则C51中调用汇编程序时参数传递有两种方式,一种是通过寄存器传递,一种是通过固定存储区传递。1.通过寄存器传递参数•KeilC51规定,调用函数时,通过寄存器最多可传递3个参数,余下的通过固定存储区传递。•如果在源程序中采用了编译控制命令“#pragmaNOREGPARMS”,则所有参数传递都发生在固定的存储区域,所使用的地址空间依赖于所选择的存储模式。•用寄存器传递参数的函数在编译时被C51编译器在函数名前加了一个“_”的前缀,用固定存储区传递参数的函数转换成的函数名没有下划线,如表6-1所示。•不同的参数用到的寄存器不一样,不同的数据类型用到的寄存器也不同。表6-3是利用寄存器传递参数的规则。10表6-3C51利用寄存器传递参数规则参数类型charintlong/float通用指针第1个参数R7R6、R7R4-R7R1-R3第2个参数R5R4、R5无R1-R3第3个参数R3R2、R3无R1-R3其中,int型和long型数据传递时,低地址寄存器中放数据的高位字节,高地址寄存器中放数据的低位字节;float型数据满足IEEE格式,R4中存放阶码和符号位,尾数按从高位到低位的顺序依次存放在寄存器R5、R6和R7中;通用指针的存储类型存放在R3中,高位在R2,低位在R1。如果某一函数的形式参数有两个或更多,当发生寄存器冲突时,后者改为通过固定存储区传递。11【例6-1】函数参数传递举例。•func1(inta)a是第一个参数,在R6,R7中传递。•func2(intb,intc,int*d)“b”是第一个参数,在R6,R7中传递;“c”是第二个参数,在R4,R5中传递;“d”是第三个参数,在R1,R2和R3中传递。•func3(longe,longf)“e”是第一个参数,在R4-R7中传递;“f”是第二个参数,不能在寄存器中传递,只能在固定存储区中传递。•func4(floatg,charh)“g”是第一个参数,在R4-R7中传递;“h”是第二个参数,必须在固定存储区中传递。122.通过固定存储区传递•通过固定存储区传递参数的优点是传递途径非常清晰,缺点是代码效率不高,速度较慢。•用固定存储区传递参数给汇编程序,参数段首地址通过名为“?函数名?BYTE”的符号确定。当传递位值时,使用名为“?函数名?BIT”的符号保存位参数段首地址。所有传递的参数存放在以首地址开始递增的存储区内,即使通过寄存器传递参数,参数也将在这些段中分配空间,参数按声明的先后在每个段中顺序保存。•用作参数传递的固定存储区可能在内部数据区或外部数据区,由存储模式决定。SMALL模式的参数段用内部数据区,COMPACT和LARGE模式用外部数据区。133.函数返回值当函数具有返回值时,也需传递参数,这种返回值的传递是通过51内核单片机内部寄存器完成的,其传递规则如表6-4所示。14表6-4函数返回值所用寄存器分配返回值类型寄存器说明bitCY进位标志char,unsignedchar,或1字节指针R7int,unsignedint,或2字节指针R6、R7高位字节在R6,低位字节在R7long或unsignedlongR4-R7高位字节在R4,低位字节在R7floatR4-R732位IEEE格式,阶码和符号位在R4,尾数高位字节在R5,低位字节在R7通用指针R1-R3R3存放存储器类型,高位字节在R2,低位字节在R115【例6-2】C51的函数名转换规则、段命名规则及参数传递规则举例。/*-------------------------文件名:DIV.c功能:计算x/y-------------------------*/#includeSTC12C5A.h#defineucharunsignedcharucharfunc(ucharx,uchary);//函数func原型声明voidmain(void)//主函数{func(0x12,0x34);//调用函数funcwhile(1);}ucharfunc(ucharx,uchary){return(x/y);//计算x/y并返回结果}16/*-------------------------文件名:DIV.SRC说明:此文件是DIV.c编译后的汇编输出文件(限于篇幅,有所省略)------------------------*/?PR?main?DIVSEGMENTCODE;主函数main代码段声明?PR?_func?DIVSEGMENTCODE;函数func代码段声明EXTRNCODE(?C_STARTUP);使用其它模块中的符号?C_STARTUPPUBLIC_func;公开函数名,以便可以被其它模块调用PUBLICmain;#includeSTC12C5A.h;#defineucharunsignedchar;ucharfunc(ucharx,uchary);;voidmain(void)RSEG?PR?main?DIV17main:;主函数代码段起始USING0;SOURCELINE#4;{;SOURCELINE#5;func(0x12,0x34);;SOURCELINE#6MOVR5,#034H;R5传递第二个char参数MOVR7,#012H;R7传递第一个char参数LCALL_func;调用函数func?C0001:;while(1);;SOURCELINE#7SJMP?C0001;ENDOFmain;}18;ucharfunc(ucharx,uchary)RSEG?PR?_func?DIV_func:;函数func代码段起始USING0;SOURCELINE#9;--Variable'y?141'assignedtoRegister'R5'---;--Variable'x?140'assignedtoRegister'R7'---;{;SOURCELINE#10;return(x/y);;SOURCELINE#11MOVA,R7;计算x/yMOVB,R5DIVABMOVR7,A;结果经R7返回19;};SOURCELINE#12?C0004:RET;返回;ENDOF_funcEND;结束•上面给出了一个C51程序及其编译后的程序清单,可以看出,函数func有2个char型参数,通过R7,R5传递,所以转换成汇编函数名_func,返回值通过R7传递回主函数。•如果在前述的DIV.C源文件中使用“#pragmaNOREGPARMS”控制命令,禁止寄存器内参数传递,则所有参数均通过固定的存储区传递。其编译后的汇编输出文件如下:20?PR?main?DIVSEGMENTCODE;主函数main代码段声明?PR?func?DIVSEGMENTCODE;函数func代码段声明?DT?func?DIVSEGMENTDATAOVERLAYABLE;局部变量内部数据段声明EXTRNCODE(?C_STARTUP);使用外部模块?C_STARTUPPUBLIC?func?BYTE;公开函数func中的data区的局部变量PUBLICfunc;公开函数名,以便可以被其它模块调用PUBLICmainRSEG?DT?func?DIV?func?BYTE:;局部变量内部存储区首地址x?140:DS1ORG1y?141:DS121;#includeSTC12C5A.h;#defineucharunsignedchar;#pragmaNOREGPARMS;ucharfunc(ucharx,uchary);;voidmain(void)RSEG?PR?main?DIVmain:;主函数代码段起始USING0;SOURCELINE#5;{;SOURCELINE#6;func(0x12,0x34);;SOURCELINE#7MOV?func?BYTE,#012H;固定存储区首地址单元传递第一个参数MOV?func?BYTE+01H,#034H;固定存储区首地址加1单元传递第二个参数LCALLfunc;调用函数func22?C0001:;while(1);;SOURCELINE#8SJMP?C0001;ENDOFmain;};ucharfunc(ucharx,uchary)RSEG?PR?func?DIVfunc:;函数func代码段起始USING0;SOURCELINE#10;{;SOURCELINE#11;return(x/y);;SOURCELINE#1223MOVA,x?140;计算x/yMO