使用ADS1.2進(jìn)行嵌入式軟件開(kāi)發(fā)(上)
嵌入式應用程序通常都是在樣機環(huán)境下調試與開(kāi)發(fā)的,這種環(huán)境與最終產(chǎn)品之間并不完全相同。因此,在系統調試階段就考慮應用程序在最終目標硬件中的運行情況是非常重要的。
本文旨在討論如何將一個(gè)開(kāi)發(fā)/調試環(huán)境下的嵌入式應用程序轉移到最終獨立運行的目標系統中去,并提到了ARM ADS1.2開(kāi)發(fā)工具包的一些功能特性及其在這個(gè)過(guò)程中所起到的作用。
使用ADS開(kāi)發(fā)嵌入式程序時(shí),需要著(zhù)重考慮以下幾個(gè)問(wèn)題:
與硬件相關(guān)的C語(yǔ)言庫函數的使用;
某些C語(yǔ)言庫函數使用了調試環(huán)境中的資源,要把這些使用的資源重定向到目標系統中的硬件上來(lái);
可執行映象文件的存儲器映射必須根據目標硬件的存儲器分布進(jìn)行裁剪;
在主程序執行前,嵌入式應用程序必須先完成系統的初始化。一個(gè)完整的初始化包括用戶(hù)的啟動(dòng)執行代碼和ADS中C庫函數的初始化過(guò)程。
圖1 Semihosting的實(shí)現舉例
圖2 C語(yǔ)言庫函數結構
圖3 缺省的存儲器映射
圖4 連接器布局規則
缺省的工程項目設置
剛開(kāi)始一個(gè)嵌入式應用軟件開(kāi)發(fā)時(shí),ADS用戶(hù)可能并不完全清楚目標硬件的一些參數指標。比如有關(guān)外設、存儲器地址分布,甚至處理器類(lèi)型等一些細節,可能還沒(méi)有最終確定。為了在所有這些細節全部就緒前就能進(jìn)行軟件開(kāi)發(fā),ADS工具有一套程序構建和調試的缺省設置。了解這套缺省的工程項目設置方法,對于掌握最終的移植步驟非常有好處。
ADS1.2C語(yǔ)言函數庫
Semihosting
在A(yíng)DS的C語(yǔ)言函數庫中,某些ANSIC的功能是由主機的調試環(huán)境來(lái)提供的,這套機制有一個(gè)專(zhuān)門(mén)術(shù)語(yǔ)叫Semihosting。Semihosting通過(guò)一組軟件中斷(SWI)指令來(lái)實(shí)現。如圖1所示,當一個(gè)Semihosting軟中斷被執行時(shí),調試系統先識別這個(gè)SWI請求,然后掛起正在運行的程序,調用Semihosting的服務(wù),完成后再恢復原來(lái)的程序執行。因此,主機執行的任務(wù)對于程序來(lái)說(shuō)是透明的。
C語(yǔ)言庫函數結構
從概念上來(lái)講,C語(yǔ)言庫函數可以被分成兩部分,一是ANSIC語(yǔ)言規范本身的一部分,一是只受某一特定ANSIC層次支持的函數,如圖2所示。
其中一些ANSIC的功能是由主機調試環(huán)境調用驅動(dòng)程序級的函數完成的。例如,ADS的庫函數printf()把輸出信息輸出到調試器的控制臺窗口,這個(gè)功能通過(guò)調用__sys_write()實(shí)現,__sys_write()執行了一個(gè)把字符串輸出到主機控制臺的Semihosting軟中斷服務(wù)程序。
缺省的存儲器映射
如果用戶(hù)在程序編譯時(shí)沒(méi)有指定映象的存儲器映射分布,ADS將為生成的目標代碼和數據分配一個(gè)缺省的存儲器映射圖,如圖3所示。
目標印象被連接至地址0x8000,存儲和執行區域都位于該地址開(kāi)始的空間。RO(只讀)部分放在前面,接著(zhù)是RW(讀寫(xiě))部分,最后是ZI(零初始化)部分。
在ZI部分之上緊跟著(zhù)HEAP,所以HEAP的確切地址要在連接時(shí)才能確定。
STACK的基地址是在應用程序啟動(dòng)時(shí)由一個(gè)Semihosting操作提供。這項Semihosting操作返回的地址值視不同調試環(huán)境而定:
ARMulator返回配置文件peripherals.ami中的設置值;缺省為0x08000000。
Multi-ICE返回的是調試器內部變量$top_of_memory的值;缺省為0x00080000。
連接器布局規則
連接器對代碼和數據在存儲器系統中的分配,遵循一套規則,如圖4所示。
映象首先按照屬性以RO-RW-ZI的次序進(jìn)行排列,在同一種屬性里面代碼先于數據。然后連接器將輸入段根據名字的字母順序進(jìn)行排列,輸入段的名字與匯編代碼里面的塊名字指示一致(在匯編程序中用AREA關(guān)鍵字)。在輸入段中,來(lái)自不同對象的代碼和數據放置次序與在連接器命令行中指定的對象文件次序一致。
在需要靈活分配代碼和數據放置位置的情況下,建議用戶(hù)不要簡(jiǎn)單地依靠這些規則。后面會(huì )介紹一種如何控制代碼和數據布局的機制Scatterloading。
圖5 缺省的ADS初始化過(guò)程
圖6 C庫函數重定向
圖7 scatter文件語(yǔ)法
圖8 分散加載的簡(jiǎn)單樣例
啟動(dòng)應用程序
大多數嵌入式系統在進(jìn)入應用主程序之前有一個(gè)初始化的過(guò)程,該過(guò)程完成系統的啟動(dòng)和初始化功能。缺省的ADS初始化過(guò)程如圖5所示。
總體上,初始化過(guò)程可以分成兩部分來(lái)看:
_main負責設置運行映像存儲器映射;
_rt_entry負責庫函數的初始化。
_main完成代碼和數據的復制,并把ZI數據區清零。這一步只有當代碼和數據區在存儲和運行時(shí)處于不同的存儲器位置時(shí)才有意義。接著(zhù)_main跳進(jìn)_rt_entry,進(jìn)行STACK和HEAP等的初始化。最后_rt_entry跳進(jìn)應用程序的入口main()。當應用程序執行完時(shí),_rt_entry又將控制權交還給調試器。
函數main()在A(yíng)DS中有特殊的意義。當一個(gè)程序工程項目中存在main()時(shí),連接器會(huì )把_main和_rt_entry中的初始化代碼連接進(jìn)來(lái);如果沒(méi)有main()函數,初始化過(guò)程就不會(huì )被連接,結果就會(huì )導致一些標準的C庫函數無(wú)效。
根據目標環(huán)境裁減C庫函數
缺省狀態(tài)下C庫函數利用Semihotsting機制來(lái)實(shí)現設備驅動(dòng)的功能。但一個(gè)真正的嵌入式系統,要使用到具體的外設或硬件獨立于主機環(huán)境運行。
C庫函數重定向
用戶(hù)可以定義自己的C語(yǔ)言庫函數,連接器在連接時(shí)自動(dòng)使用這些新的功能函數。這個(gè)過(guò)程叫做重定向C語(yǔ)言庫函數,如圖6所示。
舉例來(lái)說(shuō),用戶(hù)有一個(gè)I/O設備(如UART)。本來(lái)庫函數fputc()是把字符輸出到調試器控制窗口中去的,但用戶(hù)把輸出設備改成了UART端口,這樣一來(lái),所有基于fputc()函數的printf()系列函數輸出都被重定向到UART端口上去了。
下面是實(shí)現fputc()重定向的一個(gè)例子:
externvoidsendchar(char*ch);
intfputc(intch,FILE*f)
{/*e.g.writeacharactertoanUART*/
chartempch=ch;
sendchar(&tempch);
returnch;
}
這個(gè)例子簡(jiǎn)單地將輸入字符重新定向到另一個(gè)函數sendchar(),sendchar()假定是一個(gè)另外定義的串口輸出函數。在這里,fputc()就好像目標硬件和標準C庫函數之間的一個(gè)抽象層。
在C語(yǔ)言庫函數中禁用Semihosting
在一個(gè)獨立的嵌入式應用程序中,應該不存在SemihostingSWI操作。因此,用戶(hù)必須確定在所有調用到的庫函數中沒(méi)有使用Semihosting。為了保證這一點(diǎn),在程序中可以引進(jìn)一個(gè)符號關(guān)鍵字_use_no_semihosting:
在C代碼中,使用#prgrama #pragmaimport〈_use_no_semihosting_swi〉
在匯編程序中,使用IMPORT
IMPORT_use_no_semihosting_swi
這樣,當有使用SWI機制的庫函數被連接時(shí),連接器會(huì )進(jìn)行報錯:
Error:Symbol_semihosting_swi_guardmultiplydefined
為了確定具體是哪一個(gè)函數,連接時(shí)打開(kāi)-verbose選項。這樣在結果信息輸出時(shí),該庫函數上將有一個(gè)_I_use_semihosting_swi的標記。
Loadingmembersys_wxit.ofromc_a_un.1.
Definition:_sys_exit
Reference:_I_use_semihosting_swi
用戶(hù)必須要把這些函數定義成自己的執行內容。
有一點(diǎn)需要注意,連接器只能報告庫函數中被調用的Semihosting,對用戶(hù)自定義函數中使用的Semihosting則不會(huì )報錯。
根據目標硬件定制存儲器映射
分散裝載(Scatlerloading)
在實(shí)際的嵌入式系統中,ADS提供的缺省存儲器映射是不能滿(mǎn)足要求的。用戶(hù)的目標硬件通常有多個(gè)存儲器設備位于不同的位置,并且這些存儲器設備在程序裝載和運行時(shí)可能還有不同的配置。
Scattertoading可以通過(guò)一個(gè)文本文件來(lái)指定一段代碼或數據在加載和運行時(shí)在存儲器中的不同位置。這個(gè)文本文件scatterfile在命令行中由-scatter開(kāi)關(guān)指定,例如:
armlink_scatterscat.scffilel.ofile2.0
在scatterfile中可以為每一個(gè)代碼或數據區在裝載和執行時(shí)指定不同的存儲區域地址,Scatlertoading的存儲區塊可以分成二種類(lèi)型:
裝載區:當系統啟動(dòng)或加載時(shí)應用程序的存放區。
執行區:系統啟動(dòng)后,應用程序進(jìn)行執行和數據訪(fǎng)問(wèn)的存儲器區域,系統在實(shí)時(shí)運行時(shí)可以有一個(gè)或多個(gè)執行塊。
映像中所有的代碼和數據都有一個(gè)裝載地址和運行地址(二者可能相同也可能不同,視具體情況而定)。在系統啟動(dòng)時(shí),C函數庫中的__main初始化代碼會(huì )執行必要的復制及清零操作,使應用程序的相應代碼和數據段從裝載狀態(tài)轉入執行狀態(tài)。
1.scatter文件語(yǔ)法
scatter文件是一個(gè)簡(jiǎn)單的文本文件,包含一些簡(jiǎn)單的語(yǔ)法。
My_Region0x00000x1000
{
thecontextofregion
}
每個(gè)塊由一個(gè)頭標題開(kāi)始定義,頭中至少包含塊的名字和起始地址,另外還有最大長(cháng)度和其他一些屬性選項。塊定義的內容包括在緊接的一對花括號內,依賴(lài)于具體的系統情況。
一個(gè)加載塊必須至少含有一個(gè)執行塊;實(shí)踐中通常有多個(gè)執行塊。
一個(gè)執行塊必須至少含有一個(gè)代碼或數據段;這些通常來(lái)自源文件或庫函數等的目標文件;通配符號*可以匹配指定屬性項中所有沒(méi)有在文件中定義的余下部分。
2.簡(jiǎn)單分散加載樣例
圖8所示樣例中,只有一個(gè)加載塊,包含了所有的代碼和數據,起始地址為0。這個(gè)加載塊一共對應兩個(gè)執行塊。一個(gè)包含所有的RO代碼和數據,執行地址與裝載地址相同;同時(shí)另一個(gè)起始地址為0x10000的執行塊,包含所有的RW和ZI數據。這樣當系統開(kāi)始啟動(dòng)時(shí),從第一個(gè)執行塊開(kāi)始運行(執行地址等于裝載地址),在執行過(guò)程中,有一段初始化代碼會(huì )把裝載塊中的一部分代碼轉移到另外的執行塊中。
下面是這個(gè)scatter描述文件,該文件描述了上述存儲器映射方式。
LOAD_ROM0x4000
{
EXE_ROM0x00000x4000;Rootregion
{
*〈+RO〉;Allcodeandconstantdata
}
RAM0x100000x8000
{
*〈+RW,+ZI〉;Allnon-constantdata
}
}
3.在分散文件中放置對象
在大多數應用中,并不是像前例那樣,簡(jiǎn)單地把所有屬性都放在一起,用戶(hù)需要控制特定代碼和數據段的放置位置。這可以通過(guò)在scatter文件中對單個(gè)目標文件進(jìn)行定義實(shí)現,而不是只簡(jiǎn)單地依靠通配符。
為了覆蓋標準的連接器布局規則,我們可以使用+FIRST和+LAST分散加載指令。典型的例子是在執行塊的開(kāi)始處放置中斷向量表格:
LOAD_ROM0x00000x4000
{
EXEC_ROM0x00000x4000
{
vectors.o〈Vect,+FIRST〉
*〈+RO〉
}
;moreexecregions...
}
在這個(gè)scatter文件中,保證了vextors.o中的Vect域被放置于地址0x0000。
4.RootRegion(根區)
根區是一個(gè)執行塊,它的加載地址與執行地址是一致的。每個(gè)scatter文件至少有一個(gè)根區。分散加載有一個(gè)限制:創(chuàng )建執行塊的代碼和數據(即完成復制和清零的代碼和數據)無(wú)法自行復制到另一個(gè)位置。因此,在根區中必須含有下面的部分:
_main.o,包含復制代碼/數據的代碼;
連接器輸出變量$$Table和ZISection$$Table,包含被復制代碼/數據的地址。
由于上面兩個(gè)部分的屬性是只讀的,因此他們被*〈+RO〉通配符語(yǔ)法匹配。如果*〈+RO〉被用在了非根區中,則在根區中必須顯式地指明另一個(gè)RO區域。
下面是一個(gè)例子:
LOAD_ROM0x00000x4000
{
EXE_ROM0x00000x4000;rootregion
{
_main.o〈+RO〉;copyingcode
*〈Region$$Tabl0e〉;RO/RWaddressestocopy
*〈ZISection$$Table〉;ZIaddressestozero
}
RAM0x100000x8000
{
*〈+RO〉;allotherROsections
*〈+RW,+ZI〉;allRWandZIsections
}
}■(待續)
在下期中將更詳細介紹利用scatter文件進(jìn)行存儲器配置的方法,以及系統啟動(dòng)和初始化的過(guò)程和存儲器映射變化關(guān)系。
評論