引導加載程序幾乎包含在每個嵌入式系統中,并提供了一種在現場更新應用程序代碼的好方法,而無需訪問編程端口。與引導加載程序一樣重要的是,嵌入式開發人員在嘗試從引導加載程序跳轉到他們的應用程序代碼時經常會被出錯。跳躍需要干凈利落,但有幾個因素會導致問題,例如:
l 一次寫入寄存器(例如看門狗寄存器)
l 時鐘設置
l 堆棧和程序指針
l 外圍設置
開發人員可以通過兩種不同的方式從引導加載程序干凈地過渡到應用程序代碼。第一種方法涉及開發人員仔細匹配他們的應用程序代碼和引導程序設置。例如,開發人員將匹配看門狗寄存器、時鐘設置,甚至可能匹配UART等外設。引導加載程序將所有這些組件初始化為已知的系統狀態,并簡單地將程序執行交給應用程序代碼。這樣做的問題是,應用程序代碼中的任何更改也需要在引導裝載程序中進行更改。
處理引導加載程序和應用程序代碼之間跳轉的第二種也是更干凈的方法是遵循一個簡單的過程,將引導加載程序所涉及的任何設置恢復到它們最初的重置狀態。這將包括外設,如GPIO、時鐘,甚至修改堆棧和程序計數器。當嵌入式開發人員這樣做時,應用程序代碼完全不知道內存中正在執行另一個應用程序,唯一需要匹配的設置是只能寫入一次的寄存器!
準備從引導程序跳轉到應用程序代碼的過程很簡單,可以在下面找到:
l 確認應用復位向量已經編程
l 驗證應用程序校驗和及安全憑證
l 對外設進行去初始化,并將其置于復位狀態
l 將向量表寄存器設置為應用復位向量(ARM)
l 將堆棧指針寄存器設置為應用程序起始地址
l 將程序計數器設置為復位向量(跳轉到應用程序)
讓我們簡單討論一下每一步。在引導加載程序做任何事情之前,它需要驗證應用程序的完整性。第一步是驗證復位向量已經編程,并且不是0xFFFFFFFF或0x00000000。通常,擦除的閃存會設置其所有位,因此如果我們看到這種狀態,則引導加載程序知道有問題,應該保持在引導加載程序中,這是一種已知的安全狀態。請記住,我們假設兩者之間的任何編程值都是正確的,這可能是一個糟糕的假設,但對于今天嵌入式開發人員來說已經足夠好了。
接下來,引導裝載程序應該對應用程序空間執行校驗和計算,以確保應用程序是有效的。同樣,如果在更新過程中出現問題,可能會有一個帶有復位向量的部分程序,如果沒有校驗和,就無法知道這一點。最重要的是,引導裝載程序還應該檢查任何數字簽名或安全措施,以確保不僅應用程序是完整的,而且其來源也是正確的,它不是惡意軟件或修改過的程序。
一旦引導裝載程序驗證了應用程序是完整的并且來自正確的來源,就該回到初始狀態了。任何被觸摸的非一次寫入的外設都應該被放回到它們的復位狀態。最好的方法是查看所用的驅動器、驅動器初始化和訪問的寄存器,然后在數據手冊中查找這些寄存器。每個數據手冊都顯示了上電復位寄存器的值。對于每個被修改的寄存器,它們可以被復位到這些狀態。通常,我會避免更改時鐘寄存器、看門狗和一次寫入寄存器。我只是在應用程序和引導裝載程序之間匹配這些狀態。
此時,微控制器回到復位狀態,并準備運行應用程序代碼。在此之前,需要更新向量表寄存器,以指向應用程序的向量表位置,而不是引導加載程序。中斷向量表可以位于任何地方,因此引導裝載程序和應用程序鏈接器文件之間需要協調。例如,嵌入式開發人員將編寫如下單行代碼,其中PROGRAM_FLASH_BASE是應用程序的第一個向量表位置:
SCB _ VTOR =(uint 32 _ t)PROGRAM _ FLASH _ BASE;
一旦完成了這些,就該跳到應用程序了。開發人員可以通過幾種不同的方式從引導裝載程序跳轉到應用程序。一種方法是簡單地取消引用應用程序的重置向量位置。這樣做的問題是堆棧指針可能不在正確的位置,從而導致奇怪的行為。理想情況下,開發人員會設置堆棧指針,然后設置程序計數器。如何做到這一點將因微控制器而異。幾乎總是需要使用內聯匯編代碼來完成(這是我唯一一次提倡編寫匯編代碼)。對于ARM微控制器,下面是一個示例代碼片段:
void Flash _ start application(uint 32 _ t start address)
{
asm(" ldr SP,[r0,# 0]");
asm("ldr PC,[r0,# 4]");
}
根據所使用的編譯器,確切的代碼會略有不同。內聯匯編不是C標準,所以每個編譯器供應商都以不同的方式實現了它,或者在某些情況下根本沒有實現!讓我們來看看這是怎么回事。
為了最小化匯編語言代碼,將匯編語言代碼包裝在C函數中是至關重要的。原因是當startAddress被傳遞到Flash_StartApplication函數中時,它會自動存儲在寄存器r0中。有了這些知識,就沒有理由添加額外的匯編語言指令來將所需的起始地址加載到寄存器中。(是的,它為我們節省了一條匯編指令,但這樣做也更容易維護,更靈活)。然后,第一條匯編指令是獲取存儲在寄存器r0中偏移量為0(# 0)的值,并將其復制到堆棧指針(SP)寄存器。然后,第二條指令告訴處理器將r0中存儲的值加上偏移量4 (#4),并將其復制到程序計數器(PC)寄存器中。偏移量4實際上是將r0中存儲的值加4。執行的下一條指令將是應用程序代碼的復位向量。我們剛剛成功地進入了應用程序!
這就是全部了!按照這個過程,嵌入式開發人員現在可以很容易地從引導加載程序跳轉到你的應用程序代碼,并確保它將按照你期望的方式運行。