Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1567601
  • 博文数量: 354
  • 博客积分: 8137
  • 博客等级: 中将
  • 技术积分: 5137
  • 用 户 组: 普通用户
  • 注册时间: 2009-11-26 15:40
文章分类

全部博文(354)

文章存档

2010年(300)

2009年(54)

分类: 嵌入式

2010-07-05 01:03:21

Franklin C51和A51函数的相互调用 


 摘 要 本文讨论如何在Franklin C51和A51的编程过程中,实现C函数和汇编子程序的互相调用。内容涉及C51函数及其相关段的命名规则,调用中两种参数的传递规则,最后,用两个具体 的编程实例,说明C函数和汇编子程序相互调用方法。

   关键词 函数名 段 参数传递

1 引言

   C语言是一种编译型程序设计语言,它兼顾了多种高级语言的特点,并可以调用汇编语言的子程序。用C语言设计开发微控制器程序已成为一种必然的趋势。 Franklin C51是一种专门针对Intel 8051系列微处理器的C开发工具,它提供了丰富的库函数,具有很强的数据处理能力,编程中对8051寄存器和存储器的分配均由编译器自动管理,因而,通 常用C51来编写主程序。然而,有时也需要在C程序中调用一些用汇编A51编写的子程序。例如,以前用汇编语

言编写的子程序、要求较高的 处理速度而必须用更简练的汇编语言编写的特殊函数或因时序要求严格而不得不使用灵活性更强的汇编语言编写的某些接口程序。另一方面,在以汇编语言为主体的 程序开发过程中,如果涉及到复杂的数学运算,往往需要借助C语言工具所提供的运算库函数和强大的数据处理能力,这就要求在汇编中调用C函数。本文所涉及的 内容,正是讨论如何在Franklin C51和A51的编程过程中,实现C函数和汇编子程序的互相调用。

2 Franklin C51和A51接口所涉及的几个主要问题

2.1 C51函数名的转换及其命名规则

   C51程序模块编译成目标文件后,其中的函数名依据其定义的性质不同会转换为不同的函数名,因此,在C和汇编程序的相互调用中,要求汇编程序必须服从这种 函数名的转换规则,否则,将无法调用到所需的函数或出现错误。C51中函数名的转换规则如表1所列。

2.2 C51函数及其相关段的命名规则

   一个C51源程序模块被编译后,其中的每一个函数以“?PR?函数名?模块名”为名的命名规则被分配到一个独立的CODE段。例如,如果模块 “FUNC51”内包含一个名为“func”的函数,则其CODE段的名字是“?PR?FUNC?FUNC51”。如果一个函数包含有data和bit对 象的局部变量,编译器将按“?函数名?BYTE和?函数名?BIT”命令规则建立一个data和bit段,它们代表所要传递参数的起始位置,其偏移值为 零。这些段是公开的,因而它们的地址可被其它模块访问。另外,这些段被编译器赋予“OVERLAYABLE”标志,故可被L51连接/定位器作覆盖分析。 依赖于所使用的存储器模式,这些段的段名按表2所列规则命名,在相互调用时,汇编语言必须服从C51有关段名的命名规则。

2.3 C51函数的参数传递规则

   C和汇编接口的关键在于要弄清C函数的参数传递规则。Franklin C51具有特定的参数传递规则,这就为二者的接口提供了条件。Franklin C51函数最多可通过CPU寄存器传递三个参数,这种传递技术的优点是可产生与汇编语言相比的高效代码。表3是利用寄存器传递参数的规则。如果参数较多而 使得寄存器不够用时,部分参数将在固定的存储区域内传送,这种混合的情况有时会令程序员在弄清每一个参数的传递方式时发生困难。如果在源程序中选择了编译 控制命令“#pragma NOREGPARMS”,则所有参数传递都发生在固定的存储区域,所使用的地址空间依赖于所选择的存储器模式。这种参数传递技术的优点是传递途径非常清 晰,缺点是代码效率不高,速度较慢。当函数具有返回值时,也需传递参数,这种返回值参数的传递均是通过CPU内部寄存器完成,其传递规则如表4所示。

表3 寄存器参数传递规则

hr3.gif (2098 bytes)

  下面是几个说明参数传递规则的例子:

func1(int a) “a”在R6/R7中传递
func2(int b,int c,int *d)
“b”和“c”分别在R6/R7和R4/R5中传递,
“d”在R1/R2/R3中传递
func3(long e,long f)

表1 C51中函数名的转换

hr1.gif (4846 bytes)

表2 各种存储器模式下函数相关段
名的命名 规则

hr2.gif (3323 bytes)

表4 函数返回值传递规则

hr4.gif (4009 bytes)


    “e”在R4/R5/R6/R7中传递,“f”在参数段中传递SRC是一个十分有用的编译控制命令,它可令C51编译器将一个C源文件编译成一个相应的汇 编源文件,而不是目标文件,在这个汇编文件中,我们可清楚地看到每一个参数的传递方法。例如,对于下面的C源文件(文件名ASM.C):
#include
#define uchar unsigned char
uchar func(uchar x,uchar y); /*函数func 原型声明*/
void main(void) /* 主函数 */
{
func(0x12,0x34); /* 调用函数func */
}
uchar func(uchar x,uchar y) /* 函数func */
{
return (x/y); /* 计算x/y并返回结果 */
}


   编译后将产生如下的汇编输出文件(限于篇幅有所省略):
;ASM.SRC generated from: ASM.C
?PR?main?ASM SEGMENT CODE; ;主函数main代码段声明
?PR?_func?ASM SEGMENT CODE ;函数func代码段声明
PUBLIC _func ;公开函数名以便可被

;其它模块调用
PUBLIC main

RSEG ?PR?main?ASM
main: ;主函数代码段起始;
;{
;func(0x12,0x34);

MOV R7,#02H ;R7传递第一个char参数 注:#02H应为#12H

MOV R5,#034H ;R5传递第二个char参数

LCALL _func ;调用函数func
;}

RET
; uchar func(uchar x,uchar y)

RSEG  ?PR?_func?ASM_func: ; 函数func代码段起始  注:RSEG ?PR?_func?ASM
                                                    _func:
;{
; return (x/y);

MOV     A,R7 ;计算x/y

MOV     B,R5

DIV     AB

MOV     R7,A ;结果经R7返回
; }

RET

END

   从上列汇编程序可以看出,函数名func前有一个前缀字符“_”,这表明该函数含有寄存器内的参数传递,寄存器R7和R5用来传递参数,计算结果经R7返 回。如果在前述的C源文件中用“#pragma NOREGPARMS”控制命令禁止寄存器内的参数传递,则所有参数均通过固定的存储区域传递。

3 应用举例

3.1 汇编中调用C51函数

   C51源文件func51.C中有一个名为func的函数,它完成某算术运算功能,该C源文件清单如下:
#pragma NOREGPARMS
#include
#include
unsigned char func(unsigned int v_a,unsigned int v_b)
{
return sqrt(v_a/v_b);  /* 计算HR5.gif (152 bytes)并返回结果 */
}
            
   该函数需传递两个用于运算的参数,本例用“NOREGPARMS”命令禁止寄存器内的参数传递,即两个参数均在存储器区域内传递,且选择SMALL存储器 模式。那么,在汇编中调用该函数的程序清单如下(文件名ASM51.A51):

EXTRN         CODE (func) ;外部函数func声明
EXTRN         DATA (?func?BYTE) ;外部函数func局部变量传送段声明
funca51    SEGMENT CODE ;funca51代码代声明
VAR             SEGMENT DATA ;局部变量段声明
STACK          SEGMENT IDATA ;堆栈段声明

RSEG VAR ;局部变量段
a_v: DS  2 ;用于存放第一个int参数的变量
b_v: DS 2 ;用于存放第二个int参数的变量
result: DS 1 ;存放func函数char结果的变量

RSEG   STACK

DS     20H ;为堆栈保留32个字节

RSEG   funca51 ;funca51代码段起始

JMP    START
START: MOV SP,#STACK-1 ;初始化堆栈

MOV    ?func?BYTE+0,a_v+0 ;取第一个int参数

MOV ?func?BYTE+1,a_v+1

MOV ?func?BYTE+2,b_v+0 ;取第二个int参数



MOV ?func?BYTE+3,b_v+1

LCALL func ;调用C函数func

MOV result,R7 ;存取结果

END

   分别用C51和A51编译器对上述func51.C和ASM51.A51编译,再执行连接L51 ASM51.OBJ,func51.OBJ NOOVERLAY,即可实现在ASM51中调用C函数func。连接时选择NOOVERLAY是为了禁止数据段和位段的覆盖。

3.2 在C51中调用汇编程序

   下面以8051和DS1820接口程序为例来说明在C中调用汇编程序的方法。Dallas公司的DS1820是一种数字式温度计,它与微控制器接口只需一 根I/O线,所有的命令、状态和9位温度读数均通过单线双向传送。虽然该器件的硬件接口十分简单,但对读/写时序中的时间片精度要求严格,因而,本例接口 程序采用汇编语言编写,主程序及修正温度值的计算部分用C语言编写。本例假定读者对DS1820有所了解,不然请参阅Dallas公司有关DS1820的 数据资料。
   以下是8051微控制器与DS1820接口的C源程序,本程序要求8051的P1.0与DS1820连接,工作频率12MHz。清单中 error(char)和display(char *)分别是错误处理和LCD显示处理函数,限于篇幅未给出。

#include
#include
#include
extern WDS1820(unsigned char x); /* 写DS1820命令外部函数声明 */
extern RDS1820(unsigned char *pt); /* 读DS1820数据外部函数声明 */
extern bit RTDS1820(void); /* 复位DS1820外部函数声明 */
extern Delay15(unsigned char n); /* 延时15μs外部函数声明 */
sbit P1_0=P1^0; /* sbit对象P1.0声明 */
void main (void) /* 主函数 */
{
  unsigned data int i;
  float data tempF;
  unsigned char data temp[10],disbuf[10]; /* 存放温度数据和显示数据的局部数组变量声明 */
  if(RTDS1820()!=1) error(0x3); /* 复位并判DS1820是否存在 */
  Delay15(0xff); /* 延时约15×255μs */
WDS1820(0xcc); WDS1820(0x44); /* 向DS1820发跳读ROM和启动温度变换命令 */
  P1_0=1; /* P1.0口置线高电平 */
  do{ Delay15(0xff);i++;}while(i<=400); /* 延时约1.5秒钟 */
  if(RTDS1820()!=1) error(0x3); /* 复位并判DS1820是否存在 */
  Delay15(0xff); /* 延时约15×255μs */
  WDS1820(0xcc); WDS1820(0xbe); /* 向DS1820发跳读ROM和读9字节数据命令 */
  RDS1820(&temp); /* 9字节数据读入数组temp */
  tempF=(((temp[1]<<8)+temp[0])>>1)-0.25+((temp[7]-temp[6])/temp[7]);     /* 温度值修正计算 */
  sprintf(&disbuf,"T=%+4.1f%c",tempF,‘C‘); /* 按T=±XXX.X C格式组织数据送disbuf */
  display(&temp); /* 数据送LCD显示 */
}

  以下是用A51宏汇编编写的DS1820接口源程序清单,共有4个子程序,其中 RTDS 1820无参数传递,但具有bit对象的返回值,DELAY15和WDS1820带有一个经R7传递的无符号char类参数,RDS1820带有一个经 R7传递的1字节指针类参数。

       NAME   RW1820 ;定义模块名
?PR?RDS1820?RW1820    SEGMENT CODE ;RDS1820子程序代码段声明
?PR?WDS1820?RW1820    SEGMENT CODE ;WDS1820子程序代码段声明
?PR?RTDS1820?RW1820    SEGMENT CODE ;RTDS1820子程序代码段声明
?PR?DELAY15?RW1820    SEGMENT CODE ;DELAY15子程序代码段声明
PUBLIC
RTDS1820,_WDS1820,_RDS1820,_DELAY15 ;公开函数名以便C模块可调用它们
       RSEG    ?PR?RDS1820?RW1820_RDS1820:   ;RDS1820代码段起始,完成9字节温度数据的读取

MOV     R1,#9 ;置9字节数据计数器初值

MOV     A,R7 ;取经R7传递的数组temp首址(C中定义)

MOV     R0,A
RD18201:MOV        R2,#8      ;置1字节位移位计数器初值
RD18202:SETB      P1.0          ;P1.0置为高电平
       NOP
       NOP
       CLR      P1.0        ;P1.0置为低电平
       NOP
       NOP
       SETB      P1.0    ;P1.0置为高电平,准备输入数据
       MOV        R7,#1               ;延时15μs
       LCALL    DELAY15
       MOV       C,P1.0            ;P1.0状态读入位累加器
       RRC       A              ;累加器A右移
       DJNZ     R2,RD18202           ;判一个字节是否读完
       MOV       @R0,A              ;保存结果
       INC       R0            ;地址指针加1
       DJNZ     R1,RD18201  ;判9字节是否读完
       RET         ;返回
       RSEG    ?PR?WDS1820?RW1820_WDS1820: ;WDS1820代码段起始, 完成1字节命令的写入
       MOV     R1,#8              ;置1字节位移位计数器初值
       CLR     C                    ;清位累加器
       MOV     A,R7                ;取经R7传递的命令参数
WR18201:CLR     P1.0       ;P1.0置为低电平
       MOV              R7,#1      ;延时15μs
       LCALL          DELAY15
       RRC     A              ;累加器A右移1位
       MOV     P1.0,C    ;发送1位数据给DS1820
       MOV     R7,#1     ;延时15μs
       LCALL   DELAY15
       SETB    P1.0    ;P1.0置为高电平
       NOP
       DJNZ    R1,WR18201        ;判1字节数据是否发送完毕
       SETB    P1.0       ;P1.0置为高电平
       RET          ;返回
       RSEG    ?PR?RTDS1820?RW1820
RTDS1820:     ;RTDS1820代码段起始,判DS1820是否存在
       CLR     P1.0       ;P1.0置为低电平
       MOV     R7,#40     ;延时约60μs
       LCALL   DELAY15
       SETB    P1.0       ;P1.0置为高电平
       MOV     R7,#4      ;延时约60μs
       LCALL   DELAY15
       MOV     R7,#100   ;置循环读初值
       SETB    C              ;位累加器置1
RST0:  JNB      P1.0,RST1    ;P1.0为低表明DS1820存在并返回
       DJNZ    R7,RST0 ;判循环读100次结束否
       CLR    C               ;无DS1820存在脉冲,位累加器清零
RST1:  RET      ;DS1820存在标志经位累加器返回
       RSEG   ?PR?DELAY15?RW1820_DELAY15: ;DELAY15代码段起始,延 时15μs功能
DELAY15:MOV     R6,#6            ;2C
DEL151: DJNZ   R6,DEL151    ;2C*R6
       DJNZ    R7,DELAY15           ;2C
       RET
       END
阅读(2650) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~