刪除
由于在暑假匆忙接收的嵌入式項目中需要使用特別大的數(shù)組,非分頁RAM的內(nèi)存不夠用了,沒辦法,硬著頭皮嘗試使用分頁RAM,但是完全沒有單片機的基礎(chǔ),導致極其的困難。之前寫程序都是按照純軟件的思維,主要考慮架構(gòu),不會考慮到每個變量具體存在哪個物理地址這么底層的問題,結(jié)果被飛思卡爾這分頁地址、prm文件什么的搞得一頭霧水,而網(wǎng)上的資料又少,講的又大同小異的籠統(tǒng),最后寫出來的程序因為這分頁地址的原因存在各種問題(還以為把變量放到分頁RAM了,結(jié)果現(xiàn)在稍微懂了點回去看,發(fā)現(xiàn)其實很多根本還是分配在非分頁區(qū)。暈倒~。但是居然還能相對正常運行也是很神奇)。這些天各種找相關(guān)的資料,結(jié)果發(fā)現(xiàn)在CodeWarrior的官方文檔資料里其實把我想知道的都講的很清楚了(還是官方文檔給力,以后學什么東西直接找官方文檔,不去到處找網(wǎng)上一堆零零散散的資源來學了)。本著學習的態(tài)度,將逐步把官方文檔翻譯一遍,供大家一起交流學習進步。
翻譯的資料是公開的,我想應(yīng)該不會有什么版權(quán)問題,如涉及版權(quán)問題,請聯(lián)系我刪除文章,原文檔在這里(https://www.nxp.com/pages/codewarrior-development-studio-for-hcs12x-microcontrollers-classic-ide-v5.2:CW-HCS12X?&tab=Documentation_Tab&linkline=Users-Guides),另感謝NXP提供的學習資料。
理解S12(X)架構(gòu)中的地址映射方案by Christian Michel Sendis
譯者注:譯者博客(),轉(zhuǎn)載請保留這條。此為官方文檔AN3784,僅供學習交流使用,請勿用于商業(yè)用途。這篇文章中的memory(內(nèi)存)和map,譯者很多情況下直接翻譯為“地址”,譯者認為這篇文章中,很多情況下這幾個詞是同義詞,都是指存放字節(jié)的一個地方。
目錄
1. 介紹
2. CPU 本地地址
3. 分頁窗口
4. 內(nèi)存頁
5. 控制各個對象在內(nèi)存中放置的位置
在一個S12或S12X架構(gòu)中,很有必要分清楚兩種類型的內(nèi)存地址:banked和non-banked。這篇文檔描述了應(yīng)該怎么樣正確的訪問某個內(nèi)存地址,同時還較詳細地描述了CodeWarrior的鏈接器是怎么把你的代碼分配在這兩種地址中的。理解你的應(yīng)用是怎么使用內(nèi)存的將有助于避免掉入常見的陷阱,還能幫助你發(fā)現(xiàn)哪里還有代碼優(yōu)化的空間。
HCS12(X)的地址總線位數(shù)導致不是所有的內(nèi)存地址都是平等的。由于HCS12(X) CPU地址總線的位寬是16位,它可以直接訪問的地址大小也就是16位可以訪問的大小。16位地址可以訪問的字節(jié)數(shù)為:2^16=65536字節(jié),或者說64kB。當你有超過64kB的內(nèi)存時,64kB外的地址就沒辦法用16位來編碼了。
Non-banked內(nèi)存指的是那些可以直接通過16位地址來訪問的內(nèi)存地址。
Banked內(nèi)存指的是需要通過額外的行為來擴展HCS12(X) CPU尋址能力后才能訪問的內(nèi)存地址。
Banked和noe-banked分別是分頁和非分頁的同義詞。分頁和非分頁這兩個詞源于內(nèi)存頁這個主意,內(nèi)存頁這個概念被用于擴展內(nèi)存尋址能力。在Freescale的文檔中,這些同義詞常是可替換的。
為了理解一個應(yīng)用是怎么訪問banked內(nèi)存的,你需要理解以下三個概念:
CPU本地地址(CPU Local Map)
分頁窗口(Page Window)
內(nèi)存頁(Memory Page)
CPU本地地址CPU本地地址這個詞指的是CPU可以通過它的指令集而直接訪問的64kB地址空間。這64kB尋址空間包含了不同類型的內(nèi)存資源:寄存器地址,RAM,EEPROM和Flash。你可以把CPU本地地址看做是訪問這些物理地址的入口。
當讀或?qū)慍PU本地地址的其中某個地址時,內(nèi)存映射控制(MMC)模塊會把這個本地地址翻譯為另一個物理地址。MMC轉(zhuǎn)換本地的16位地址為一個不同的物理地址,這個物理地址使用23位來編碼(見圖1)。我們之所以強調(diào)這個是因為,在HCS12(X)中,這23位全局地址空間也可以通過特殊的指令直接訪問。MMC模塊被集成在芯片上以把特定的本地地址與特定的芯片上內(nèi)存資源聯(lián)系起來。
圖 1. MMC模塊的地址翻譯過程
圖2展示了對于一個HCS12設(shè)備,64kB空間內(nèi)的地址是怎么聯(lián)系到特定的內(nèi)存資源的。寄存器以及其他內(nèi)存資源有著專用的地址范圍。這圖是S12DP512的。
對于HCS12家族,不同設(shè)備可能有不同的本地地址映射圖,然而它們卻有兩個共有特性:
首個共有特性是:雖然RAM、EEPROM和寄存器空間的訪問可能在每個設(shè)備上不同,但是在上電重置后,由MMC映射的RAM、EEPROM和寄存器的默認地址總是在本地地址的頭16kB(從0x0000到0x3FFF)內(nèi)的。只有在HCS12家族中,你可以通過寫MMC模塊內(nèi)特殊的INIT寄存器來修改EEPROM、RAM和寄存器空間的位置。欲知詳情,請查閱你的設(shè)備的文檔。
第二個共有特性是:低48kB(從地址0x4000到0xFFFF)存放著Flash內(nèi)存。這個Flash區(qū)域被分為三個大小為16kB的塊。中間那個16kB,從0x8000到0xBFFF,被稱為Flash分頁窗口(見 分頁窗口)。
.
圖 2.HCS12的CPU本地地址映射
在HCS12(X)家族中,所有設(shè)備的CPU本地地址映射是一致的。圖3展示了64kB地址空間是怎么分配給特定內(nèi)存資源的。所有HCS12(X)設(shè)備的CPU本地地址映射的分布是一致的。
圖 3.HCS12(X)的默認CPU本地地址映射
在HCS12和HCS12X架構(gòu)中,分頁窗口的概念是一致的。在CPU本地地址內(nèi)的大部分地址總指向定義好的固定物理位置。然而一些特定的地址不總指向同樣的物理位置。這些特別的地址范圍被稱為分頁窗口(page windows)。在一個分頁窗口內(nèi)的本地地址是16位的,這16位不足以讓MMC模塊確定其對應(yīng)物理位置。
對于分頁窗口范圍內(nèi)的本地地址,MMC模塊需要額外的信息來翻譯給定的本地地址為想要的物理地址,這個信息被存儲在一個寄存器內(nèi)。存儲這個信息的寄存器被稱作分頁寄存器(page register)。
HCS12架構(gòu)只包含一個用于Flash內(nèi)存訪問的分頁窗口。這個分頁窗口位于地址0x8000到0xBFFF。其對應(yīng)的分頁寄存器是PPAGE寄存器,這寄存器用于選擇Flash分頁窗口實際指向哪一部分物理內(nèi)存。
改變PPAGE寄存器的值會改變映射在CPU本地地址的分頁窗口內(nèi)的內(nèi)容。
HCS12X CPU本地地址包含三個分頁窗口:一個用于EEPROM、一個用于RAM、還有一個用于Flash。每個分頁窗口使用特定的分頁寄存器來選擇其實際指向的物理內(nèi)存。EPAGE分頁寄存器用于EEPROM的分頁窗口;RPAGE用于RAM的分頁窗口;PPAGE用于Flash的分頁窗口。
改變一個分頁寄存器的值將改變本地地址的對應(yīng)分頁窗口中映射的內(nèi)容。
內(nèi)存頁內(nèi)存頁是物理內(nèi)存的一塊有固定大小的連續(xù)部分。頁大小與是什么內(nèi)存資源有關(guān):EEPROM是1kB,RAM是4kB,F(xiàn)lash是16kB。一種內(nèi)存資源的內(nèi)存頁的大小與這內(nèi)存資源在本地地址中對應(yīng)資源的分頁窗口大小有關(guān)。
這種對物理內(nèi)存的分頁只是一種概念上的劃分。內(nèi)存在物理上并不是真的分成了一頁一頁的。每個分頁使用一個分頁號來標識。為了讓某個分頁映射到分頁窗口內(nèi),需要將其分頁號寫進對應(yīng)的分頁寄存器。分頁號在芯片集成時就定義好了。定分頁號的方案如下:
在HCS12家族中,分頁被按順序編號,最后一頁內(nèi)存的分頁號固定為0x3F。比如:如果你的S12設(shè)備有32kB的Flash,那么Flash會被概念上分成兩個16kB的頁,分頁號分別為0x3E和0x3F。如果它有48kB的Flash,就會有3個分頁,分頁號分別為0x3D、0x3E和0x3F。
在HCS12(X)家族中,分頁被按順序編號,最后一頁內(nèi)存的分頁號固定為0xFF。比如:如果你的S12X設(shè)備有8kB的RAM,RAM將會被概念上分為兩個4kB頁,分頁號為0xFE和0xFF。如果它有16kB的RAM,那么就會有4個RAM分頁。
注意: 所有的分頁和分頁號的概念只在訪問banked地址時才有用,你必須要明白,這種概念上的劃分為編號分頁是對整個內(nèi)存資源的劃分。在某個內(nèi)存資源內(nèi)的任何地址都有對應(yīng)的分頁號,不管你用不用的到它。換句話說,不管某個內(nèi)存地址會不會被通過頁面切換機制訪問或者直接地訪問,它就在那里。分頁切換機制
為了在本地地址的分頁窗口中訪問一個特定的物理頁,需要先把其分頁號寫入分頁寄存器。比如,如果你是用高級語言寫的代碼,比如用C,CodeWarrior編譯器會負責產(chǎn)生合適的指令,對分頁寄存器的操作對用戶是透明的(譯者注:“透明”即你不需要管它)。這樣,用戶只需要確保編譯器正確地理解了哪些變量或函數(shù)是放置在banked地址中的,以使編譯器能產(chǎn)生所需的額外指令。這是通過在工程創(chuàng)建時選擇個合適的地址模型或者通過在聲明變量或函數(shù)時使用特殊的語法來實現(xiàn)的。
一旦需要的物理內(nèi)存頁被映射到了分頁窗口內(nèi),CPU就可以使用16位尋址來訪問其中的數(shù)據(jù)了。
注意: 你可以通過分頁窗口來訪問任意分頁內(nèi)存資源的任意內(nèi)容。但是,每次你需要訪問這樣一個內(nèi)存地址之前的那個寫分頁寄存器的操作會帶來可觀的開銷。這就是為什么有些特定地址被直接映射到本地地址上,它們與任何分頁寄存器的值無關(guān),被稱為non-banked,或者非分頁地址。對于這些地址,通常不會使用分頁訪問,而是更喜歡使用直接訪問。HCS12設(shè)備的分頁切換
圖4展示了CPU本地映射,其中non-banked地址上標記了它們總是映射的地址的對應(yīng)分頁號。后面的例子示例了HCS12設(shè)備的頁面切換。
圖 4. CPU本地地址映射
本地地址值0xC000對應(yīng)于一個non-banked地址。根據(jù)圖4 這個地址指向Flash分頁0x3F的首個字節(jié)。讀寫地址0xC000總是會訪問同一個物理地址,不管你怎么設(shè)置分頁寄存器的值。
你也可以通過向PPAGE寄存器寫入0x3F的方式來訪問物理Flash分頁0x3F的首個字節(jié)。這樣Flash分頁0x3F的所有內(nèi)容就會出現(xiàn)在0x8000到0xBFFF間了。Flash分頁0x3F的首個字節(jié)出現(xiàn)在本地地址0x8000處。
這兩種方式都是正確的;但是直接訪問0xC000的開銷更小,因此更推薦。
HCS12下banked地址的例子假設(shè)你想要讀Flash分頁0x3C的首個字節(jié)。這種情況下,F(xiàn)lash分頁0x3C無法在CPU本地地址的non-banked地址找到(參照圖4)。唯一的解決方案是使用Flash分頁窗口來訪問。應(yīng)用必須向PPAGE寄存器中寫入0x3C,然后訪問位于本地地址0x8000的這個分頁的首個字節(jié)。
HCS12X設(shè)備的分頁切換圖5 在HCS12X的本地地址中的non-banked區(qū)域上標記了其對應(yīng)指向的物理地址分頁號。后面跟著的例子示例了HCS12X設(shè)備的頁面切換。
圖 5. HCS12X帶有分頁號的本地地址映射
本地地址0xC000對應(yīng)于一個non-banked地址,它不屬于任何分頁窗口。根據(jù)圖5,這個地址指向Flash分頁號0xFF的首個字節(jié)。讀寫地址0xC000總是訪問通過物理地址,跟任何分頁寄存器的值無關(guān)。
你也可以通過向PPAGE寄存器寫入0xFF的方式來訪問物理Flash分頁0xFF的首個字節(jié)。這樣Flash分頁0xFF的所有內(nèi)容就會出現(xiàn)在0x8000到0xBFFF間了。Flash分頁0xFF的首個字節(jié)出現(xiàn)在本地地址0x8000處。
這兩種方式都是正確的;但是直接訪問0xC000的開銷更小,因此更推薦。
HCS12X下banked地址的例子假設(shè)你想要讀Flash分頁0xFC的首個字節(jié)。這種情況下,F(xiàn)lash分頁0xFC無法在CPU本地地址的non-banked地址找到(參照圖5)。唯一的解決方案是使用Flash分頁窗口來訪問。應(yīng)用必須向PPAGE寄存器中寫入0xFC,然后訪問位于本地地址0x8000的這個分頁的首個字節(jié)。
在HCS12和HCS12X的例子中,CodeWarrior的C編譯器都會負責自動地在訪問一個分頁地址之前自動地插入指令來寫對應(yīng)的分頁寄存器。然而,為了確保這件事情會發(fā)生,程序員需要選擇最合適應(yīng)用的地址模型,并最終使用特別的標識符,如關(guān)鍵詞__near或__far,或者#pragma聲明來在需要的地方修改編譯器行為。
HCS12X設(shè)備的全局訪問在S12架構(gòu)中,一個對象可以有的最大大小是16kB。這是受限于CPU本地地址映射,在其中,同時可被CPU訪問的最大連續(xù)內(nèi)存空間是16kB。在S12架構(gòu)中,企圖分配一個大于16kB的對象會導致一個鏈接器錯誤。
為了減少這個限制,在S12X架構(gòu)中,引入了另一種尋址方法:全局訪問。
全局訪問使得可以訪問,在一個“新的”地址空間中的,最大64K的連續(xù)內(nèi)存區(qū)域,這個空間被叫做全局地址空間。
在S12X CPU看來,有兩種可以訪問數(shù)據(jù)的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處的值
.
我們已經(jīng)學習了分頁窗口和分頁寄存器的概念,對于64kB全局地址,我們可以把它理解為一個64kB的分頁窗口,其中的內(nèi)容取決于第四個分頁寄存器—GPAGE。
全局地址是個覆蓋了8Mb地址空間的23位地址,從地址0x000000到0x7FFFFF。在這個線性全局地址空間中,所有的內(nèi)存資源都被分組,GPAGE寄存器可以被用于訪問所有的RAM、EEPROM和FLASH地址,以及外部地址空間。
在向GPAGE中寫入正確的值后,全局地址的內(nèi)容就只能通過全局指令來訪問了。
GPAGE的值的選取方式與其他分頁寄存器的方式很相似:
寫0xFF到GPAGE中會使全局地址映射總地址的最后64kB。
寫0xFE會映射物理地址的的倒數(shù)第二個64kB。
什么時候使用全局尋址使用全局尋址主要是由于兩個原因:
當需要鏈接非常大的對象,大到由于本地地址中沒有足夠的連續(xù)地址空間而無法被鏈接時。這種情況下,使用全局尋址使得程序員能夠通過全局指令來訪問高達64kB的連續(xù)地址空間??杀绘溄拥膯蝹€數(shù)據(jù)對象的最大大小是64kB。
當在運行被分頁存儲的代碼的同時,試圖訪問同一種內(nèi)存資源中的被分頁存儲的對象(變量或常量)。
.
比如,當應(yīng)用需要訪問分配在某個給定Flash分頁中的常量,但當前執(zhí)行的代碼卻跑在一個不同的Flash分頁中。通常,當在S12架構(gòu)中遇到這種情況時,會使用一個non-banked運行時例程來訪問分頁的對象。
在S12X中,當在分頁Flash中運行時,可以使用這個新的全局訪問方式來訪問Flash中的任何地址,不需要碰PPAGE寄存器,也不需要跳到一個non-banked例程。
為了指示編譯器使用全局訪問來訪問某個對象,可以把它聲明在一個 #pragma DATA_SEG __GPAGE_SEG 塊或#pragma CONST_SEG __GPAGE_SEG 塊中,這取決于對象的特性。
S12X本地地址再映射能力在新的S12X設(shè)備上,MMC模塊可以由用戶來配置0x4000到0x7FFF之間的CPU本地地址。這部分的本地地址默認是用來映射Flash的,但是它可以被配置來映射RAM或者外部空間,因此給用戶提供了更大的靈活性,更靈活的配置哪些地址是non-banked的。請參考S12X編譯器手冊的編譯器選項 -Map以及你的設(shè)備的datasheet,MMC模塊,來獲取更多關(guān)于這一特性的信息。
現(xiàn)在你已經(jīng)見識到了訪問內(nèi)存地址的兩種不同的方式。下一章會描述,怎么指示CodeWarrior鏈接器來放置我們的代碼和變量到需要的內(nèi)存地址中。我們還將看到,當使用C語言來開發(fā)時,怎么確保CodeWarrior的編譯器知道某個對象應(yīng)該放在banked還是non-banked內(nèi)存地址中,以產(chǎn)生合適的代碼。
控制對象在內(nèi)存中的分配這個部分描述了CodeWarrior連接器默認是怎么在內(nèi)存中放置對象的,以及怎么改變這個默認行為以定制我們的應(yīng)用。
名詞 對象(objects) 指的是在內(nèi)存中有固定地址的實體??梢允牵?/p>
函數(shù)(代碼)
變量(放置在RAM中的數(shù)據(jù)和數(shù)組)
常量(放置在Flash中并被標識為”const”的數(shù)據(jù))
字符串(沒有被預(yù)定義為數(shù)組的字符串字面值)
比如:printf( “Hello World”)將會產(chǎn)生字符串”Hello World”。相反地,聲明一個變量為
unsigned char Message[] = “Hello World”;
的話,鏈接器會認為它是一個數(shù)組而不是一個字符串。
.
對象的位置由#pragma聲明來控制。后面的內(nèi)容并不是對#pragma聲明的完全描述,只列出了那些常用的而已。如果想要詳細描述的話,請參考放在你的CodeWarrior安裝路徑下編譯器和搭建工具的手冊。
我們將看到四種#pragma聲明:
#pragma CODE_SEG
#pragma DATA_SEG
#pragma CONST_SEG
#pragma STRING_SEG
.
這四種聲明都可以插入到C源文件中,并用于控制聲明后面的對象的位置和特性。
怎么使用pragma聲明來控制對象的位置讓我們先看一個例子。這里,一個變量、一個常量和一個函數(shù)被放在一個明確的placement section中。placement section是指向內(nèi)存中特定區(qū)域的標簽,被定義在項目的鏈接器參數(shù)文件(*.prm文件)的PLACEMENT塊中。下一章中給出了鏈接器參數(shù)文件的結(jié)構(gòu),用于參考。
unsigned char variable1; const unsigned char constant1; void function1(void) { /* 代碼 */ }. #pragma聲明并不是強制的。在上例中就沒有#pragma聲明。這種情況下,鏈接器使用默認行為并將放置對象到他們的默認位置。
DEFAULT_ROM 是代碼的默認位置
DEFAULT_RAM 是變量和數(shù)組的默認位置
ROM_VAR
是常量(ROM變量)的默認位置
.
DEFAULT_ROM、DEFAULT_RAM 和 ROM_VAR是定義在鏈接器參數(shù)文件的PLACEMENT塊內(nèi)標簽。他們是CodeWarrior認識的特殊關(guān)鍵字。鏈接器認得它們,比如,ROM_VAR是常量在缺少#pragma聲明時應(yīng)該被放置的位置。
在這個例子中:
variable1 將被放在 DEFAULT_RAM
constant1 將被放在 ROM_VAR
function1 將被放在 DEFAULT_ROM
.
鏈接器參數(shù)文件定義了每個placement section對應(yīng)的地址范圍。
下一個例子說明了#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 內(nèi)的代碼 */ } #pragma CODE_SEG DEFAULT (6) void function2(void) { /* function2 內(nèi)的代碼 */ }這里有一些定義#pragma聲明的行為的規(guī)則:
#pragma聲明只對相關(guān)的對象起作用。比如,一條#pragma DATA_SEG聲明不會影響常量的位置。只有#pragma CONST_SEG聲明可以影響常量對象的位置。
#pragma聲明只對聲明在#pragma后面的對象產(chǎn)生影響,并且一直影響到遇到另一個同特性的#pragma語句或者到編譯單元(見以下注釋)的結(jié)束。
.
在上例中,有六個#pragma聲明,它們被編號以便于后文好引用它們。
在這個例子中,我們假設(shè)標簽MYVARIABLES、MYCONSTANTS和MYCODE是由用戶定義在項目鏈接器參數(shù)文件內(nèi)的placement section的名字。
#pragma聲明(1) 會導致variable1被分配在placement section MYVARIABLES。
#pragma聲明(2) 會結(jié)束pragma聲明(1)的影響。它會導致variable2被分配在它的默認位置,也就是DEFAULT_RAM。
#pragma聲明(3) 會導致constant1被分配在placement section MYCONSTANTS。
#pragma聲明(4) 會結(jié)束pragma聲明(3)的影響。它會導致constant2被分配在它的默認位置,也就是ROM_VAR。
#pragma聲明(5) 會導致function1被分配在placement section MYCODE。
#pragma聲明(6) 會結(jié)束pragma聲明(5)的影響。它會導致function2被分配在它的默認位置,也就是ROM_VAR。
注意: 我們之前提到過,一條#pragma聲明的影響一直會持續(xù)到遇到下一個#pragma聲明,或者到達編譯單元的末尾。一個編譯單元相當于源文件加上所有它包含進的頭文件。這意味著在一個*.h頭文件內(nèi)的#pragma聲明可以影響那些包含了它的源文件內(nèi)的對象的位置。這可能對于開發(fā)者來說是使很難追蹤的。這就是為什么總是像上例中那樣在一個#pragma聲明的后面明確寫出一個#pragma DEFAULT 聲明是一個好的編程實踐。 注意: 在上例中,只有鏈接器的默認行為被修改了。鏈接器的工作在所有對象被給定地址后就結(jié)束了。在上例中,用到的placement section可以是banked或non-banked的。鏈接器在一定程度上不關(guān)心這些。生成訪問地址的指令是編譯器的活。開發(fā)者要負責確保編譯器知道哪些對象放置在banked地址以及non-banked地址中,以使編譯器能生成正確的訪問指令。比如:是否需要操作分頁寄存器。許多開發(fā)者使用默認的編譯器設(shè)置。這樣就不需要為應(yīng)用中的不同對象分別定制編譯器行為了。默認情況下編譯器的尋址行為被稱為地址模型(memory model)。這在下一章中會討論。
編譯器和鏈接器的默認行為當使用項目向?qū)?chuàng)建一個新的CodeWarrior項目時,用戶被要求選擇一個地址模型,選項有:Small、banked和large地址模型。選擇的地址模型將決定CodeWarrior的鏈接器會默認地會把你的代碼以及變量放在哪里,以及決定CodeWarrior的編譯器會怎么產(chǎn)生訪問你的對象的指令。
這里描述了每個地址模型:
Small memory model:你的代碼和變量都會默認放在non-banked位置。
Banked memory model:你的代碼默認會放在banked地址中,但是你的變量會默認放在non-banked地址中。
Large memory model:你的代碼和變量都會默認放在banked地址中。
.
選擇地址模型將會影響你的項目中的三個元素:
編譯器選項
ANSI庫
鏈接器參數(shù)文件
項目的編譯器選項編譯器的行為受到選擇的地址模型影響。CodeWarrior項目向?qū)г谀沩椖康木幾g器選項中插入一個 -M 選項。有三種參數(shù),取決于模型:-Ms、-Mb或-Ml。這個選項指示編譯器根據(jù)模型的假定來編譯。
Small地址模型對應(yīng)選項 -Ms。編譯器不會插入任何指令來處理任何分頁寄存器。變量將會被直接訪問non-banked地址,并且你的代碼會使用JSR/RTS指令來執(zhí)行。
Banked地址模型對應(yīng)選項 -Mb。當訪問你的代碼的時候,編譯器會使用處理PPAGE寄存器的指令。在調(diào)用一個函數(shù)的時候會使用CALL指令。CALL指令會負責在運行你的函數(shù)前把它的分頁號寫到PPAGE寄存器中。變量則會默認的按照non-banked地址的方式訪問。
Large地址模型對應(yīng)選項 -Ml。編譯器將使用CALL指令來訪問你的代碼,并且在訪問RAM和EEPROM變量前也會插入分頁處理指令,它們被默認放在分頁地址。因此這個地址模型對代碼大小和執(zhí)行時間非常不友好,在大部分情況下不推薦。
如果你需要訪問分頁區(qū)變量,大部分情況下用這個方法就夠了:選擇banked地址模型,然后每次變量要放到分頁區(qū)的時候都用特別標識符來告知編譯器。文檔的后面部分會討論怎么訪問分頁區(qū)變量。這使得你可以只在需要訪問分頁區(qū)變量時才進行分頁訪問,而不是默認對所有變量都這么訪問。
注意: JSR/RTS和CALL/RTC指令會在后面部分被簡短的討論項目的ANSI庫
在項目向?qū)е羞x擇一個地址模型還會添加對應(yīng)的ANSI庫。通過預(yù)編譯的*.lib文件加進項目的ANSI庫需要與你項目選擇的編譯器選項兼容。
項目的鏈接器參數(shù)文件這是指示鏈接器要到哪里放置你的代碼的文件。根據(jù)選擇的地址模型,不同的參數(shù)文件會被加入你的項目中。
圖6展示了由CodeWarrior項目向?qū)橐粋€S12XEP100設(shè)備創(chuàng)建的參數(shù)文件,這個項目中沒有使用XGATE。
我們就僅僅看下small和banked地址模型對應(yīng)的prm文件的PLACEMENT塊,。
圖 6. small地址模型
圖 7. banked地址模型
所有的藍顏色的標簽都是CodeWarrior認得的特別關(guān)鍵詞。這些標簽都有特別的用處:
DEFAULT_ROM 是你的代碼會被默認分配的地方,也就是當沒有#pragma CODE_SEG聲明時的。它在參數(shù)文件中是強制要有的。
DEFAULT_RAM 是你的變量會被默認分配的地方,也就是當沒有#pragma DATA_SEG聲明時的。它是參數(shù)文件中強制要有的字段。
__PRESTART 指示啟動代碼要放在哪里。
STARTUP 是Startup數(shù)據(jù)結(jié)構(gòu)被放置的地方
ROM_VAR 是存儲常量的默認位置(用“const”聲明的變量)
STRINGS是你的字符串字面值默認會被分配的地方,也就是當沒有#pragma STRING_SEG聲明時的。(字符串字面值是傳遞給函數(shù)的字面值,比如printf(“hello world”); 中使用的”hello world”)
NON_BANKED 是一個由庫使用的特殊標簽,用于存放那些必須non-banked的對象。這個標簽也可以被程序員在他的代碼中使用。
VIRTUAL_TABLE_SEGMENT是C++應(yīng)用專用的。
COPY是ram對象的初始化值會被分配的地方。
比如,當你這樣聲明一個變量:
unsigned char variable=0xAA;
0xAA這個值就被存放到Flash資源的COPY部分了。啟動代碼會在每次重置后復(fù)制這個值到相應(yīng)“變量”的位置。
SSTACK 是你的棧被放置的地方。SSTACK的大小是由命令STACKSIZE決定的,這命令也出現(xiàn)在prm文件中。
.
任何黑顏色的文本都不是特殊關(guān)鍵詞。(見圖6和圖7)在這里,使用的黑顏色的標簽只是為了示范的目的。在這里就是指上圖中的標簽PAGED_RAM和OTHER_ROM。CodeWarrior不特別地使用這些標簽。它們是程序員用于在應(yīng)用代碼中使用#pragma聲明使用的。
small和banked地址模型參數(shù)文件間最本質(zhì)的區(qū)別是DEFAULT_ROM標簽的位置。
small地址模型參數(shù)文件不使用分頁Flash地址。標簽OTHER_ROM指向分頁地址,但是不被由向?qū)?chuàng)建的項目使用。OTHER_ROM放在那只是給程序員在需要的時候使用的。
你必須要明白,在任何地址模型中你都可以使用分頁或者非分頁對象。地址模型只是影響默認位置和默認編譯器行為。
在你的代碼中,你總是可以通過使用特殊的語法以本地地改變默認行為。
比如,為了在banked地址模型中使用banked變量,需要額外輸入一些東西。后面的部分講了些編碼的注意事項。
改變編譯器默認行為 改變對代碼的默認訪問方式最高效地訪問放置在non-banked地址內(nèi)的函數(shù)的方式是通過JSR(跳轉(zhuǎn)到子例程)/RTS(從子例程返回)指令對。JSR/RTS指令對不處理任何分頁寄存器,并使用16位地址來跳轉(zhuǎn)。
另一方面,為了訪問放置在banked地址內(nèi)的函數(shù),必須使用CALL/RTC(從call返回)指令對。CALL/RTC指令對會處理PPAGE寄存器。
為了在調(diào)用一個函數(shù)的時候強制使用JSR/RTS指令對,那個函數(shù)必須使用“__near”聲明。
示例:
void __near myfunction(void);為了在調(diào)用一個函數(shù)的時候強制使用CALL/RTC指令對,那個函數(shù)必須使用 “__far”聲明。
示例:
void __far myfunction(void); 改變對變量的默認訪問方式如果一個變量放置在non-banked地址中,不需要使用什么關(guān)鍵詞來保證正確的訪問。
在S12架構(gòu)中,只有內(nèi)部的Flash內(nèi)存資源有分頁,所以唯一的情景就是要訪問的那個數(shù)據(jù)被放在分頁Flash中,或者說,那是個分頁區(qū)常量。見下例:
示例 1:在S12架構(gòu)中訪問分頁區(qū)常量
清單 1:在S12架構(gòu)中訪問分頁區(qū)常量
#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 展示了鏈接器參數(shù)文件的對應(yīng)位置。
圖 8. 鏈接器參數(shù)文件
注意,這里在constant1的聲明中使用了 __far 標識符。這個__far標識符指示編譯器在訪問constant1的地址之前處理PPAGE寄存器。因為在S12架構(gòu)中只有一個分頁寄存器,就沒必要講更多了。
在S12X架構(gòu)中,我們得說明哪一個分頁寄存器與哪個變量相關(guān)。下例進行了說明。
示例 2:在S12X架構(gòu)中訪問分頁區(qū)變量
當一個變量被分頁存儲,程序員需要告知編譯器,這個變量的地址與哪個寄存器相關(guān)。這是通過使用#pragma聲明來實現(xiàn)的。
使用關(guān)鍵詞 __RPAGE_SEG、__EPAGE_SEG、__PPAGE_SEG 和__GPAGE_SEG來分別告知編譯器去處理RPAGE、EPAGE、PPAGE 和 GPAGE寄存器。
在Banked RAM區(qū)的變量在訪問一個banked RAM地址之前,編譯器需要插入 寫RPAGE寄存器 的指令。對應(yīng)的語法如下:
#pragma DATA_SEG __RPAGE_SEG PAGED_RAM 在Banked EEPROM區(qū)的變量在訪問一個banked EEPROM地址之前,編譯器需要插入 寫EPAGE寄存器 的指令。對應(yīng)的語法如下:
#pragma DATA_SEG __EPAGE_SEG MY_EEPROM 在Banked Flash區(qū)的常量在訪問一個banked FLASH地址之前,編譯器需要插入 寫PPAGE寄存器 的指令。對應(yīng)的語法如下:
#pragma CONST_SEG __PPAGE_SEG OTHER_ROM我們看看怎么訪問一個分頁RAM區(qū)變量,和一個分頁ROM區(qū)變量:
#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 展示了這個例子的鏈接器參數(shù)文件。
圖 9. 鏈接器參數(shù)文件
在這個例子中,#pragma聲明(1) 被用來指示連接器把variable1放置在叫做PAGED_RAM的section中。同時,由于有__RPAGE_SEG標識符,我們告知編譯器在訪問這個變量之前需要先處理RPAGE寄存器。
. #pragma聲明(2) 被用來指示連接器把常量constant1放置在叫做PAGED_ROM的section中。同時,告知編譯器在訪問受這個#pragma影響的常量之前需要先處理GPAGE寄存器。
這是個有意思的選擇。我們故意選擇GPAGE寄存器來訪問分頁Flash區(qū)數(shù)據(jù)。因為在這個例子中,我們的代碼存放的DEFAULT_ROM的位置也在個分頁Flash區(qū)。因此當執(zhí)行main函數(shù)時,PPAGE寄存器需要被設(shè)置為0xFC(見圖9),這樣CPU才可以讀取在Flash分頁窗口內(nèi)的主函數(shù)。當在Flash分頁窗口內(nèi)執(zhí)行時,PPAGE值絕對不能改變,否則CPU就會跑飛。這就是為什么,為了訪問另一個Flash頁的地址,我們不再使用PPAGE寄存器,而是使用了GPAGE寄存器。
這是利用GPAGE寄存器的一個典型例子。
這篇文檔到這里就結(jié)束了。想要獲得關(guān)于鏈接器和編譯器行為的更多信息的話,請參考位于CodeWarrior安裝路徑下的搭建工具與編譯器手冊。代碼示例可以在CodeWarrior安裝路徑內(nèi)的文件夾(CodeWarrior_Examples)里找到。
電子發(fā)燒友App


















評論