Keil C51中變量和函數(shù)的絕對地址定位問題
1、變量絕對地址定位
1) 在定義變量時(shí)使用 _at_ 關(guān)鍵字加上地址就可。
unsigned char idata myvar _at_ 0x40;
把變量 myvar 定義在 idata 的 0x40 處, 在 M51 文件中可以找到這麼一行
IDATA 0040H 0001H ABSOLUTE ;表示有變量在 idata 的 0x0040 處絕對地址定位.
2) 使用 KeilC 編譯器定義絕對地址的變量, 方法待查.
2、函數(shù)絕對地址定位
1) 在程序中編寫一函數(shù) myTest
void myTest(void)
{
// Add your code here
}
2) 使用 KeilC 編譯器定位絕對地址的函數(shù),打開 Project -> Options for Target 菜單,選中 BL51 Locate 選項(xiàng)卡,在 Code: 中輸入:?PR?myTest?MAIN(0x4000) 把函數(shù) myTest 定位到程序區(qū)的 0x4000 處,再次編譯就可以了。
3) 一次定位多個(gè)函數(shù)的方法
同樣地, 在程序中再編寫另外一個(gè)函數(shù) myTest1
void myTest1(void)
{
// Add your code here
}
在 Options for Target 菜單的 BL51 Locate 選項(xiàng)卡的 Code: 中輸入:?PR?myTest1?MAIN(0x3900), ?PR?myTest?MAIN(0x4000) 把函數(shù) myTest1 定位在程序區(qū)的 0x3900 處, 把函數(shù) myTest 定義在程序區(qū)的 0x4000 處,重新編譯就可以了.
在 M51 文件中可以找到下面的內(nèi)容:
復(fù)制代碼
3.obj TO Reader RAMSIZE (256) CODE (?PR?MYTEST1?MAIN (0X3900), ?PR?MYTEST?MAIN (0X4000))
3665H 029BH *** GAP ***
CODE 3900H 0014H UNIT ?PR?MYTEST1?MAIN
3914H 06ECH *** GAP ***
CODE 4000H 0014H UNIT ?PR?MYTEST?MAIN
復(fù)制代碼
4) 函數(shù)的調(diào)用
程序中直接調(diào)用函數(shù)的方式就不說明了, 這里重點(diǎn)講使用函數(shù)指針調(diào)用絕對地址處的函數(shù)的方法.
(1) 定義調(diào)用的函數(shù)原形 typedef void (*CALL_MYTEST)(void); 這是一個(gè)回調(diào)函數(shù)的原形, 參數(shù)為空.
(2) 定義相應(yīng)的函數(shù)指針變量 CALL_MYTEST myTestCall = NULL;
(3) 函數(shù)指針變量賦值,指向我們定位的絕對地址的函數(shù) myTestCall = 0x3900; 指向函數(shù) myTest1
(4) 函數(shù)指針調(diào)用
if (myTestCall != NULL)
{
myTestCall(); // 調(diào)用函數(shù)指針處的函數(shù) myTest1, 置 PC 指針為 0x3900
}
檢查編譯生成的 bin 文件, 到 0x3900 處可以看到 myTest1 的內(nèi)容,在 0x4000 處可以看到 myTest 的內(nèi)容,
(5) 其它說明:如果在 0x3000 到 0x3900 的程序空間沒有內(nèi)容時(shí),把 myTestCall 的地址指針指到 0x3800 (在 0x3000 到 0x3900 之間) 時(shí), 會從 0x3900 處開始執(zhí)行。至於在 Load 中調(diào)用 AP 中的函數(shù)的方法與此類似, 但是相應(yīng)的參數(shù)傳遞可能要另尋方法.
段的定義
RSEG是段選擇指令,要想明白它的意思就要了解段的意思。
段是程序代碼或數(shù)據(jù)對象的存儲單位。程序代碼放到代碼段,數(shù)據(jù)對象放到數(shù)據(jù)段。段分兩種,一是絕對段,一是再定位段。絕對段在匯編語言中指定,在用L51聯(lián)接的時(shí)候,地址不會改變。用于如訪問一個(gè)固定存儲器的i/o,或提供中斷向量的入口地址。而再定位段的地址是浮動(dòng)的,它的地址有L51對程序模塊連接時(shí)決定,C51對源程序編譯所產(chǎn)生的段都是再定位段,它都有段名和存儲類型。絕對段沒有段名。
說了這么多,大家可能還是不明白段是什么意思。別急,接著往下看。
例如,你寫用C寫了一個(gè)函數(shù) void test_fun(void) { …},存在test.c中,用編譯器編譯以后.SRC FILE中看到,:
?PR?test_fun?TEST SEGMENT CODE //(函數(shù)放到代碼段中)
寫這個(gè)函數(shù)體的時(shí)候:
RSEG ?PR?test_fun?TEST //選擇已定位的代碼段為當(dāng)前段 test_fun:
…… //代碼
所以函數(shù)的表達(dá)模式是這樣: ?PR?函數(shù)名?文件名
而函數(shù)名又分:
1:無參函數(shù) ?PR?函數(shù)名?文件名
2:有參函數(shù) ?PR?_函數(shù)名?文件名
3:再入函數(shù) ?PR?_?函數(shù)名?文件名
又例如 你定義了全局變量
unsigned char data temp1,temp2;
unsigned char xdata temp3;
在test.c文件中,編譯器會為每個(gè)文件分0到多個(gè)全局?jǐn)?shù)據(jù)段,相同類型的全局變量被存到同一段中。所以上面會編譯成如下:
復(fù)制代碼
RSEG ? DT? TEST
. temp1: DS 1
. temp2: DS 1
;
RSEG ?XD? TEST
. temp3: DS 1
復(fù)制代碼
復(fù)制代碼
//下面是各個(gè)類型的數(shù)據(jù)全局段的表示
?CO? 文件名 //常數(shù)段
?XD? FILE_NAME //XDATA 數(shù)據(jù)段
?DT? FILE_NAME //DATA 數(shù)據(jù)段
?ID? FILE_NAME //IDATA…..
?BI? FILE_NAME //BIT …..
?BA? FILE_NAME //BDATA….
?PD? FILE_NAME //PDATA…..
復(fù)制代碼
看到這里大家應(yīng)該明白段的意思了吧。也許你會問,這有什么作用哪?它就是用在當(dāng)你需要用匯編語言寫一部份程序的時(shí)候,把匯編寫的函數(shù)放在這個(gè)文件中,改名xxx.a51,按上面的規(guī)則寫。編譯就好。
既然知道了段的意思,現(xiàn)在我們回到SEG的用法上來。A51中有兩種段選擇指令:再定位段選擇指令 和 絕對段選擇指令,它們用來選擇當(dāng)前段是再定位段還是絕對段。使用不同的段選擇指令,將使程序定位在不同的地址空間之內(nèi)。
1、再定位段的選擇指令是: RSEG 段名
它用來選擇一個(gè)在前面已經(jīng)定義過的再定位段作為當(dāng)前段。用法就像我們上面的例子,先申明了一個(gè)函數(shù)段,后面寫這個(gè)函數(shù)段。
2、絕對段選擇指令
復(fù)制代碼
CSEG [AT 絕對地址表達(dá)式] //絕對代碼段
DSEG [AT 絕對地址表達(dá)式] //內(nèi)部絕對數(shù)據(jù)段
XSEG [AT 絕對地址表達(dá)式] //外部絕對數(shù)據(jù)段
ISEG [AT 絕對地址表達(dá)式] //內(nèi)部間接尋址絕對數(shù)據(jù)段
BSEG [AT 絕對地址表達(dá)式] //絕對位尋址段
復(fù)制代碼
它們的用法,舉一個(gè)例子:
例如我們寫串口中斷程序,起始地址是0x23.就這樣寫
CSEG AT 0X23
LJMP serialISR
RSEG ?PR?serialISR?TEST
. serialISR:
匯編函數(shù)使用同一個(gè)工程C文件中的變量,例如ICFLAG在C文件中定義,則匯編文件中定義方式為
EXTERN ICFLAG ;定義外部變量
定義函數(shù),例如
復(fù)制代碼
CARDATR:
...........
RET
GLOBAL CARDATR
復(fù)制代碼
在同一個(gè)工程文件下調(diào)用匯編中的函數(shù)CARDATR,則應(yīng)該定義函數(shù)
extern void CARDATR(void);
C18指定數(shù)據(jù)絕對地址
例如:
#pragma udata overlay RECBUFS =0x190 //200
UINT8 NUMBER;
UINT8 REC_BUF[31];
#pragma udata