STM32一種從用戶代碼調(diào)用系統(tǒng)存儲器中Bootloader 的方法
前言
大家都知道,任何STM32 都包含有一塊系統(tǒng)存儲器(System Memory),里邊存儲著內(nèi)部的啟動代碼Bootloader。不同的
STM32 型號所支持的用于升級代碼的通訊口不盡相同,需要參考應(yīng)用筆記AN2606。但是,有一個問題避免不了,那就是如
何進入System Memory 去執(zhí)行Bootloader?通常的辦法都是將BOOT1 和BOOT0 進行配置:BOOT0 拉高,BOOT1 拉低
(有些型號的BOOT1 由選項字節(jié)nBOOT1 進行控制)。可是在一些產(chǎn)品中,由于外觀的要求,往往不方便在外邊開口去放
置按鍵或跳線來改變BOOT 腳的電平。而且,用戶并不想自己寫IAP 代碼,覺得麻煩。特別是一些產(chǎn)品,需要使用USB
DFU 來進行代碼升級的,而在產(chǎn)品功能中USB 又沒用到,用戶就會覺得自己為了一個通過USB 進行代碼升級的功能,去寫
IAP 的話,需要去熟悉USB 的代碼,覺得麻煩,而且這些USB 的代碼還占用了用戶的程序空間。對于這些用戶來講,他們很
希望能在不管BOOT 腳的情況下能夠去調(diào)用STM32 中System Memory 的Bootloader,完成代碼升級功能。
問題
某客戶在其產(chǎn)品的設(shè)計中,使用了STM32F411。由于產(chǎn)品外觀的要求,無法在外部對BOOT 腳進行控制,而且外觀上只有
USB 接口是留在外邊的,需要使用USB DFU 進行升級。而且USB 接口只用于代碼升級,沒有其他功能,所以客戶不想去碰
USB 代碼,希望能夠直接使用System Memory 中的Bootloader 進行代碼升級。
調(diào)研
1.判斷其可行性
首先,打開應(yīng)用筆記AN2606《STM32 microcontroller system memory boot mode》,翻到3.1 Bootloader activation 一節(jié)的
最后,可以看到如下信息:
這里的意思就是說,用戶可以通過從用戶代碼跳轉(zhuǎn)到系統(tǒng)存儲器去執(zhí)行Bootloader。但是,在跳轉(zhuǎn)到Bootloader 之前,有幾
個事情必須要做好:
1) 關(guān)閉所有外設(shè)的時鐘
2) 關(guān)閉使用的PLL
3) 禁用所有中斷
4) 清除所有掛起的中斷標(biāo)志位
最后,可以通過離開Bootloader 激活條件且產(chǎn)生一個硬件復(fù)位或者直接使用Go 命令去執(zhí)行用戶代碼。
那么,如何從用戶代碼跳轉(zhuǎn)到System Memory 中去呢?這個其實并不難,如果寫過IAP,或者看過關(guān)于IAP 的應(yīng)用筆記中的
參考代碼的話,比如應(yīng)用筆記AN3965“STM32F40x/STM32F41x in-application programming using the USART”及其參考
代碼STSW-STM32067,都應(yīng)該知道,IAP 的啟動代碼通過重新設(shè)置主堆棧指針并跳轉(zhuǎn)到用戶代碼來執(zhí)行用戶代碼的。同樣
的道理,只要知道System Memory 的地址,一樣可以從用戶代碼通過重新設(shè)置主堆棧指針并跳轉(zhuǎn)到System Memory 來執(zhí)行
Bootloader。而System Memory 地址可以從參考手冊來獲得。比如,查看STM32F411 的參考手冊RM0383,可以找到如下
的表格:
可以知道STM32F411 的System memory 地址從0x1FFF0000 開始。
那很多人又會問了,我的代碼很復(fù)雜,用了很多外設(shè),開了很多中斷,可是要跳轉(zhuǎn)到System Memory 中的Bootloader,需要
關(guān)所有外設(shè)的時鐘,需要關(guān)PLL,需要關(guān)閉所有中斷,需要禁用所有的中斷,清除所有掛起的中斷。這可是一項非常龐大的
的任務(wù)啊!所以,在這里,我們需要一個更簡單的事情來完成這項龐大的任務(wù)。其實真的就有這么簡單的一個方法——復(fù)位!
通過軟件復(fù)位來實現(xiàn)這一目的。但是,復(fù)位后,又怎么知道還記得我們要去做代碼升級呢?這又要用到STM32 另一個特性了,
那就是后備數(shù)據(jù)寄存器Backup Data Registers 在軟件復(fù)位后會保留其值,這樣給了我們在復(fù)位前后做一個標(biāo)志的機會。
這樣,考證下來,客戶的需求是具備可行性的。接下來需要做的是理清思路。
2.軟件流程
這里使用32F411EDISCOVERY 板來設(shè)計一個參考例程:設(shè)計一個用戶程序,讓LED3 進行閃爍;當(dāng)用戶按鍵被按下,產(chǎn)生
EXTI 中斷,在中斷中選擇后備數(shù)據(jù)寄存器RTC_BKP0R,寫入值0x32F2,然后產(chǎn)生軟件復(fù)位;軟件復(fù)位后,在運行代碼的
最前面對RTC_BKP0R 進行判斷,如果其值不是0x32F2 則直接去運行用戶代碼,如果其值為0x32F2 則是需要跳轉(zhuǎn)到
Bootloader 去進行代碼升級,并在跳轉(zhuǎn)前將RTC_BKP0R 清零。這樣,在進入Bootloader 后,客戶進行USB DFU 升級后,
將來不會因為非需要升級代碼的復(fù)位而誤入Bootloader。
來看軟件流程圖,先來看主程序的流程圖:
再來看EXTI 中斷的流程圖:
3.主要代碼
使用STM32F4Cube 庫來開發(fā)這個例程。先來看位于main.c 中的main 函數(shù):
Main 函數(shù)很簡單,配置系統(tǒng)時鐘,對使用的LED 進行初始化,然后配置了用戶按鍵的EXTI 中斷,然后就進入主循環(huán)了。前
面說到,要實現(xiàn)用戶的功能程序為LED3 閃爍,在主循環(huán)我們沒看到,是因為在Cube 庫中,會使用SysTick,所以把LED3
的閃爍放到SysTick 的中斷代碼中了,查看stm32f4xx_it.c,如下:
從main 函數(shù)最開始的那段注釋中知道,跳入main 函數(shù)前,在startup_stm32f411xe.s 中早已經(jīng)先調(diào)用執(zhí)行了位于
system_stm32f4xx.c 中的SystemInit 函數(shù)。SystemInit 函數(shù)主要執(zhí)行初始化FPU、復(fù)位RCC 時鐘寄存器、配置向量表等功
能。由于我們希望在最原始的狀態(tài)下進入System Memory,所以我們將跳轉(zhuǎn)到System Memory 放在這個函數(shù)的最前頭,如
下:
可以看到,在函數(shù)的最前面對RTC_BKP_DR0 進行了判斷,如果其值為0x32F2 的話,則先啟動備份域的訪問時序,如
RM0383 中5.1.2 Battery backup domain 所描述的:
然后將RTC_BKP_DR0 清零,再關(guān)閉執(zhí)行這次操作所打開的時鐘。
主堆棧指針MSP 的初始值位于向量表偏移量為0x00 的位置,復(fù)位Reset 的值則位于向量表偏移量為0x04 的位置。對于
STM32F411 來說,當(dāng)執(zhí)行System Memeory 中的Bootloader 時,MSP 的初始值位于0x1FFF0000,而Reset 則位于
0x1FFF0004。所以在程序中,使用__set_MSP(*(__IO uint32_t*) 0x1FFF0000);來重新設(shè)置主堆棧指針,而后再跳轉(zhuǎn)到
0x1FFF0004 去執(zhí)行Bootloader。
再來看位于stm32f4xx_it.c 中的EXTI 中斷程序:
及其位于main.c中的Callback函數(shù):
當(dāng)判斷到用戶按鍵按下,需要進行用戶代碼升級時,先啟動備份域的訪問時序,將RTC_BKP_DR0 的值寫為0x32F2。再讀
回來判斷是否寫入成功,以方便調(diào)試。如果寫入成功后,則就調(diào)用HAL_NVIC_SystemReset()進行軟件復(fù)位。重新復(fù)位后,
就可以進入System Memory 了。
4.實驗
使用32F411EDISCOVERY 來做實驗。
1) 先將程序編譯,下載到32F411EDISCOVERY 板,可以看到LED3 在進行閃爍
2) 按下User 按鍵,LED3 熄滅,已經(jīng)進入System Memory 中的Bootloader
3) 打開DfuSeDemo 軟件,此時Available DFU Devices 中沒有任何顯示
4) 將一根USB Micro 連接線插入32F411EDISCOVERY 板的CN5,可見LED7 亮起,USB 已連接
5) 驅(qū)動完成后,可以再查看一下DfuSeDemo,Available DFU Devices 已經(jīng)顯示為“STM Device in DFU Mode”,代
表已經(jīng)成功驅(qū)動并正常工作了
6) 之后就是正常的升級代碼的流程了,點“Choose”按鈕選擇要更新的代碼,這里準(zhǔn)備好了一個
32F411EDISCOVERY 板的Demo 程序經(jīng)過Dfu file manager 軟件生成的32f411ediscovery.dfu 的文件,導(dǎo)入
7) 點“Upgrade”按鈕進行升級,彈出的對話框選Yes 就可以了,之后就升級成功了
8) 再點一下“Leave DFU mode”,進度條顯示“Successfully left DFU mode!”,就可以進入更新后的用戶代碼了,
可以看到4 個LED 燈正常歡快的滾動和閃爍著……
注意
此例程僅為驗證其可行性,在實際應(yīng)用中,有不盡完善的地方請用戶自行完善。另外,有幾個需要注意的地方:
1) 此Demo 代碼基于STM32Cube_FW_F4_V1.11.0 撰寫,解壓縮后,可將其放入
STM32Cube_FW_F4_V1.11.0ProjectsSTM32F411E-DiscoveryTemplates 替換掉原來的源代碼文件,即可編譯
運行。
2) 此程序使用按鍵按下作為條件來觸發(fā)軟件代碼升級,用戶可以根據(jù)自己的情況修改觸發(fā)條件,如多個按鍵同時按下,
等等。
3) 當(dāng)用戶應(yīng)用中使用了RTC 的話,RTC 時鐘源一旦被選擇后是無法修改的,除非備份域被復(fù)位。在RM0383 關(guān)于
RCC_BDCR 的描述中有提及:
4) 關(guān)于如何使用Dfu file manager 生成.dfu 文件,請參考UM0412“Getting started with DfuSe USB device firmware
upgrade”或者實戰(zhàn)經(jīng)驗“利用USB DFU 實現(xiàn)IAP 功能”。
5) 關(guān)于Bootloader 中所使用的USB DFU 協(xié)議,請參考AN3156“USB DFU protocol used in the STM32 bootloader”。
文檔和代碼下載地址:
http://www.stmcu.org/document/detail/index/id-217309
http://www.stmcu.org/document/download/index/id-212785
實戰(zhàn)經(jīng)驗匯總:
http://www.stmcu.org/module/forum/thread-606863-1-1.html