第27卷第1期 2008年2月 兰州 交通大学学报
Journal of Lanzhou Jiaotong University V()1.27 No.1
Feh.2008
文章编号:1001—4373(2008)01—013卜O4
KEIL C5 1集成开发环境C和汇编语言的相互调用
严天峰, 王耀琦 (兰州交通大学电子与信息工程学院,甘肃兰州 730070) 摘要:目前C语言已成为开发单片机项目的主要工具,但一些特殊的应用场合仍然需要汇编语言编写程序,如编 写时序要求非常严格的接口协议时,这必然涉及到C与汇编的相互调用,即混合编程的问题.详细介绍了KE1L C51环境下的C和汇编语言相互调用的方法和原则,并具体说明混合编程的细节和应注意的问题. 关键词:KEIL C51汇编调用混合编程 中图分类号:TP311.11 文献标识码:A
目前,C语言已是单片机应用系统的主流编程 工具,它具有代码可靠性高,可移植性好,易于维护 的特点.特别是德国KEII 公司推出功能强大的基 于WINDOWS平台的51系列单片机集成开发工具 /*Vision之后,这一趋势越发明显.采用C语言几乎 可以完成江编语言的所有工作,可以大在提高程序 的开发效率.但在一些特殊应用的场合仍然需要通 过汇编语言编写程序,比如对时序要求非常严格的 接口协议和中断向量的地址处理等等.另外还存在 种情况,即程序员原先已用汇编语言编写了大量 的子程序,现在虽然改用C语言开发设计,但又不 想重写代码;或者想充分发挥C语言在数值计算的 优势,通过汇编语言调用C来实现复杂的数学计 算.这必然要涉及到C与汇编的相互调用,即混合 编程的问题.C与汇编的混合编程,重点是参数的传 递和函数值的返回以及C51对目标代码的段管理, 这是嵌人式系统混合编程过程中实现开发和运行效 率统一的关键环节. 1 C51编译器对程序和数据代码段的管理 C51能否成功调用汇编语言的前提条件之一是 汇编程序的编写应符合C51编译器的编译规则.事 实上,C51对汇编程序的调用就是对函数的调用.因 此,要实现C和汇编的相互调用,首先要清楚的是 个被C51编译后的函数,其程序代码段和数据段 的转换规则.C51对所属模块的各个函数进行编译 收稿日期:2007—09—13 作者简介:严天峰(1970一),男,四川平昌人,副教授 时,每个函数都生成一个以?PR?函数名?模块名 为段名的程序代码段,如果该函数包括无明确存储 器类型声明的局部变量,将生成一个字节类型的局 部数据段;当参数中有位变量时,还将生成一个位类 型的局部位段,用来存放在函数内部已定义的位标 量和位变量参数.在SMAI I 编译模式下,局部段 的命名原则如表1所示_1]. 表1局部段的命名原则 Tab.1 Naming principle of local se ̄ent
每个局部段的段名表示该段的起始地址.假如, 模块example包含一个名为“func”的函数,其程序 代码段的命名为”?PR?func?example,其中func (函数名)即为该段的起始地址.如果func函数包含 有DATA和BIT对象的局部变量,局部数据段和 局部位段的起始地址则定义为?func?BYTE和? func?BIT,它们代表所传递的参数的初始位置. C51编译器将源程序的函数名转换为汇编格式的目 标文件时,转换后的函数名要根据参数传递的性质 不同而改变,当C和汇编程序互相调用时,汇编程 序的编写必须符合这种转换规则,否则编译器将会 出现错误提示.表2为函数名的转换规则l_1].
维普资讯 http://www.cqvip.com 132 兰州 交通 大学学 报 第27卷 void func(void) 函数类型Fune(形参) Fune_(void)reentrant
函数内部无参数在寄存器内部传递,函数名不做改变. 函数内部有参数在寄存器内部传递,函数名前加“一”下划线. 原函数为再入函数时,函数名前加“一?”,表明有参数在数据存储器内部传递. 假设函数rune的C源程序如下所示,现分析经 汇编后产生的代码. //c源程序: void(unsigned char a) {bit c; c=0;} 汇编后的代码(文件ExAMPLE.SRC): NAMEExAMPLE;模块名 PRfune?D MPLE SEGMENT CODE;定义程
序代码段 DT一fune?D LE SE 诬NT DATA;定义 局部数据段
c?041:DBIT 1;a?041标号为该BIT位地址 void rune(unsigned char a) RsEG?PR?fune?D MPLE;程序代码段 rune:;程序代码段起始地址 MOV a?040,R7;R7参数传递到地址a?040 ;{ ;bit c; ;c一0; CLR c?041 ;} RET END
Ⅸunc?ExA LE sEGMENT瑚T.定义局部 2 参数的传递和返回
位段 一………一。。 PUBLICrune;PUBLIC声明,表明该函数可被其它模 块调用 RSDG?DT?fune?D MPLE;局部数据段
一funeBYTE:;局部数据段起始地址 a?040:DS 1;a?040标号为该字节的地址 RSEG?BI?fune?EXAMPI,E;局部位段
一fune?BIT:;局部位段起始地址
C和汇编的另一个关键问题是参数的传递和返 回.函数调用时,默认为通过寄存器进行参数传递. 当采用预处理编译控制指令#pragma NOREG— PARMS时,参数将通过固定的存储区域传递.表3 为参数在寄存器内部传递时的传递规则 .
表3寄存器参数传递规则 Tab.3 Passing rulesof registerparameters
例如在函数func(*P,unsigned char a,un— signed char b)中,指针“P”通过R1,R2,R3寄存器 进行参数传递,其中R1传递低字节,R2传递高字 可从前面介绍的EXAMPI E.SRC例子中清楚的 看到,通过R7寄存器将形参“a”的内容传递到了地 址a?040.当C程序调用的汇编函数有参数返回 节.“a”通过R7传递,“b”则通过R5传递.读者也 时,其返回值的参数传递见表4[引. 表4函数返回值的参数传递 Tab.4 Parameterpassingoffunction returned value
维普资讯 http://www.cqvip.com 第1期 严天峰等:KEIL C51集成开发环境C和汇编语言的相互调用 3 C51和汇编语言相互调用的方法 3.1 C51调用汇编 下面以C51调用一个ADCo8O9模数转换子程 序为例来具体说明C51调用汇编的方法.在C主程 序调用汇编子程序之前,应先用”extern”声明被调 用的汇编程序是一个外部函数;同时在汇编程序中 则要用“PUBI IC”声明该子程序可以被其它模块调 用. C51程序: #include<reg51.h> #include<stdio.h> #define uchar unsigned char extern uchar ad0809(uchar p);//说明被调用的 ad0809汇编子程序是一个外部函数, //采用一般指针传递参数 extern delasm(uchar time)}//被调用delasm延时子程 序是一个外部函数 main() { uchar data adc_data[8]}//ADCo809转换后的8通道 采样值放入adc_data数组 while(1) ad0809(adc_data);//调用的ad0809汇编子程序,通过 数组名进行参数传递 delasm(2)}//调用的delasm延时子程序 } AD0809 A/D转换子程序: EOCEQUP1.4 NAME ADCo809;模块名为 809 7 PR?一 )o8097 ADO0809 SEGMENT C0DE;子程 序代码段声明,一AD0809为函数名,“一“表明有参数在寄存 器内部传递 PUBLIC—AD0809;用“PUBLIC”声明该函数可以被 其它模块调用 RSEG?PR?一AD08097 ADCo809;AD0809子程序代 码段起始位置 AD0809: MOV A,R1;R1传递数组adc_data[8]首址 MOV R0,A MOVR4,#00H MOVR7,#8 MOVDPTR,#0FDF8H SAM:M0V A,R4 M0VX@D rR,A JB EOC,¥ M0VX A,@DPTR MOV@R0,A INC DPTR INC R0 INCR4 DJNZR7,SAM RET END 上面的例程中,在C程序里,首先对汇编子程 序AD0809进行外部函数声明,extern uehar ad0809(uchar*p)表明传递的是一个一般指针变 量.主函数用ad0809(ado—data)调用汇编子程序,在 调用ad0809子程序的过程中,将数组名adc—data (即该数组的首地址)以指针的方式进行参数传递, R1存放该数组的首地址.在AD0809汇编子程序 中,将R1地址内容赋给R0,而以R0寻址的连续8 个地址空间存放的就是ADCo8O9八个通道的采样 值,经过参数传递后,数组ade—data[8]各元素的内 容就是A/D转换后的结果.函数ad0809(uchar* p)中的形参P被定义为一个一般指针。参数传递通 过R1、R2、R3寄存器进行;如果写为uchar data p,形参P则被定义为一个基于data区的指针,按表 3可知,参数传递通过R7进行,读者可自行改动测 试. 3.2汇编调用C 下面的例子是一个汇编调用C程序的过程,汇 编程序A_FUNC.ASM的60H至63H分别存放两 个int类型的整型数值,count.C实际上是一个2字 节乘法的运算函数,同时将一个4字节长整类型的 结果返回给汇编,程序代码如下: //主函数: #include<stdio.h> extem void a_func(void)f说明被调用的a rune子程 序是一个外部函数 voidmain(void) { ̄func();};汇编程序A_FUNC.ASM: NAMEA..FUNC,模块名 PRa_func?A_FUNC SEGMENT CODE;程序代 码段声明 EXTRN CODE(count);声明被调用的eounL c是外 部函数 PUBLICa_func RSEG?PR?a_func?A_FUNC ̄程序代码段起始 a..func; USING0 MOV 60h,#23h}60H至63H存放两个整型数值, 60H存放乘数高字节