上電之后(Boot-Load階段)該做什么
1、第一行程序
拿到空PCB板之后,硬件工程師首先會測試各主要線路是否通連,各焊點是否有空焊、斷接或短路的情況,然后逐個模塊焊接上去。之后需要驗證系統上電之后,CPU與各組件的供電電壓是否正常,供給CPU的震盪電路能否能夠正常起振,外部存儲器能否正常讀寫。當把我們的程序用JTAG工具下載到板子上后,在真正調試系統前需要做好以下檢查:
- 利用調試工具,在程序的第一行設定斷點,確定程序有停下來;
- 檢查CPU的程序計數器PC是否正確;
- 檢查CPU內部RAM的內容和我們下載的可執行文件是否相同;
- 程序的第一行命令為設定CPU狀態寄存器,並觀察CPU的狀態寄存器是否如預期改變;
- 繼續單步執行,確認PC寄存器是否會跟着改變,且每行命令的執行結果都是正確的。
檢查完以上各項后,只能證明板子上的電源電路以及CPU是正常的,接下來要繼續驗證CPU與外圍設備,確認板子的正確性與穩定性后,才能進行下一步測試。
2、基本硬件測試
既然Boot-Loader的責任是幫其它程序布置可運行的環境,那么就要做好以下驗證:
- CPU寄存器(狀態寄存器、通用寄存器、內存映射寄存器)操作測試;
// 設定SP(Stack Point)寄存器
//
asm("xld.w %r15, 0x2000");
asm("ld.w %sp, %r15");
// 設定CPU的狀態寄存器
//
asm("xld.w %r15, 0x200010");
asm("ld.w %psr, %r15");
// 將寄存器0x300023的bit 1設為1
*(volatile unsigned char *)0x300023 |= 0x2;
- Stack Pointer的設置是否正確?函數調用是否正確運行?
- 中斷是量表設置是否正確?中斷矢量程序是否正常運行?
- 存儲器初始化及其操作測試,保證所有的存儲器都可以正常讀寫;
- 將數據段載入RAM,對bss段設定初值,並將需要在RAM中運行的程序載入到RAM。保證當主程序執行起來后,全局變量的初始值都是正確的。
只有確保以上測試通過后才能進行下一步工作。
(1)確認函數調用能否正常運行
正確設置堆棧(Stack)是函數能否成功調用的前提,在嵌入式系統開發時,系統要自行管理堆棧,如果管理不當,可能會發生函數調用或調用幾層之后就死機的狀況。因為C語言利用堆棧完成以下事項:
- 存儲函數返回地址;
- 函數調用時的參數傳遞(參數較多時);
- 存儲函數內部的局部變量;
- 中斷服務程序執行時(發生中斷時),存儲CPU當前狀態及返回地址。
堆棧頂點地址(Stack Point)的配置是一件很重要的事,但卻極易被人忽略。主要是在Windows或Linux上編程時,操作系統在產生可執行文件時,linker會自動幫程序加上一段Startup Code,其中就包含了Stack存儲器的配置。但在無操作系統的嵌入式系統中,調用任何函數之前都要先為其設置好堆棧空間(Stack Point)。
當用C語言調用了一個函數,例如fun(a,b),編譯后的機器碼應該包含以下動作:
- 執行指令push,將參數a和b存入Stack,同時堆棧指針SP減一;
- 將當前程序計數寄存器PC的值(也即返回地址:函數調用指令的下一條指令地址)存到堆棧中;
- 執行指令Call,把PC的值設為函數fun()的地址,下一個被執行的指令就是函數的第一條命令。
- 當函數fun執行時,可利用當前SP的值計算出參數a和b的地址;
- 如果函數內部有局部變量,則依次將這些變量存到堆棧中。所以在嵌入式開發中盡量不要定義size太大的變量,否則有棧溢出(Stack Overflow)的風險。
- 當函數執行完畢,CPU會執行ret命令,該命令會從Stack頂層取出返回地址,然后賦值給PC寄存器,則下個指令就會執行函數后面的下一行指令,從而完成函數的調用。
如果SP寄存器沒有設定到正確的地址,或是沒有配置足夠大的存儲區域作為棧空間,那么在調用函數時很可能就會出錯。下圖就是一個棧空間溢出,破壞程序數據段的例子:
為避免以上情況的發生,一般會選擇某塊RAM 的頂端(最大地址)當作SP寄存器的初值,但具體棧的大小定位多少合適要根據具體軟硬件環境和項目要求。一般采用的方法是,剛開始稍微定義大一點,例如2KB-4KB左右,然后讓測試人員運行完系統所有功能(函數)后,記錄下SP在每次函數調用后的最小值,它與棧頂地址的差就是所需最小棧空間,一般會稍微再放一點。
(2)確認中斷系統能否正常運行
負責寫驅動程序的工程師要將中斷服務程序的地址填入中斷矢量表,並必須保證當驅動程序被執行時,中斷系統是正常的。一般來說主要做好以下工作:
- 中斷矢量表數組,詳細注解每個entry代表的中斷源;
- 如果是外接中斷控制器,要先完成中斷控制器的驅動程序,才能開始中斷系統的測試。
- 設定CPU的中斷矢量表地址寄存器(有些CPU無中斷矢量表地址寄存器,但它會指定某個固定地址為中斷矢量表的地址)
- 設定CPU的中斷控制寄存器(優先級、中斷允許位等)
- 確定中斷被觸發后,對應的ISR會被執行。
- 提供ISR的范例,讓ISR編寫者不用知道中斷系統的細節。
// ISR模板
//
void isr_template(void)
{
// 將所有通用目的寄存器存到堆棧
//
asm("pushn %r15"); /*將r0 - r15 都存到堆棧中 */
//將ALR與AHR寄存器通過r1存到堆棧
//你無需搞清ALR和AHR是什么寄存器,不同的CPU有不同的寄存器需要存儲
//
asm("ld.w %r1, %alr");
asm("ld.w %r0, %ahr");
asm("pushn %r1");
//調用C語言函數your_ISR,即真正ISR要處理的事寫在該函數里就行
//
asm("xcall your_ISR");
//從堆棧中取回被調用時的ALR和AHR寄存器的值
//
asm("popn %r1");
asm("ld.w %alr, %r1");
asm("ld.w %ahr, %r0");
//從堆棧中取回r1 - r15的值
//
asm("popn %r15");
//執行中斷返回指令,返回被中斷的程序
//
asm("reti");
}
在以上各環節中容易出錯的地方有:
- 中斷優先級寄存器沒設正確;
- 中斷矢量表中各個entry與中斷源的對應關系錯誤;
- 中斷矢量表地址設置錯誤,很多CPU會要求中斷矢量表的地址要設置在偶數地址或是4的倍數,甚至是128KB的倍數。
那如何判斷ISR有沒有被正確執行呢?一般的方法是選擇一個簡單的中斷源(例如除0錯誤中斷),在其ISR中設定一個斷點,然后單步執行,看能否順利執行ISR程序及正確返回中斷發生的地方(除零指令的下一條語句)。
(3)存儲器測試
存儲器出問題的地方有:
-
硬件方面:數據線、地址線連接錯誤;
-
軟件方面:SRAM、NOR Flash、ROM不需要額外電路,直接可以使用,但SDRAM則還需要額外的SDRAM Controler電路才能使用,程序必須先設定好SDRAM Controler的配置(SDRAM大小、速度等);
-
外部存儲器的時序設置,若時序設定太快,系統會不穩定,太慢,則系統性能變差。一般CPU的Timing設定表會說明應該如何設定。
-
在進行下部工作前要先測試存儲器的每一個Byte,確保讀寫(如果可以寫入的話)正常。方法是對每一個字節依次寫入0x00、0xFF、0x55、0xAA,確保每一位都會被寫入0與1。
-
int SRAM_testing(void) { int i,counter =0; //待測RAM起始地址為0x2000000,大小為2MB. unsigned char *pointer = (unsigned char *)0x2000000; unsigned char data[4]={0x00,0xFF,0x55,0xAA}; for(i=0; i<4; i++) { // 逐一對每個字節寫入某特殊值 for(j=0; j<(8*1024*1024); j++) pointer[i] = data[i] // 逐一讀出每個字節,判斷寫入的值是否正確 for(j=0; j<(8*1024*1024); j++) pointer[i]==data[i]?::counter++; } return counter; //返回出錯字節的個數 }
-
對於只讀ROM,如何驗證燒錄到存儲器中的數據和原始映像文件一致呢?一般會采用校驗和檢驗法。即分別計算原始映像文件和燒錄到ROM中文件的校驗和是否相等。
-
/*************************************************************** Function Name: calculate_ROM_checksum Function Purpuse:計算起始地址為0x2000000,size為8MB存儲器的校驗和 ****************************************************************/ unsigned long calculate_ROM_checksum(void) { unsigned long checksum = 0; unsigned char *pointer = 0x2000000; for(i=0; i<(8*1024*1024); i++) checksum += pointer[i]; return checksum; }
(4)CPU初始化
在Boot-Loader階段因該做好以下CPU相關的設定:
- 設定堆棧指針寄存器SP;
- 設定狀態寄存器,禁止中斷;
- 設定中斷矢量表指針;
- 設定CPU執行狀態(時鍾時序);
- 設定存儲器控制器(如果用到了類似SDRAM的存儲器);
- 設定CPU操作各存儲器的時序;
- 設定CPU的PIN腳功能;
- 初始化外圍設備(LCD Controler、USB Controler、SD卡接口等)
3、載入程序段與數據初始化
(1)載入data段
有初值的全局變量必須被存儲在可執行文件中、被燒錄到ROM里。但執行時因為這些全局變量的值會被改變,所以當然不能在ROM里運行,連接時必須尋址到RAM中。正因為這種 “存儲在ROM,運行在RAM” 的特性,才有傳輸data段的需要,且必須在所有程序使用全局變量前完成這些事。
上圖中,data段的內容原本在可執行文件中的rodata段之后,但執行時,需要將data段復制到RAM中的bss段之后。連接腳本如下:
.data __END_bss : AT(__END_rodata)
{
__START_data = .;
*(.data);
__END_data = .;
// 定義可在程序中使用的變量“__START_data_LMA”,表示data段的存儲起始地址LMA
__START_data_LMA = LOADADDR(.data);
//定義可在程序中使用的變量“__SIZE_DATA”,表示data段的大小
__SIZE_DATA = __END_data - __START_data;
}
傳輸程序如下:
/************************************************** Function Name: copy_data_section() Function Purpuse:將可執行文件中的數據段復制到內存中 ***************************************************/
extern unsigned long *__START_data;
extern unsigned long *__START_data_LMA;
extern int __SIZE_DATA;
void copy_data_section(void)
{
int i;
unsigned long *dest = __START_data;
unsigned long *src = __START_data_LMA;
//假設data段的大小是4的整數倍個字節
for(i=0; i<(__SIZE_DATA/4); i++)
dest[i] = src[i];
}
(2)設定bss段
bss段的設定較為簡單,因為bss段里的成員都是沒有初始值的全局變量,所有根本不需要存儲空間,在執行時只要把bss段的執行空間(VMA)都設為0即可。
/******************************************* 定義bss段,起始地址(VMA)從0開始 ******************************************/
.bss 0x0 :
{
__START_bss = .;
*(.bss);
__END_bss = .;
//定義可在程序中使用的變量:__SIZE_BSS
__SIZE_BSS = __END_bss - __START_bss;
}
設定bss段為0的代碼如下:
/************************************************** Function Name: clear_bss_section() Function Purpuse:將bss段清零 ***************************************************/
extern unsigned long * __START_bss;
extern int __START_BSS;
void clear_bss_section(void)
{
int i;
unsigned long * dest = __START_bss;
//假設bss段的大小為4的整數倍字節大小
for(i=0; i<(__SIZE_BSS/4); i++)
dest[i] = 0;
}
Attention:在boot階段,data段和bss段一定要先設定,否則執行期間全局變量的值就不正確。換句話說,在設定完data和bss段之前,boot-load程序是不能使用全局變量的,如果一定要使用,那就避免在定義全局變量時賦值,一定要在程序內明確賦值才行。例如:

(3)載入text段
當某個系統程序或者應用程序模塊需要較高的執行速度時,往往可以將他們復制到系統內存中執行。但系統內存往往空間有限,不可能同時全部加載進去。所以我們一般會寫一個函數,並尋址到同一個地址,在需要時才做載入的動作。
各種類型的存儲器性能由大至小分別為:CPU寄存器、CPU cache、CPU內部RAM、外部SRAM、NOR Flash、SDRAM、Mask ROM、NAND Flash。
NAND Flash:價格低,容量大,可把其想象成類似硬盤的設備,只不過無法直接尋址操作,程序無法再上面直接執行;
NOR Flash:價格高,容量小,但讀數據快,可把其想象成可重復寫的ROM,程序可在上面直接運行。
Mask ROM:成本高,容量有限,但程序可直接在上面運行;
SDRAM:性價比高,一般作為系統的外置內存,程序可直接在上面運行;
SRAM:價格昂貴,容量小,一般作為系統的內置內存,程序可在上面直接運行。
(4)幾種系統存儲器架構
-
從NAND Flash啟動的架構:
-
-
啟動流程為:
- 上電后,CPU內置程序會從NAND Flash的特定地址(一般是第一個block塊地址)讀出Boot-Loader程序到CPU的內部內存中。
- CPU將控制權交給內部存儲器中的Boot-Loader;
- Boot-Loader初始化SDRAM,再從NAND Flash中將主程序載入到SDRAM中;
- Boot-Loader將控制權交給主程序。
獲取更多知識,請點擊關注:
嵌入式Linux&ARM
CSDN博客
簡書博客
oader程序到CPU的內部內存中。- CPU將控制權交給內部存儲器中的Boot-Loader;
- Boot-Loader初始化SDRAM,再從NAND Flash中將主程序載入到SDRAM中;
- Boot-Loader將控制權交給主程序。
獲取更多知識,請點擊關注:
嵌入式Linux&ARM
CSDN博客
簡書博客
知乎專欄