stm32L0無用戶bootloader實現IAP

DK45

什麼是IAP

IAP-in application programming,就是在應用中編程的意思,在產品發佈以後,不管是增加功能啊,或者對bug修復啊,都可以對原來固件進行更新升級。

傳統IAP思路

基於stm32做的iap大多數的思路都是先設計一個bootloader,如果需要升級呢就跳轉到bootloader用來更新後面的應用程序。應用程序的空閒呢可以是一個或者兩個。如下圖所示,兩個app肯定就要限定每個app的大小,但是它相比一個app空間會更安全,因為它隨時都會存在一個可以工作的app。

stm32L0無用戶bootloader實現IAP

這種方式用的比較多,但是也會存在一些問題:

  • 需要維護bootloader和app兩套代碼
  • bootloader無法更新,所以要確保bootloader完全沒有bug
  • 像單APP空間如果更新失敗了,那麼只能停留在bootloader區域

雙bank IAP原理

而stm32L0系列呢(部分型號)提供了一套雙BANK機制可以用來做雙存儲區在線升級。基於該雙bank方式就可以拋棄bootloader,維護起來更方便。不過要理解雙bank還是稍微有那麼一點複雜,好的結果是用來卻很方便。

我們先從宏觀上理解一下雙bank的原理:

stm32L0無用戶bootloader實現IAP

如圖所示,它的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運行起來的一樣。

stm32L0無用戶bootloader實現IAP

當從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啟動流程如下:

stm32L0無用戶bootloader實現IAP

所以我這裡畫了一張stm32L071cb上電到從bank1或者bank2開始運行的流程圖:

stm32L0無用戶bootloader實現IAP

雙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在線升級中斷向量重定向深度剖析)

  1. 從bank2啟動以後就修改VTOR為0x8000000
  2. 從bank2啟動以後修改MEM_MODE,重新把用戶flash定位到0x0地址
  3. 從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就可以了。


分享到:


相關文章: