HCS12X–資料定義(如何在CodeWarrior中將資料定義到分頁區)
由於在暑假匆忙接收的嵌入式專案中需要使用特別大的陣列,非分頁RAM的記憶體不夠用了,沒辦法,硬著頭皮嘗試使用分頁RAM,但是完全沒有微控制器的基礎,導致極其的困難。之前寫程式都是按照純軟體的思維,主要考慮架構,不會考慮到每個變數具體存在哪個實體地址這麼底層的問題,結果被飛思卡爾這分頁地址、prm檔案什麼的搞得一頭霧水,而網上的資料又少,講的又大同小異的籠統,最後寫出來的程式因為這分頁地址的原因存在各種問題(還以為把變數放到分頁RAM了,結果現在稍微懂了點回去看,發現其實很多根本還是分配在非分頁區。暈倒~。但是居然還能相對正常執行也是很神奇)。這些天各種找相關的資料,結果發現在CodeWarrior的官方文件資料裡其實把我想知道的都講的很清楚了(還是官方文件給力,以後學什麼東西直接找官方文件,不去到處找網上一堆零零散散的資源來學了)。本著學習的態度,將逐步把官方文件翻譯一遍,供大家一起交流學習進步。
HCS12X – 資料定義
譯者注:譯者部落格(http://blog.csdn.net/lin_strong),轉載請保留這條。此為官方文件TN238,僅供學習交流使用,請勿用於商業用途。
這個文件描述了程式設計師要怎麼樣幫助HCS12X編譯器來產生在資料訪問上更加優化的程式碼。我們將討論以下主題:
- 分配在直接定址區的變數
- 分配在擴充套件定址區的變數
- 分配在banked定址區的變數 — 使用邏輯定址
- 分配在banked定址區的變數 — 使用全域性定址
- Banked常量分配
- 邏輯地址 vs. 全域性地址
對於以上舉例的每個變數型別,我們都會描述:
- 怎麼定義一個變數
- 怎麼宣告一個變數
- 生成來分為這變數的程式碼
- 怎麼定義一個指向這樣一個變數的指標
- 生成來訪問指標和其指向的變數的程式碼
- PRM檔案中的位置
- 注意1:
- 這篇技術文件中描述的資訊適用於SMALL(-Ms)以及BANKED(-Mb)地址模型。而不適用於LARGE(-Ml)地址模型。
- 注意2 :
- 對於少於32Kb程式碼的應用,我們通常推薦使用SMALL地址模型,或者使用BANKED地址模型。我們不推薦使用LARGE地址模型。
分配在直接定址區的變數
為了使編譯器將某個變數分配到直接定址頁,你必須定義(以及宣告)它到個特別的segment中,這segment帶有屬性__SHORT_SEG。
- 定義&訪問直接定址區的資料
1 像這樣進行變數定義:
#pragma DATA_SEG __SHORT_SEG MyShortData
unsigned char rub_short_var;
#pragma DATA_SEG DEFAULT
2 像這樣進行變數宣告:
#pragma DATA_SEG __SHORT_SEG MyShortData
extern unsigned char rub_short_var;
#pragma DATA_SEG DEFAULT
3 對這變數的訪問會生成如下程式碼:
37: rub_short_var = 2;
00E08002 C602 [1] LDAB #2
00E08004 5B08 [2] STAB $08
4 沒有用於訪問直接定址頁變數的特殊指標型別。一個指向這樣一個變數的指標將像這樣定義:
unsigned char* ptr_on_short_var;
5 初始化和訪問被指向的物件會產生如下程式碼:
46: ptr_on_short_var = &rub_short_var;
00E0801F 180320102102 MOVW #8208,$2102
47: (*ptr_on_short_var)++;
00E08025 62FBA0D9 INC [$A0D9,PC] /* [ptr_on_short_var,PCR] */
6 對於定義在__SHORT_SEG section的變數,PRM檔案中的SEGMENT和PLACEMENT是這樣子的:
SEGMENTS
DIRECT_PAGE = READ_WRITE 0x2010 TO 0x20FF;
/* 這裡省略了其他segment定義 */
END
PLACEMENT
MyShortData INTO DIRECT_PAGE;
/* 這裡省略了其他placement定義 */
END
- 注意:
- 包含通過直接定址模式訪問的變數的section,應該被分配在用邏輯地址定義的segment中
配置直接定址區
在HCS12X上,直接定址頁可以移動,並且不是硬編碼到0x00..0xFF(HCS12上是硬編碼到這的)。編譯器需要進行特別的設定來支援這一特性。
- 如果DIRECT暫存器包含任何東西但是預設值是0(譯者注:意思是存在DIRECT暫存器?),編譯器和彙編器就要啟用-CpDirect選項,其後跟著可直接訪問區的起始地址。比如:如果DIRECT初始化為0x20,那麼直接訪問視窗就是從0x2000到0x20FF,編譯器和彙編器選項就是-CpDirect0x2000。編譯器和彙編器都要加上這個選項。
- DIRECT暫存器必須由使用者程式碼來初始化。預設的啟動程式碼不初始化DIRECT。
- 在prm檔案中,section MyShortData要被分配到這個區域(在上例中是從0x2000到0x20FF)。
- 小心,不正確地分配直接訪問section(也就是說MyShortData沒有被正確地分配)不會產生連結器診斷訊息。
對於HCS12,連結器會報告一個fixup溢位。但是對於HCS12X卻無法做到這一點,因為直接訪問頁可以被對映。
分配在擴充套件定址區的變數
為了使編譯器將某個變數分配到擴充套件地址空間,你只需要用普通的方式定義(並宣告)它。
1 像這樣進行變數定義:
unsigned char rub_var;
2 像這樣進行變數宣告:
extern unsigned char rub_var;
3 對這變數的訪問會生成如下程式碼:
39: rub_var =7;
00E08002 C607 [1] LDAB #7
00E08004 7B2001 [3] STAB $2001
4 指向這個變數的指標可以像這樣定義:
unsigned char * ptr_on_var;
5 初始化和訪問被指向的物件會產生如下程式碼:
57: ptr_on_var = &rub_var;
00E0805F 18032100210C MOVW #8448,$210C
58: (*ptr_on_var)++;
00E08065 62FBA0A3 INC [$A0A3,PC] /* [ptr_on_var,PCR] */
6 這種方式定義的變數被分配在預定義section DEFAULT_RAM中。在PRM檔案中,這個section的SEGMENT和PLACEMENT定義是這樣子的:
SEGMENTS
RAM = READ_WRITE 0x2100 TO 0x3FFF;
/* 這裡省略了其他segment定義 */
END
PLACEMENT
DEFAULT_RAM INTO RAM;
/* 這裡省略了其他placement定義 */
END
- 注意:
- 包含使用擴充套件定址模式定義的變數的Section應該被分配在使用邏輯地址定義的segment中。
分配在Banked定址區的變數
分配在banked RAM中的變數可以使用以下來訪問:
- 邏輯地址(使用RPAGE) 或
- 全域性地址
使用邏輯地址
為了告訴編譯器你想要使用一個變數的邏輯地址來訪問它,你得使用如下語法:
1 像這樣進行變數定義:
#pragma DATA_SEG __RPAGE_SEG PAGED_RAM
unsigned char rub_far_var;
#pragma DATA_SEG DEFAULT
2 像這樣進行變數宣告:
#pragma DATA_SEG __RPAGE_SEG PAGED_RAM
extern unsigned char rub_far_var;
#pragma DATA_SEG DEFAULT
3 對這變數的訪問會生成如下程式碼:
38: rub_far_var = 5;
00E08002 C6FB [1] LDAB #251 /* #PAGE(rub_far_var) */
00E08004 5B16 [2] STAB $16 /* RPAGE */
00E08006 C605 [1] LDAB #5
00E08008 7B1000 [3] STAB $1000
4 指向這個變數的指標可以像這樣定義:
unsigned char * __rptr ptr_on_far_var;
5 初始化和訪問被指向的物件會產生如下程式碼:
50: ptr_on_far_var = &rub_far_var;
00E08029 180310002105 MOVW #4096,$2105
00E0802F 180BFB2104 MOVB #251,$2104
51: (*ptr_on_far_var)++;
00E08034 FE2105 LDX $2105 /* ptr_on_far_var:1 */
00E08037 F62104 LDAB $2104 /* ptr_on_far_var */
00E0803A 7B0016 STAB $0016 /* RPAGE */
00E0803D 6200 INC 0,X
6 對於定義在RPAGE section中的變數,對應PRM檔案中的SEGMENT和PLACEMENT是這樣子的:
SEGMENTS
RAM_FB = READ_WRITE 0xFB1000 TO 0xFB1FFF;
RAM_FC = READ_WRITE 0xFC1000 TO 0xFC1FFF;
RAM_FD = READ_WRITE 0xFD1000 TO 0xFD1FFF;
/* 這裡省略了其他segment定義 */
END
PLACEMENT
PAGED_RAM INTO RAM_FB, RAM_FC, RAM_FD;
/* 這裡省略了其他placement定義 */
END
- 注意:
- 包含通過邏輯定址模式訪問的變數的section,應該被分配在用邏輯地址定義的segment中
- 注意:
- 分配在RPAGE segment中的變數也可以通過使用far指標來訪問,
- unsigned char * __far ptr_on_far_var;
- 在這種情況下,將會使用全域性定址模式來訪問指向的物件
使用全域性地址
使用全域性地址的主要原因是因為一個物件可能沒法裝進單頁的邏輯地址中去。通過使用全域性地址訪問物件,就繞開了這個制約,物件就可以大到所有可用的記憶體大小或可訪問的64KB。
全域性地址指標可以用於任何物件,物件不受到任何特殊限制。
為了告訴編譯器你想要訪問一個使用全域性地址的變數,你要使用以下語法:
1 像這樣進行變數定義:
#pragma DATA_SEG __GPAGE_SEG PAGED_RAM
unsigned char rub_far_var;
#pragma DATA_SEG DEFAULT
2 像這樣進行變數宣告:
#pragma DATA_SEG __GPAGE_SEG PAGED_RAM
extern unsigned char rub_far_var;
#pragma DATA_SEG DEFAULT
3 對這變數的訪問會生成如下程式碼:
35: rub_far_var = 7;
00E0802B C607 [1] LDAB #7
00E0802D 860F [1] LDAA #15 /* #GLOBAL_PAGE(rub_far_var)*/
00E0802F 5A10 [2] STAA $10 /* GPAGE*/
00E08031 187BE00F [4] GSTAB $E00F
4 指向這個變數的指標可以像這樣定義:
unsigned char *__far ptr_on_far_var;
5 初始化和訪問被指向的物件會產生如下程式碼:
37: ptr_on_far_var = &rub_far_var;
00E08035 1803B0002101 MOVW #45056,$2101
00E0803B 180B0F2100 MOVB #15,$2100
38: (*ptr_on_far_var)++;
00E08040 FE2101 LDX $2101 /* ptr_on_far_var:1 */
00E08043 F62100 LDAB $2100 /* ptr_on_far_var */
00E08046 5B10 STAB $10 /* GPAGE */
00E08048 18A600 GLDAA 0,X
00E0804B 42 INCA
00E0804C 186A00 GSTAA 0,X
6 對於定義在 GPAGE section中的變數,對應PRM檔案中的SEGMENT和PLACEMENT是這樣子的:
SEGMENTS
RAM_BANKED = NO_INIT 0xF9000'G TO 0xFCFFF'G;
/* 這裡省略了其他segment定義 */
END
PLACEMENT
PAGED_RAM INTO RAM RAM_BANKED;
/* 這裡省略了其他placement定義 */
END
- 注意:
- 包含通過全域性定址模式訪問的變數的section可以被分配在使用邏輯或全域性地址定義的segment中。所以PRM檔案也可以看起來像這樣:
SEGMENTS
RAM_FB = READ_WRITE 0xFB1000 TO 0xFB1FFF;
RAM_FC = READ_WRITE 0xFC1000 TO 0xFC1FFF;
RAM_FD = READ_WRITE 0xFD1000 TO 0xFD1FFF;
/* 這裡省略了其他segments定義 */
END
PLACEMENT
PAGED_RAM INTO RAM_FB, RAM_FC, RAM_FD;
/* 這裡省略了其他placement定義 */
END
分頁常量分配
常量可以被分配在banked EEPROM或者在banked FLASH中,並且可以通過以下來訪問:
- EEPROM(使用EPAGE)中的邏輯地址 或
- FLASH(使用PPAGE)中的邏輯地址 或
- 全域性地址
在EEPROM中使用邏輯地址
為了告知編譯器你想要使用一個常量在EEPROM中的邏輯地址訪問它,你得使用如下語法:
1 像這樣進行常量定義:
#pragma CONST_SEG __EPAGE_SEG PAGED_CONST
const unsigned char cub_far_const=1;
#pragma CONST_SEG DEFAULT
2 像這樣進行常量宣告:
#pragma CONST_SEG __EPAGE_SEG PAGED_CONST
extern const unsigned char cub_far_const=1;
#pragma CONST_SEG DEFAULT
3 指向這個變數的指標可以像這樣定義:
const unsigned char *__eptr ptr_on_far_const;
- 注意:
- 分配在一個EPAGE segment的常量還可以使用far指標來訪問。
- const unsigned char *__far ptr_on_far_const;
- 這種情況下,會使用全域性定址模式來訪問指向的物件
在FLASH中使用邏輯地址
編譯器只支援那些分配在非分頁區的程式碼在FLASH中使用邏輯地址來定址。因為通常不會這樣做,我們推薦為所有在FLASH中的資料物件使用全域性地址。
為了實現使用邏輯地址,要使用segment qualifier __PPAGE_SEG 和pointer
qualifier __pptr。
使用全域性地址
1 像這樣進行常量定義:
#pragma CONST_SEG __GPAGE_SEG PAGED_CONST
const unsigned char cub_far_const=1;
#pragma CONST_SEG DEFAULT
2 像這樣進行常量宣告:
#pragma CONST_SEG __GPAGE_SEG PAGED_CONST
extern const unsigned char cub_far_const= 1;
#pragma CONST_SEG DEFAULT
3 指向這個變數的指標可以像這樣定義:
const unsigned char *__far ptr_on_far_const;
邏輯地址 vs 全域性地址
- 對於那些大小不大於4K(Banked RAM視窗的大小)的變數,使用邏輯地址比使用全域性地址更有效率。
事件 | 邏輯地址 | 全域性地址 |
---|---|---|
資料訪問(程式碼大小) xy=3; | 9位元組 | 10位元組 |
資料訪問(執行速度) xy=3; | 7週期 | 8週期 |
指標訪問(程式碼大小) *xx=4; | 11位元組 | 12位元組 |
指標訪問(執行速度) *xx=4; | 11週期 | 15週期 |
指標內容遞增(程式碼大小) (*xx)++; | 11位元組 | 15位元組 |
指標內容遞增(執行速度) (*xx)++; | 12週期 | 16週期 |
- 如果一個變數的大小大於4K,聯結器將不得不把它分配地橫跨bank的邊界。這種情況下,我們推薦使用全域性定址來訪問這個變數
- 我們推薦使用全域性定址模式來訪問分配在FLASH中的常量或字串常量
- 我們推薦為所有那些在執行時使用邏輯地址的物件使用邏輯定址。包括:棧,程式碼,直接定址的變數,擴充套件定址區的變數,I/O暫存器。
- 為了定義個分頁的變數,一定要使用DATA_SEG pragma。不要試圖使用 __far關鍵字來幹這件事。far關鍵字指定了編譯器應該怎麼訪問一個變數,但它不會影響變數被分配的方式。