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