程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> Franklin C51和A51函數的相互調用

Franklin C51和A51函數的相互調用

編輯:關於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 寄存器參數傳遞規則

下面是幾個說明參數傳遞規則的例子:

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中函數名的轉換

表2 各種存儲器模式下函數相關段名的命名規則

表4 函數返回值傳遞規則

“e”在R4/R5/R6/R7中傳遞,“f”在參數段中傳遞SRC是一個十分有用的編譯控制命令,它可令C51編譯器將一個C源文件編譯成一個相應的匯編源文件,而不是目標文件,在這個匯編文件中,我們可清楚地看到每一個參數的傳遞方法。例如,對於下面的C源文件(文件名ASM.C):

#include <reg51.h>
#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 <reg51.h>
#include <math.h>
unsigned char func(unsigned int v_a,unsigned int v_b)
{
return sqrt(v_a/v_b); /* 計算並返回結果 */
}

該函數需傳遞兩個用於運算的參數,本例用“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 <reg51.h>
#include <stdio.h>
#include <math.h>
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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved