目錄
1. 介紹
2. CPU 本地地址
3. 分頁窗口
4. 內存頁
5. 控制各個對象在內存中放置的位置
介紹
在一個S12或S12X架構中,很有必要分清楚兩種類型的內存地址:banked和non-banked。這篇文檔描述了應該怎么樣正確的訪問某個內存地址,同時還較詳細地描述了CodeWarrior的鏈接器是怎么把你的代碼分配在這兩種地址中的。理解你的應用是怎么使用內存的將有助於避免掉入常見的陷阱,還能幫助你發現哪里還有代碼優化的空間。
HCS12(X)的地址總線位數導致不是所有的內存地址都是平等的。由於HCS12(X) CPU地址總線的位寬是16位,它可以直接訪問的地址大小也就是16位可以訪問的大小。16位地址可以訪問的字節數為:2^16=65536字節,或者說64kB。當你有超過64kB的內存時,64kB外的地址就沒辦法用16位來編碼了。
Non-banked內存指的是那些可以直接通過16位地址來訪問的內存地址。
Banked內存指的是需要通過額外的行為來擴展HCS12(X) CPU尋址能力后才能訪問的內存地址。
Banked和noe-banked分別是分頁和非分頁的同義詞。分頁和非分頁這兩個詞源於內存頁這個主意,內存頁這個概念被用於擴展內存尋址能力。在Freescale的文檔中,這些同義詞常是可替換的。
為了理解一個應用是怎么訪問banked內存的,你需要理解以下三個概念:
- CPU本地地址(CPU Local Map)
- 分頁窗口(Page Window)
- 內存頁(Memory Page)
CPU本地地址
CPU本地地址這個詞指的是CPU可以通過它的指令集而直接訪問的64kB地址空間。這64kB尋址空間包含了不同類型的內存資源:寄存器地址,RAM,EEPROM和Flash。你可以把CPU本地地址看做是訪問這些物理地址的入口。
當讀或寫CPU本地地址的其中某個地址時,內存映射控制(MMC)模塊會把這個本地地址翻譯為另一個物理地址。MMC轉換本地的16位地址為一個不同的物理地址,這個物理地址使用23位來編碼(見圖1)。我們之所以強調這個是因為,在HCS12(X)中,這23位全局地址空間也可以通過特殊的指令直接訪問。MMC模塊被集成在芯片上以把特定的本地地址與特定的芯片上內存資源聯系起來。
圖 1. MMC模塊的地址翻譯過程
HCS12家族的CPU本地地址
圖2展示了對於一個HCS12設備,64kB空間內的地址是怎么聯系到特定的內存資源的。寄存器以及其他內存資源有着專用的地址范圍。這圖是S12DP512的。
對於HCS12家族,不同設備可能有不同的本地地址映射圖,然而它們卻有兩個共有特性:
- 首個共有特性是:雖然RAM、EEPROM和寄存器空間的訪問可能在每個設備上不同,但是在上電重置后,由MMC映射的RAM、EEPROM和寄存器的默認地址總是在本地地址的頭16kB(從0x0000到0x3FFF)內的。只有在HCS12家族中,你可以通過寫MMC模塊內特殊的INIT寄存器來修改EEPROM、RAM和寄存器空間的位置。欲知詳情,請查閱你的設備的文檔。
- 第二個共有特性是:低48kB(從地址0x4000到0xFFFF)存放着Flash內存。這個Flash區域被分為三個大小為16kB的塊。中間那個16kB,從0x8000到0xBFFF,被稱為Flash分頁窗口(見 分頁窗口)。
.
圖 2.HCS12的CPU本地地址映射
HCS12X家族的CPU本地地址
在HCS12(X)家族中,所有設備的CPU本地地址映射是一致的。圖3展示了64kB地址空間是怎么分配給特定內存資源的。所有HCS12(X)設備的CPU本地地址映射的分布是一致的。
圖 3.HCS12(X)的默認CPU本地地址映射
分頁窗口
在HCS12和HCS12X架構中,分頁窗口的概念是一致的。在CPU本地地址內的大部分地址總指向定義好的固定物理位置。然而一些特定的地址不總指向同樣的物理位置。這些特別的地址范圍被稱為分頁窗口(page windows)。在一個分頁窗口內的本地地址是16位的,這16位不足以讓MMC模塊確定其對應物理位置。
對於分頁窗口范圍內的本地地址,MMC模塊需要額外的信息來翻譯給定的本地地址為想要的物理地址,這個信息被存儲在一個寄存器內。存儲這個信息的寄存器被稱作分頁寄存器(page register)。
HCS12架構只包含一個用於Flash內存訪問的分頁窗口。這個分頁窗口位於地址0x8000到0xBFFF。其對應的分頁寄存器是PPAGE寄存器,這寄存器用於選擇Flash分頁窗口實際指向哪一部分物理內存。
改變PPAGE寄存器的值會改變映射在CPU本地地址的分頁窗口內的內容。
HCS12X CPU本地地址包含三個分頁窗口:一個用於EEPROM、一個用於RAM、還有一個用於Flash。每個分頁窗口使用特定的分頁寄存器來選擇其實際指向的物理內存。EPAGE分頁寄存器用於EEPROM的分頁窗口;RPAGE用於RAM的分頁窗口;PPAGE用於Flash的分頁窗口。
改變一個分頁寄存器的值將改變本地地址的對應分頁窗口中映射的內容。
內存頁
內存頁是物理內存的一塊有固定大小的連續部分。頁大小與是什么內存資源有關:EEPROM是1kB,RAM是4kB,Flash是16kB。一種內存資源的內存頁的大小與這內存資源在本地地址中對應資源的分頁窗口大小有關。
這種對物理內存的分頁只是一種概念上的划分。內存在物理上並不是真的分成了一頁一頁的。每個分頁使用一個分頁號來標識。為了讓某個分頁映射到分頁窗口內,需要將其分頁號寫進對應的分頁寄存器。分頁號在芯片集成時就定義好了。定分頁號的方案如下:
- 在HCS12家族中,分頁被按順序編號,最后一頁內存的分頁號固定為0x3F。比如:如果你的S12設備有32kB的Flash,那么Flash會被概念上分成兩個16kB的頁,分頁號分別為0x3E和0x3F。如果它有48kB的Flash,就會有3個分頁,分頁號分別為0x3D、0x3E和0x3F。
- 在HCS12(X)家族中,分頁被按順序編號,最后一頁內存的分頁號固定為0xFF。比如:如果你的S12X設備有8kB的RAM,RAM將會被概念上分為兩個4kB頁,分頁號為0xFE和0xFF。如果它有16kB的RAM,那么就會有4個RAM分頁。
- 注意:
- 所有的分頁和分頁號的概念只在訪問banked地址時才有用, 你必須要明白,這種概念上的划分為編號分頁是對整個內存資源的划分。在某個內存資源內的任何地址都有對應的分頁號,不管你用不用的到它。換句話說,不管某個內存地址會不會被通過頁面切換機制訪問或者直接地訪問,它就在那里。
分頁切換機制
為了在本地地址的分頁窗口中訪問一個特定的物理頁,需要先把其分頁號寫入分頁寄存器。比如,如果你是用高級語言寫的代碼,比如用C,CodeWarrior編譯器會負責產生合適的指令,對分頁寄存器的操作對用戶是透明的(譯者注:“透明”即你不需要管它)。這樣,用戶只需要確保編譯器正確地理解了哪些變量或函數是放置在banked地址中的,以使編譯器能產生所需的額外指令。這是通過在工程創建時選擇個合適的地址模型或者通過在聲明變量或函數時使用特殊的語法來實現的。
一旦需要的物理內存頁被映射到了分頁窗口內,CPU就可以使用16位尋址來訪問其中的數據了。
- 注意:
- 你可以通過分頁窗口來訪問任意分頁內存資源的任意內容。但是,每次你需要訪問這樣一個內存地址之前的那個寫分頁寄存器的操作會帶來可觀的開銷。這就是為什么有些特定地址被直接映射到本地地址上,它們與任何分頁寄存器的值無關,被稱為non-banked,或者非分頁地址。對於這些地址,通常不會使用分頁訪問,而是更喜歡使用直接訪問。
HCS12設備的分頁切換
圖4展示了CPU本地映射,其中non-banked地址上標記了它們總是映射的地址的對應分頁號。后面的例子示例了HCS12設備的頁面切換。
圖 4. CPU本地地址映射
HCS12下non-banked地址的例子
本地地址值0xC000對應於一個non-banked地址。根據圖4 這個地址指向Flash分頁0x3F的首個字節。讀寫地址0xC000總是會訪問同一個物理地址,不管你怎么設置分頁寄存器的值。
你也可以通過向PPAGE寄存器寫入0x3F的方式來訪問物理Flash分頁0x3F的首個字節。這樣Flash分頁0x3F的所有內容就會出現在0x8000到0xBFFF間了。Flash分頁0x3F的首個字節出現在本地地址0x8000處。
這兩種方式都是正確的;但是直接訪問0xC000的開銷更小,因此更推薦。
HCS12下banked地址的例子
假設你想要讀Flash分頁0x3C的首個字節。這種情況下,Flash分頁0x3C無法在CPU本地地址的non-banked地址找到(參照圖4)。唯一的解決方案是使用Flash分頁窗口來訪問。應用必須向PPAGE寄存器中寫入0x3C,然后訪問位於本地地址0x8000的這個分頁的首個字節。
HCS12X設備的分頁切換
圖5 在HCS12X的本地地址中的non-banked區域上標記了其對應指向的物理地址分頁號。后面跟着的例子示例了HCS12X設備的頁面切換。
圖 5. HCS12X帶有分頁號的本地地址映射
HCS12X下non-banked地址的例子
本地地址0xC000對應於一個non-banked地址,它不屬於任何分頁窗口。根據圖5,這個地址指向Flash分頁號0xFF的首個字節。讀寫地址0xC000總是訪問通過物理地址,跟任何分頁寄存器的值無關。
你也可以通過向PPAGE寄存器寫入0xFF的方式來訪問物理Flash分頁0xFF的首個字節。這樣Flash分頁0xFF的所有內容就會出現在0x8000到0xBFFF間了。Flash分頁0xFF的首個字節出現在本地地址0x8000處。
這兩種方式都是正確的;但是直接訪問0xC000的開銷更小,因此更推薦。
HCS12X下banked地址的例子
假設你想要讀Flash分頁0xFC的首個字節。這種情況下,Flash分頁0xFC無法在CPU本地地址的non-banked地址找到(參照圖5)。唯一的解決方案是使用Flash分頁窗口來訪問。應用必須向PPAGE寄存器中寫入0xFC,然后訪問位於本地地址0x8000的這個分頁的首個字節。
在HCS12和HCS12X的例子中,CodeWarrior的C編譯器都會負責自動地在訪問一個分頁地址之前自動地插入指令來寫對應的分頁寄存器。然而,為了確保這件事情會發生,程序員需要選擇最合適應用的地址模型,並最終使用特別的標識符,如關鍵詞__near或__far,或者#pragma聲明來在需要的地方修改編譯器行為。
HCS12X設備的全局訪問
在S12架構中,一個對象可以有的最大大小是16kB。這是受限於CPU本地地址映射,在其中,同時可被CPU訪問的最大連續內存空間是16kB。在S12架構中,企圖分配一個大於16kB的對象會導致一個鏈接器錯誤。
為了減少這個限制,在S12X架構中,引入了另一種尋址方法:全局訪問。
全局訪問使得可以訪問,在一個“新的”地址空間中的,最大64K的連續內存區域,這個空間被叫做全局地址空間。
在S12X CPU看來,有兩種可以訪問數據的64kB地址空間:
- 64kB CPU本地地址
- 64kB 全局地址
.
這64kB全局地址完全獨立於64kB本地地址
為了指示CPU訪問全局地址而不是更常使用的本地地址,程序員需要使用特殊的全局指令。這些全局指令有:GLDAA、GLDAB、GLDD、GLDS、GLDX、GLDY、GSTAA、GSTAB、GSTD、GSTS、GSTX 和 GSTY(詳見CPU Block Guide)。
示例
- GLDAA $100 向累加器A中加載存儲在全局地址0x100處的值
- LDAA $100 向累加器A中加載存儲在本地地址0x100處的值
.
我們已經學習了分頁窗口和分頁寄存器的概念,對於64kB全局地址,我們可以把它理解為一個64kB的分頁窗口,其中的內容取決於第四個分頁寄存器—GPAGE。
全局地址是個覆蓋了8Mb地址空間的23位地址,從地址0x000000到0x7FFFFF。在這個線性全局地址空間中,所有的內存資源都被分組,GPAGE寄存器可以被用於訪問所有的RAM、EEPROM和FLASH地址,以及外部地址空間。
在向GPAGE中寫入正確的值后,全局地址的內容就只能通過全局指令來訪問了。
GPAGE的值的選取方式與其他分頁寄存器的方式很相似:
- 寫0xFF到GPAGE中會使全局地址映射總地址的最后64kB。
- 寫0xFE會映射物理地址的的倒數第二個64kB。
什么時候使用全局尋址
使用全局尋址主要是由於兩個原因:
- 當需要鏈接非常大的對象,大到由於本地地址中沒有足夠的連續地址空間而無法被鏈接時。這種情況下,使用全局尋址使得程序員能夠通過全局指令來訪問高達64kB的連續地址空間。可被鏈接的單個數據對象的最大大小是64kB。
- 當在運行被分頁存儲的代碼的同時,試圖訪問同一種內存資源中的被分頁存儲的對象(變量或常量)。
.
比如,當應用需要訪問分配在某個給定Flash分頁中的常量,但當前執行的代碼卻跑在一個不同的Flash分頁中。通常,當在S12架構中遇到這種情況時,會使用一個non-banked運行時例程來訪問分頁的對象。
在S12X中,當在分頁Flash中運行時,可以使用這個新的全局訪問方式來訪問Flash中的任何地址,不需要碰PPAGE寄存器,也不需要跳到一個non-banked例程。
為了指示編譯器使用全局訪問來訪問某個對象,可以把它聲明在一個 #pragma DATA_SEG __GPAGE_SEG 塊或#pragma CONST_SEG __GPAGE_SEG 塊中,這取決於對象的特性。
S12X本地地址再映射能力
在新的S12X設備上,MMC模塊可以由用戶來配置0x4000到0x7FFF之間的CPU本地地址。這部分的本地地址默認是用來映射Flash的,但是它可以被配置來映射RAM或者外部空間,因此給用戶提供了更大的靈活性,更靈活的配置哪些地址是non-banked的。請參考S12X編譯器手冊的編譯器選項 -Map以及你的設備的datasheet,MMC模塊,來獲取更多關於這一特性的信息。
現在你已經見識到了訪問內存地址的兩種不同的方式。下一章會描述,怎么指示CodeWarrior鏈接器來放置我們的代碼和變量到需要的內存地址中。我們還將看到,當使用C語言來開發時,怎么確保CodeWarrior的編譯器知道某個對象應該放在banked還是non-banked內存地址中,以產生合適的代碼。
控制對象在內存中的分配
這個部分描述了CodeWarrior連接器默認是怎么在內存中放置對象的,以及怎么改變這個默認行為以定制我們的應用。
名詞 對象(objects) 指的是在內存中有固定地址的實體。可以是:
- 函數(代碼)
- 變量(放置在RAM中的數據和數組)
- 常量(放置在Flash中並被標識為”const”的數據)
- 字符串(沒有被預定義為數組的字符串字面值)
比如:printf( “Hello World”)將會產生字符串”Hello World”。相反地,聲明一個變量為
unsigned char Message[] = “Hello World”;
的話,鏈接器會認為它是一個數組而不是一個字符串。
.
對象的位置由#pragma聲明來控制。后面的內容並不是對#pragma聲明的完全描述,只列出了那些常用的而已。如果想要詳細描述的話,請參考放在你的CodeWarrior安裝路徑下編譯器和搭建工具的手冊。
我們將看到四種#pragma聲明:
- #pragma CODE_SEG
- #pragma DATA_SEG
- #pragma CONST_SEG
- #pragma STRING_SEG
.
這四種聲明都可以插入到C源文件中,並用於控制聲明后面的對象的位置和特性。
怎么使用pragma聲明來控制對象的位置
讓我們先看一個例子。這里,一個變量、一個常量和一個函數被放在一個明確的placement section中。placement section是指向內存中特定區域的標簽,被定義在項目的鏈接器參數文件(*.prm文件)的PLACEMENT塊中。下一章中給出了鏈接器參數文件的結構,用於參考。
unsigned char variable1; const unsigned char constant1; void function1(void) { /* 代碼 */ }
. #pragma聲明並不是強制的。在上例中就沒有#pragma聲明。這種情況下,鏈接器使用默認行為並將放置對象到他們的默認位置。
- DEFAULT_ROM 是代碼的默認位置
- DEFAULT_RAM 是變量和數組的默認位置
- ROM_VAR 是常量(ROM變量)的默認位置
.
DEFAULT_ROM、DEFAULT_RAM 和 ROM_VAR是定義在鏈接器參數文件的PLACEMENT塊內標簽。他們是CodeWarrior認識的特殊關鍵字。鏈接器認得它們,比如,ROM_VAR是常量在缺少#pragma聲明時應該被放置的位置。
在這個例子中:
- variable1 將被放在 DEFAULT_RAM
- constant1 將被放在 ROM_VAR
- function1 將被放在 DEFAULT_ROM
.
鏈接器參數文件定義了每個placement section對應的地址范圍。
下一個例子說明了#pragma聲明的用法,以防萬一用戶想要修改連接器的默認行為
#pragma DATA_SEG MYVARIABLES (1) unsigned char variable1; #pragma DATA_SEG DEFAULT (2) unsigned char variable2; #pragma CONST_SEG MYCONSTANTS (3) const unsigned char constant1; #pragma CONST_SEG DEFAULT (4) const unsigned char constant2; #pragma CODE_SEG MYCODE (5) void function1(void) { /* function1 內的代碼 */ } #pragma CODE_SEG DEFAULT (6) void function2(void) { /* function2 內的代碼 */ }
這里有一些定義#pragma聲明的行為的規則:
- #pragma聲明只對相關的對象起作用。比如,一條#pragma DATA_SEG聲明不會影響常量的位置。只有#pragma CONST_SEG聲明可以影響常量對象的位置。
- #pragma聲明只對聲明在#pragma后面的對象產生影響,並且一直影響到遇到另一個同特性的#pragma語句或者到編譯單元(見以下注釋)的結束。
.
在上例中,有六個#pragma聲明,它們被編號以便於后文好引用它們。
在這個例子中,我們假設標簽MYVARIABLES、MYCONSTANTS和MYCODE是由用戶定義在項目鏈接器參數文件內的placement section的名字。
- #pragma聲明(1) 會導致variable1被分配在placement section MYVARIABLES。
- #pragma聲明(2) 會結束pragma聲明(1)的影響。它會導致variable2被分配在它的默認位置,也就是DEFAULT_RAM。
- #pragma聲明(3) 會導致constant1被分配在placement section MYCONSTANTS。
- #pragma聲明(4) 會結束pragma聲明(3)的影響。它會導致constant2被分配在它的默認位置,也就是ROM_VAR。
- #pragma聲明(5) 會導致function1被分配在placement section MYCODE。
- #pragma聲明(6) 會結束pragma聲明(5)的影響。它會導致function2被分配在它的默認位置,也就是ROM_VAR。
- 注意:
- 我們之前提到過,一條#pragma聲明的影響一直會持續到遇到下一個#pragma聲明,或者到達編譯單元的末尾。一個編譯單元相當於源文件加上所有它包含進的頭文件。這意味着在一個*.h頭文件內的#pragma聲明可以影響那些包含了它的源文件內的對象的位置。這可能對於開發者來說是使很難追蹤的。這就是為什么總是像上例中那樣在一個#pragma聲明的后面明確寫出一個#pragma DEFAULT 聲明是一個好的編程實踐。
- 注意:
- 在上例中,只有鏈接器的默認行為被修改了。鏈接器的工作在所有對象被給定地址后就結束了。在上例中,用到的placement section可以是banked或non-banked的。鏈接器在一定程度上不關心這些。生成訪問地址的指令是編譯器的活。開發者要負責確保編譯器知道哪些對象放置在banked地址以及non-banked地址中,以使編譯器能生成正確的訪問指令。比如:是否需要操作分頁寄存器。
許多開發者使用默認的編譯器設置。這樣就不需要為應用中的不同對象分別定制編譯器行為了。默認情況下編譯器的尋址行為被稱為地址模型(memory model)。這在下一章中會討論。
編譯器和鏈接器的默認行為
當使用項目向導創建一個新的CodeWarrior項目時,用戶被要求選擇一個地址模型,選項有:Small、banked和large地址模型。選擇的地址模型將決定CodeWarrior的鏈接器會默認地會把你的代碼以及變量放在哪里,以及決定CodeWarrior的編譯器會怎么產生訪問你的對象的指令。
這里描述了每個地址模型:
- Small memory model:你的代碼和變量都會默認放在non-banked位置。
- Banked memory model:你的代碼默認會放在banked地址中,但是你的變量會默認放在non-banked地址中。
- Large memory model:你的代碼和變量都會默認放在banked地址中。
.
選擇地址模型將會影響你的項目中的三個元素:
- 編譯器選項
- ANSI庫
- 鏈接器參數文件
項目的編譯器選項
編譯器的行為受到選擇的地址模型影響。CodeWarrior項目向導在你項目的編譯器選項中插入一個 -M 選項。有三種參數,取決於模型:-Ms、-Mb或-Ml。這個選項指示編譯器根據模型的假定來編譯。
Small地址模型對應選項 -Ms。編譯器不會插入任何指令來處理任何分頁寄存器。變量將會被直接訪問non-banked地址,並且你的代碼會使用JSR/RTS指令來執行。
Banked地址模型對應選項 -Mb。當訪問你的代碼的時候,編譯器會使用處理PPAGE寄存器的指令。在調用一個函數的時候會使用CALL指令。CALL指令會負責在運行你的函數前把它的分頁號寫到PPAGE寄存器中。變量則會默認的按照non-banked地址的方式訪問。
Large地址模型對應選項 -Ml。編譯器將使用CALL指令來訪問你的代碼,並且在訪問RAM和EEPROM變量前也會插入分頁處理指令,它們被默認放在分頁地址。因此這個地址模型對代碼大小和執行時間非常不友好,在大部分情況下不推薦。
如果你需要訪問分頁區變量,大部分情況下用這個方法就夠了:選擇banked地址模型,然后每次變量要放到分頁區的時候都用特別標識符來告知編譯器。文檔的后面部分會討論怎么訪問分頁區變量。這使得你可以只在需要訪問分頁區變量時才進行分頁訪問,而不是默認對所有變量都這么訪問。
- 注意:
- JSR/RTS和CALL/RTC指令會在后面部分被簡短的討論
項目的ANSI庫
在項目向導中選擇一個地址模型還會添加對應的ANSI庫。通過預編譯的*.lib文件加進項目的ANSI庫需要與你項目選擇的編譯器選項兼容。
項目的鏈接器參數文件
這是指示鏈接器要到哪里放置你的代碼的文件。根據選擇的地址模型,不同的參數文件會被加入你的項目中。
圖6展示了由CodeWarrior項目向導為一個S12XEP100設備創建的參數文件,這個項目中沒有使用XGATE。
我們就僅僅看下small和banked地址模型對應的prm文件的PLACEMENT塊,。
圖 6. small地址模型
圖 7. banked地址模型
所有的藍顏色的標簽都是CodeWarrior認得的特別關鍵詞。這些標簽都有特別的用處:
- DEFAULT_ROM 是你的代碼會被默認分配的地方,也就是當沒有#pragma CODE_SEG聲明時的。它在參數文件中是強制要有的。
- DEFAULT_RAM 是你的變量會被默認分配的地方,也就是當沒有#pragma DATA_SEG聲明時的。它是參數文件中強制要有的字段。
- __PRESTART 指示啟動代碼要放在哪里。
- STARTUP 是Startup數據結構被放置的地方
- ROM_VAR 是存儲常量的默認位置(用“const”聲明的變量)
- STRINGS是你的字符串字面值默認會被分配的地方,也就是當沒有#pragma STRING_SEG聲明時的。(字符串字面值是傳遞給函數的字面值,比如printf(“hello world”); 中使用的”hello world”)
- NON_BANKED 是一個由庫使用的特殊標簽,用於存放那些必須non-banked的對象。這個標簽也可以被程序員在他的代碼中使用。
- VIRTUAL_TABLE_SEGMENT是C++應用專用的。
- COPY是ram對象的初始化值會被分配的地方。
比如,當你這樣聲明一個變量:
unsigned char variable=0xAA;
0xAA這個值就被存放到Flash資源的COPY部分了。啟動代碼會在每次重置后復制這個值到相應“變量”的位置。 - SSTACK 是你的棧被放置的地方。SSTACK的大小是由命令STACKSIZE決定的,這命令也出現在prm文件中。
.
任何黑顏色的文本都不是特殊關鍵詞。(見圖6和圖7)在這里,使用的黑顏色的標簽只是為了示范的目的。在這里就是指上圖中的標簽PAGED_RAM和OTHER_ROM。CodeWarrior不特別地使用這些標簽。它們是程序員用於在應用代碼中使用#pragma聲明使用的。
small和banked地址模型參數文件間最本質的區別是DEFAULT_ROM標簽的位置。
small地址模型參數文件不使用分頁Flash地址。標簽OTHER_ROM指向分頁地址,但是不被由向導創建的項目使用。OTHER_ROM放在那只是給程序員在需要的時候使用的。
你必須要明白,在任何地址模型中你都可以使用分頁或者非分頁對象。地址模型只是影響默認位置和默認編譯器行為。
在你的代碼中,你總是可以通過使用特殊的語法以本地地改變默認行為。
比如,為了在banked地址模型中使用banked變量,需要額外輸入一些東西。后面的部分講了些編碼的注意事項。
改變編譯器默認行為
改變對代碼的默認訪問方式
最高效地訪問放置在non-banked地址內的函數的方式是通過JSR(跳轉到子例程)/RTS(從子例程返回)指令對。JSR/RTS指令對不處理任何分頁寄存器,並使用16位地址來跳轉。
另一方面,為了訪問放置在banked地址內的函數,必須使用CALL/RTC(從call返回)指令對。CALL/RTC指令對會處理PPAGE寄存器。
為了在調用一個函數的時候強制使用JSR/RTS指令對,那個函數必須使用“__near”聲明。
示例:
void __near myfunction(void);
為了在調用一個函數的時候強制使用CALL/RTC指令對,那個函數必須使用 “__far”聲明。
示例:
void __far myfunction(void);
改變對變量的默認訪問方式
如果一個變量放置在non-banked地址中,不需要使用什么關鍵詞來保證正確的訪問。
在S12架構中,只有內部的Flash內存資源有分頁,所以唯一的情景就是要訪問的那個數據被放在分頁Flash中,或者說,那是個分頁區常量。見下例:
示例 1:在S12架構中訪問分頁區常量
清單 1:在S12架構中訪問分頁區常量
#pragma CONST_SEG PAGEDCONSTANTS volatile const unsigned int __far constant1=0xAAAA; #pragma CONST_SEG DEFAULT unsigned int variable1; void main(void) { variable1=constant1; for(;;) {}
清單 1 展示了把constant1的值讀入variable1。constant1被放置在分頁Flash地址。圖8 展示了鏈接器參數文件的對應位置。
圖 8. 鏈接器參數文件
注意,這里在constant1的聲明中使用了 __far 標識符。這個__far標識符指示編譯器在訪問constant1的地址之前處理PPAGE寄存器。因為在S12架構中只有一個分頁寄存器,就沒必要講更多了。
在S12X架構中,我們得說明哪一個分頁寄存器與哪個變量相關。下例進行了說明。
示例 2:在S12X架構中訪問分頁區變量
當一個變量被分頁存儲,程序員需要告知編譯器,這個變量的地址與哪個寄存器相關。這是通過使用#pragma聲明來實現的。
使用關鍵詞 __RPAGE_SEG、__EPAGE_SEG、__PPAGE_SEG 和__GPAGE_SEG來分別告知編譯器去處理RPAGE、EPAGE、PPAGE 和 GPAGE寄存器。
在Banked RAM區的變量
在訪問一個banked RAM地址之前,編譯器需要插入 寫RPAGE寄存器 的指令。對應的語法如下:
#pragma DATA_SEG __RPAGE_SEG PAGED_RAM
在Banked EEPROM區的變量
在訪問一個banked EEPROM地址之前,編譯器需要插入 寫EPAGE寄存器 的指令。對應的語法如下:
#pragma DATA_SEG __EPAGE_SEG MY_EEPROM
在Banked Flash區的常量
在訪問一個banked FLASH地址之前,編譯器需要插入 寫PPAGE寄存器 的指令。對應的語法如下:
#pragma CONST_SEG __PPAGE_SEG OTHER_ROM
我們看看怎么訪問一個分頁RAM區變量,和一個分頁ROM區變量:
#pragma DATA_SEG __RPAGE_SEG PAGED_RAM (1) unsigned int variable1; #pragma DATA_SEG DEFAULT #pragma CONST_SEG __GPAGE_SEG PAGED_ROM (2) volatile const unsigned int constant1=0xAAAA; #pragma CONST_SEG __GPAGE_SEG DEFAULT void main(void) { variable1 = constant1; for(;;) {} /* wait forever */ /* please make sure that you never leave this function */ }
圖 9 展示了這個例子的鏈接器參數文件。
圖 9. 鏈接器參數文件
在這個例子中,#pragma聲明(1) 被用來指示連接器把variable1放置在叫做PAGED_RAM的section中。同時,由於有__RPAGE_SEG標識符,我們告知編譯器在訪問這個變量之前需要先處理RPAGE寄存器。
. #pragma聲明(2) 被用來指示連接器把常量constant1放置在叫做PAGED_ROM的section中。同時,告知編譯器在訪問受這個#pragma影響的常量之前需要先處理GPAGE寄存器。
這是個有意思的選擇。我們故意選擇GPAGE寄存器來訪問分頁Flash區數據。因為在這個例子中,我們的代碼存放的DEFAULT_ROM的位置也在個分頁Flash區。因此當執行main函數時,PPAGE寄存器需要被設置為0xFC(見圖9),這樣CPU才可以讀取在Flash分頁窗口內的主函數。當在Flash分頁窗口內執行時,PPAGE值絕對不能改變,否則CPU就會跑飛。這就是為什么,為了訪問另一個Flash頁的地址,我們不再使用PPAGE寄存器,而是使用了GPAGE寄存器。
這是利用GPAGE寄存器的一個典型例子。
這篇文檔到這里就結束了。想要獲得關於鏈接器和編譯器行為的更多信息的話,請參考位於CodeWarrior安裝路徑下的搭建工具與編譯器手冊。代碼示例可以在CodeWarrior安裝路徑內的文件夾(CodeWarrior_Examples)里找到。