DK45
什麼是IAPIAP-in application programming,就是在應用中程式設計的意思,在產品釋出以後,不管是增加功能啊,或者對bug修復啊,都可以對原來韌體進行更新升級。
傳統IAP思路基於stm32做的iap大多數的思路都是先設計一個bootloader,如果需要升級呢就跳轉到bootloader用來更新後面的應用程式。應用程式的空閒呢可以是一個或者兩個。如下圖所示,兩個app肯定就要限定每個app的大小,但是它相比一個app空間會更安全,因為它隨時都會存在一個可以工作的app。
這種方式用的比較多,但是也會存在一些問題:
需要維護bootloader和app兩套程式碼bootloader無法更新,所以要確保bootloader完全沒有bug像單APP空間如果更新失敗了,那麼只能停留在bootloader區域雙bank IAP原理而stm32L0系列呢(部分型號)提供了一套雙BANK機制可以用來做雙儲存區線上升級。基於該雙bank方式就可以拋棄bootloader,維護起來更方便。不過要理解雙bank還是稍微有那麼一點複雜,好的結果是用來卻很方便。
我們先從巨集觀上理解一下雙bank的原理:
如圖所示,它的flash被平均分為兩塊,一塊兒為bank1,一塊兒為bank2。當在bank1中執行app時就可以把新更新的韌體寫入到bank2,寫完以後就切換到從bank2啟動執行新的app。如果當前在bank2執行就把新韌體寫入bank1,寫完以後切換從bank1啟動。
原理就是這樣很簡單,但是這裡面有個關鍵的問題點,在沒有bootloader的情況下如何實現從bank1或者bank2啟動。這就是stm32L071cb自帶的一個主要特性(其他型號是否有請自查手冊,目前只有stm32L0、stm32L4、stm32G4中的某些型號有這個特性)。下面以stm32L071cb為例來分析如何實現切換。
首先出場的是UFB。what is UFB?這是存在於SYSCFG暫存器中的一個位,它有兩個值:0或者1,功能是:
0:FLASH bank1 被對映在0x8000000地址上1:FLASH bank2 被對映在0x8000000地址上用過stm32的同學可能思維裡面有一個固定的概念:stm32的flash都是從0x8000000開始的。換句話說,我從0x8000000讀取出來的資料肯定都是同一片flash區域。然而,在stm32L071CB上面並不是。你從0x8000000讀出來一個數據可能是BANK1開頭的資料也可能是BANK2開頭的資料。到底是哪個取決於UFB這個位當前的值是0還是1。
這個UFB先記住,等待後面綜合起來理解。
雙BANK啟動能力這個可能一下抖出來的內容有點多,請打起精神。
這又要從ARM的啟動方式說起了,可以看我上一篇文章有詳細的總結:STM32線上升級中斷向量重定向深度剖析。要記住關鍵的一點就是ARM是從0x00000000取的第一條指令。而STM32正常情況下之所以從使用者flash開始執行,是因為使用者flash被對映到了0x00000000地址上。0x8000000和0x00000000都能訪問到flash同一個區域,所以才讓看起來貌似是從0x8000000執行起來的一樣。
當從bank1啟動時,實際上就是普通的啟動模式。上電後用戶flash最底部(BANK1區域)被對映到0x00000000地址,然後CPU直接從這裡取指令開始執行。
那重點就是從BANK2啟動的流程是怎樣的,這裡要給大家再介紹個新的配置選項:BFB2,存在於option bytes裡面的一個位。這個位呢就可以用來選擇從bank2啟動。因為在option bytes裡面,所以掉電是不會丟失的。
當boot0=0的時候,stm32預設就會從使用者flash啟動。也就是使用者flash被對映到0x0地址。但是當boot0=0並且BFB2=1的時候,系統flash會被對映到0x0地址,系統flash也就是stm32內建的bootloader。
這時候進內建bootloader以後呢就會去檢查BANK2有沒有有效程式碼(當在bank的第一個資料所指向的地址是有效的(指向棧頂地址),則認為程式碼就是有效的),如果有就會把UFB設定為1(UFB前面介紹過,忘了往上翻再看一遍),bank2被對映到0x8000000地址,然後跳轉到BANK2開始執行。所以從BANK2啟動和從BANK1啟動是不一樣的,也比從bank1啟動流程複雜一些。bank2啟動流程如下:
所以我這裡畫了一張stm32L071cb上電到從bank1或者bank2開始執行的流程圖:
雙BANK升級中的中斷向量表該怎麼辦從bank1啟動時的中斷向量表
仔細再看上面的流程圖,如果從bank1啟動,bank1是被對映到0x0地址的,而ARM核心預設也是從0x0處讀取中斷向量表。所以不用做任何設定都可以正常的執行。
從bank2啟動時的中斷向量表但是從BANK2啟動就沒那麼簡單了,假如BFB2=1時,CPU並不是直接跳轉到bank2執行,而是先進入了系統flash區域(內建bootloader)。這時候實際上是stm32內建bootloader被對映到了0x0地址。之後流程只是把bank2對映到了0x8000000,然後就從BANK2啟動了。
這樣如果不管中斷向量表位置會產生什麼現象呢?當中斷髮生了,ARM核心會跳轉到0x0地址處(跳回了內建bootloader)找到中斷向量。那這樣程式豈不亂套了,我們自己編寫的中斷服務函式將永遠不會被執行到。
所以進入到BANK2以後一定要做中斷向量表的重定位。這裡我列出來三種是被我在stm32L071cb上面驗證過的思路:(關於更多如何定位中斷向量表還是看我上一篇文章:STM32線上升級中斷向量重定向深度剖析)
從bank2啟動以後就修改VTOR為0x8000000從bank2啟動以後修改MEM_MODE,重新把使用者flash定位到0x0地址從bank2啟動以後把flash中中斷向量表拷貝到RAM中,並修改VTOR重定位中斷向量到RAM區最終我覺得最簡單的是第一種,就是上電以後就修改VTOR到0x8000000。當然還有另一個好處就是不管從bank1或者bank2啟動都可以執行這一條。雖然對bank1啟動來說這個設定不是必須的,但是執行了也無害。這樣就能做到bank1和bank2的app程式碼處理流程儘量統一。
另外還有就是實際上如果你使用的是STM32 HAL庫,在SystemInit()中實際上已經幫我們重新設定VTOR到0x8000000。所以我們就無需再次新增修改VTOR的程式碼了。
雙bank切換的方法對於IAP把程式碼新韌體寫到另一片bank之後,就要切換到從另一個bank啟動。儘管前面流程原理有點複雜,但是實現它的也比較簡單,主要執行以下步驟:
先檢查BFB2位確定當前處於哪個bank如果在BANK1就設定BFB2=1,如過在BANK2就設定BFB2=0執行HAL_FLASH_OB_Launch()。執行完該步驟以後系統會自動復位從BANK2啟動時無法響應中斷後來經過各方排查,定位到ARM核心的一個暫存器:PRIMASK。這個暫存器從stm32參考手冊上是查不到的,如果了解詳情可以去看arm-cortex M0+程式設計手冊。
在切換到從bank2啟動以後,中斷向量表偏移我也重新設定,但是還是出現了中斷無法響應的問題。systick中斷進不去,這樣就導致HAL庫執行會一直等待systick計時時間到的地方。
說這個暫存器你可能不熟悉,但是__enable_irq()和__disable_irq()實際上操作的就是這個暫存器的值。預設這個值是0,是不遮蔽任何中斷的。但是從bank2啟動時候發現這個值變成了1。這樣就能解釋為什麼中斷進不去了,它為1的時候除了不可遮蔽中斷,其他的中斷都一律遮蔽不響應。
而為什麼從bank2啟動才變成1,從bank1啟動就正常。再看下前面bank2啟動的流程是先從內部bootloader跳轉過來的,所以肯定是內部bootloader把PRIMASK給配置成1了。
所以最終解決起來就簡單了,就是從bank2啟動以後,就呼叫一條__enable_irq()重新修改PRIMASK值變成0就可以了。