之前一篇的文章中,主要介紹了STM32的啟動(dòng)流程和內(nèi)存主要空間的分配,這篇文章將在上一篇文章的基礎(chǔ)上,來(lái)闡述一下STM32 Bootloader的實(shí)現(xiàn)。
STM32的內(nèi)存劃分
前面文章我們說(shuō)了,STM32上電后會(huì)從0x08000000地址處開(kāi)始運(yùn)行,因此,如果我們想要使得STM32在上電之處直接進(jìn)入進(jìn)Bootloader,那么其內(nèi)存的起始地址必須要從0x08000000處開(kāi)始。這一步是由單片機(jī)的硬件所決定的,無(wú)法通過(guò)軟件干預(yù)。
因此,在我們使用Keil軟件設(shè)計(jì)STM32 Bootloader的時(shí)候,一定要在Keil的工程中設(shè)置這個(gè)地址,當(dāng)然,如果你不設(shè)置也沒(méi)關(guān)系,因?yàn)镵eil默認(rèn)就是將單片機(jī)的軟件編譯到此地址的。如圖1所示。
圖1 Keil上電起始地址的設(shè)置
另外在說(shuō)一點(diǎn),起始地址右邊的Size我們也需要關(guān)注下,因?yàn)檫@個(gè)參數(shù)會(huì)影響到后續(xù)的內(nèi)存空間分配。
好了,接下來(lái)假設(shè)我們有一塊Flash容量為64KB的芯片,我們來(lái)為其劃分一下內(nèi)存空間。我們來(lái)計(jì)算下它的地址范圍為多少。
起始地址不需要多說(shuō),就是0x08000000。
64K的地址空間該怎么算呢?這個(gè)其實(shí)是一個(gè)進(jìn)制轉(zhuǎn)換的問(wèn)題,我們知道,在十進(jìn)制中,1KB=1024個(gè)字節(jié),而我們計(jì)算機(jī)中的一個(gè)字節(jié)就是一個(gè)地址單元,因此只要要使用64KB*1024就可以得出有多少個(gè)(十進(jìn)制)地址單元了。又由于計(jì)算機(jī)中的地址都是按照十六進(jìn)制編碼來(lái)排列的,所以還需要將這個(gè)十位數(shù)的地址空間轉(zhuǎn)換成十六進(jìn)制,這個(gè)地址空間就是0x10000。注意,以上這個(gè)0x10000指的是地址空間,指明64K Flash中有0x10000個(gè)字節(jié)的存儲(chǔ)單元,而我們的內(nèi)存地址又是從0開(kāi)始計(jì)算的,因此最終的地址范圍就是0x08000000~0x08010000。
計(jì)算出以上的地址范圍之后,我們就需要對(duì)其來(lái)劃分功能區(qū)了,首先假設(shè)我們的Bootloader不帶OTA(On The Air)升級(jí)功能,因此整個(gè)內(nèi)存空間至少要?jiǎng)澐殖蓛蓚€(gè)部分,第一個(gè)部分是從0x08000000起始的Bootloader區(qū)域,假設(shè)長(zhǎng)度為X,第二個(gè)部分是緊接著(當(dāng)然也可以不僅接著)Bootloader區(qū)域的應(yīng)用區(qū)域,其地址范圍為0x08000000 + X,其長(zhǎng)度為Y。
這個(gè)地址的劃分可以現(xiàn)根據(jù)Bootloader最終編譯的大小進(jìn)行動(dòng)態(tài)改變,不過(guò)需要注意的是,STM32內(nèi)存劃分要以半頁(yè)為最小單位,因?yàn)樵趯?duì)Flash編程時(shí),都是按照半頁(yè)來(lái)擦除的,所以如果你的程序不按照半頁(yè)來(lái)對(duì)齊,那么擦出的時(shí)候就會(huì)很尷尬,半頁(yè)=32個(gè)字=128個(gè)字節(jié)。
基于上述原因,我們暫定STM32中,前10K地址存放Bootloader程序,后面的地址,存放應(yīng)用,如圖2所示。
圖2 STM32內(nèi)存劃分
Bootloader代碼設(shè)計(jì)
Bootloader這個(gè)東西,對(duì)于我們來(lái)說(shuō)是一個(gè)特殊的程序,但是對(duì)于計(jì)算機(jī)來(lái)說(shuō),它和千千萬(wàn)萬(wàn)的普通程序沒(méi)有任何差別,因此我們也需要對(duì)這個(gè)程序進(jìn)行初始化,你可以設(shè)置時(shí)鐘,設(shè)置串口或者CAN總線資源等。這些都是必須要進(jìn)行的,我們文中就不討論。
比較重要的需要討論的一點(diǎn)是跳轉(zhuǎn)程序,因?yàn)锽ootloader除了完成一些額外的不適宜在應(yīng)用中完成的工作之外,最重要的一點(diǎn)就是程序跳轉(zhuǎn),即“JampToApp()”。所謂的程序跳轉(zhuǎn),那么程序跳轉(zhuǎn)該怎么實(shí)現(xiàn)呢?
思考下,跳轉(zhuǎn)最終就是通過(guò)Bootloader跳轉(zhuǎn)到APP的地址處,在C語(yǔ)言中和跳轉(zhuǎn)直接相關(guān)的便是指針了,因此既能保證跳轉(zhuǎn)到某個(gè)地址,又能保證是類似于函數(shù)能被運(yùn)行的,就只有“指向函數(shù)的指針“這一項(xiàng)了。沒(méi)錯(cuò),Bootloader的“JampToApp()”操作就是一個(gè)函數(shù)指針,只需要獲知APP區(qū)的地址,就可以進(jìn)行跳轉(zhuǎn)了。
我們?cè)谏弦还?jié)中以及指定了APP的地址就是0x08002800,那么是否只要跳轉(zhuǎn)到這個(gè)地址就可以了呢?答案當(dāng)然是否定的,因?yàn)槲覀冎罢f(shuō)過(guò),STM32內(nèi)存空間最起始的4個(gè)字節(jié)地址,存放的是棧頂指針,因此我們需要跳轉(zhuǎn)到0x08002800+4的地址處。
在跳轉(zhuǎn)的過(guò)程中,關(guān)閉中斷等工作也是必須的,具體代碼如圖3所示。
圖3 Bootloader中的跳轉(zhuǎn)程序
上面代碼中,有一個(gè)自定義的“TYP_drcPtr”類型,它起始只是一個(gè)void類型。
typedef?void?(*TYP_drcPtr)(void);?//Define?the?jump?pointer
跳轉(zhuǎn)問(wèn)題解決了,接下來(lái)的問(wèn)題就是何時(shí)跳轉(zhuǎn)?
這個(gè)何時(shí)跳轉(zhuǎn)很重要,我建議在程序的一開(kāi)始,初始化Systemclock之前就去判斷和跳轉(zhuǎn),因?yàn)镾TM32如果你在Bootloader中初始化了Systemclock之后,再去APP區(qū)初始化,會(huì)造成硬件錯(cuò)誤,因此我建議大家在設(shè)計(jì)Bootloader的時(shí)候,程序一開(kāi)始就是去檢測(cè)能否滿足跳轉(zhuǎn)條件,滿足了立馬跳,不滿足再去執(zhí)行硬件的初始化程序。如圖4所示。
圖4 STM32跳轉(zhuǎn)時(shí)機(jī)
好了,最后說(shuō)一句,“Talk is easy, show me your code”,我已經(jīng)調(diào)試成功的bootloader請(qǐng)自行g(shù)it。
https://gitee.com/huangqilong119/STM32_bootloader.git
關(guān)于STM32的Flash編程又是另外一個(gè)故事了,我們后續(xù)再說(shuō)。
評(píng)論