為ARM Cortex-M系列晶元編寫Bootloader
為ARM Cortex-M系列晶元編寫Bootloader
本文僅在ARM Cortex M3/M4晶元上進行過測試
1.引言
Bootloader用於用戶程序的引導,其用途在於軟體啟動、固件升級等,Bootloader編寫的核心內容是向量表的重定位。為了讀者能夠比較清晰了解Bootloader的機制,小軍會說明CMSIS啟動文件的機理,為此本文分為以下三個方面:
- CMSIS啟動文件簡單分析
- Bootlader的機理及實現
- 向量表重定向的應用
2.CMSIS啟動文件簡單分析
下述為Adu360(ARM Cortex-M3內核)晶元的啟動代碼,由於符合CMSIS標準的啟動文件大同小異,本文將以此啟動文件作為示例,詳細代碼如下:
Stack_Size EQU 0x00000400nn AREA STACK, NOINIT, READWRITE, ALIGN=3nStack_Mem SPACE Stack_Sizen__initial_spnnHeap_Size EQU 0x00000200nn AREA HEAP, NOINIT, READWRITE, ALIGN=3n__heap_basenHeap_Mem SPACE Heap_Sizen__heap_limitnn PRESERVE8n THUMBnn; Vector Table Mapped to Address 0 at Resetn AREA RESET, DATA, READONLYn EXPORT __Vectorsn EXPORT __Vectors_Endn EXPORT __Vectors_Sizenn__Vectors DCD __initial_sp ; Top of Stackn DCD Reset_Handler ; Reset Handlern DCD NMI_Handler ; The NMI handlern n;中間省略若干個中斷向量nnt DCD PWM2_Int_Handler ; PWM2 [38]nt DCD 0 ; [39]n__Vectors_Endnn__Vectors_Size EQU __Vectors_End - __Vectorsnn AREA |.text|, CODE, READONLYnn; Reset handlernReset_Handler PROCn EXPORT Reset_Handler [WEAK]n IMPORT SystemInitn IMPORT __mainn LDR R0, =SystemInitn BLX R0n LDR R0, =__mainn BX R0n ENDPnn; Dummy Exception Handlers (infinite loops which can be modified)nnNMI_Handler PROCn EXPORT NMI_Handler [WEAK]n B .n ENDPn n;中間省略若干弱引用的中斷函數nnPWM2_Int_Handler n B .nn ENDPnn ALIGNnn IF :DEF:__MICROLIBn n EXPORT __initial_spn EXPORT __heap_basen EXPORT __heap_limitn n ELSEn n IMPORT __use_two_region_memoryn EXPORT __user_initial_stackheapn n__user_initial_stackheapnn LDR R0, = Heap_Memn LDR R1, =(Stack_Mem + Stack_Size)n LDR R2, = (Heap_Mem + Heap_Size)n LDR R3, = Stack_Memn BX LRnn ALIGNnn ENDIFnn ENDn
啟動程序還是比較簡單的,其主要分為三個部分:
- 堆棧的預分配
- 中斷向量表定義
- 堆棧的初始化
ARM Cortex-M系列晶元堆棧的相關操作為C準備運行時環境,這裡不做細說;中斷向量表為晶元運行的根基,我們來重點分析一下中斷向量表:
- 中斷向量表的起始為主棧指針(MSP)的初始值,用於在C運行時入口函數初始化堆棧之前建立基本的C運行時環境;
- 緊接著是向量表的第一個中斷向量複位向量,其不僅僅作為系統的一個中斷存在,還是晶元執行程序的入口函數,其具體代碼如下:
; Reset handlernReset_Handler PROCn EXPORT Reset_Handler [WEAK]n IMPORT SystemInitn IMPORT __mainn LDR R0, =SystemInitn BLX R0n LDR R0, =__mainn BX R0n ENDPn
在複位向量中調用了SystemInit函數及__main函數,SystemInit函數為CMSIS定義的晶元初始化函數,用於對晶元的時鐘等進行初始化配置,__main函數為C運行時庫的入口函數,所以跳轉到Reset_handler函數中可以啟動應用;
- 後面則是各種弱引用的中斷向量,可以被C代碼中同名的強引用定義取代;
一般,ARM Cortex-M晶元從存儲器地址0x00000000處開始執行程序,通過__initial_sp初始化主棧指針,通過執行Reset_handler執行main函數中的程序。
2.Bootloader機理及其實現
如果我們希望升級系統中的固件或者從SD卡(或網路)中載入程序執行,需要執行如下步驟:
- 啟動Bootloader
- 執行Bootloader中的任務載入用戶程序
- 切換到用戶程序中執行 這時,我們就需要用到ARM Cortex-M內核的向量表重定向機制,其提供了一個中斷向量表偏移寄存器(SCB->VTOR),改寄存器中的值為向量表的起始地址。
其示常式序為:
/* 在調用此函數之前完成用戶應用程序的載入和拷貝 */nn void retarget_vector(uint32_t new_addr)n {n __DMB(); /* 數據存儲器屏障 */n SCB->VTOR = new_addr;n __DSB(); /* 數據同步屏障,保證在其後中斷均為新的地址 */n }n
重定向之後,我們還需要跳轉到用戶程序中運行,其示常式序為:
/* 在調用此函數之前完成重定向操作 */nn__asm void jump_to_application(uint32_t addr)n{n LDR SP, [R0] ; 設置用戶程序MSP的值n LDR PC, [R0, #4] ; 運行用戶程序的Reset_handlern}n
在Bootloader的編寫中還需要注意以下幾點:
- 固件採用hex格式還是bin格式
使用bin格式,因為hex文件附帶地址信息,並不是我們需要的純二進位文件;bin格式為按存儲順序的二進位文件
- 注意ARM晶元的大小端字序
有可能存儲設備和ARM晶元的大小端不一致,導致載入的程序不符合ARM晶元的大小端字序,從而導致程序無法運行;
- VTOR的地址有條件限制
使用VTOR時需要將中斷向量表的大小拓展到最近的2的冪次方,且新向量的基址必須要對齊到這個數值。
- 什麼樣的固件才能升級
這裡的固件升級對固件編譯情況是有要求的,比如說想要把固件放到0x4000-0x20000的位置,固件在編譯時需要設置其ROM的啟動地址為0x4000,大小為0x1C000,bootloader複製代碼到0x4000-0x20000,並且中斷向量表重定向後固件才可以正常運行。
- 跳轉用戶程序之前需要清理BootLoader使用過的資源
為了防止對用戶程序產生副作用,Bootloader程序需要關閉所有使用過的晶元資源,最好關掉中斷(感謝@望海樓提醒。 )
Bootloader的實現過程可以總結如下(詳細見《ARM Cortex-M3 與 Cortex-M4 權威指南》):利用啟動ROM中的向量表啟動Bootloader- 啟動Bootloader中的任務,完成用戶程序的載入
- 設置VTOR指向用戶程序存儲器中的向量表
- 跳轉到用戶程序存儲器的向量表中的中斷向量
3.向量表重定向的應用
- Bootloader的編寫
- 應用程序載入到RAM中執行,其類似於Bootloader
- 動態修改向量表,有些情況下ROM中可能會有一個中斷的多個處理示例,可能在應用的不同階段在它們之間切換,這種情況下,可以將向量表從程序存儲中複製到RAM中,設置VTOR指向RAM中的向量表,RAM中的內容可以任意時間修改,因此可以在應用的不同階段使用不同的中斷向量。
此部分詳細見《ARM Cortex-M3 與 Cortex-M4 權威指南》
推薦閱讀:
※把51的7段數碼管顯示程序移植到ZYNQ上(三)——SDK下的程序調試和結果
※ARM裸機1期加強版最新進度(更新時間11月8日)
※不如高通835:Anandtech 對華為海思麒麟970 的測試
※ARM與中方合資並由中方控股是一招妙棋
※加入ARM陣營到底是明智之舉,還是作繭自縛?
TAG:ARM | bootloaderAndroid | 嵌入式系统 |