花了斷斷續續兩天時間在STM32上面寫了一個IAP(In Application Programing)Boot,期間多多少少還是遇到的了不少問題。現在就花點時間把這兩天寫的東西整理一下,就當是學習筆記吧。本人用的芯片是STM32F4系列,1M的FLASH,192KB的SRAM。
正文
不得不提的啟動方式
STM32支持三種啟動方式
1. FLASH啟動
2. SRAM啟動
3. 系統存儲器啟動
這三種啟動順序決定了上電后第一條指令的位置。如果你選擇FLASH啟動,則上電復位后PC指針指向第一條指令位置——0x08000000,如果你選擇SRAM啟動,則上電復位后PC指針指向第一條指令位置——0X20000000,若你選擇系統存儲器啟動,則上電復位后PC指針指向第一條指令位置——0X1FFF0000。
為什么會這樣呢?接下來我們分析一下STM32F4的存儲區地址結構。
MemoryRegions
上圖摘自《Cortex-M3與M4權威指南》。從這里我們可以看出M4內核支持4GB的存儲空間,從0X00000000-0X1FFFFFFF共512MB的空間是Code區,0x20000000-0x3FFFFFFF共512MB的空間是SRAM,其他的我們暫且不分析。我們再來看看《STM32F4xx Reference manual 》中 關於F4系列的物理內存映射
Memory mapping
從上面的物理內存映射圖我們可以看出:
0x08000000-0x080FFFFF: FLASH
0x1FFF0000-0x1FFF77FF: System memory
0x20000000-0x2001FFFF: SRAM
其中FLASH大小為1MB, SRAM大小為12KB, System memory大小為30KB。
在這里解釋三個問題:
《Cortex-M3&M4權威指南》中講到“系統上電復位后,從0x00000000處加載第一條指令…..”為什么上面提到的三種啟動方式都是從非0x00000000加載第一條的呢?-這就是“地址映射”的作用了。我們通過boot0&1引腳的配置,可以選擇不同的啟動方式,同時將flash或sram的基地址映射到啟動空間0x00000000處,這樣,加載的第一條指令位置便是我們相應的啟動方式對應的存儲設備的地址了。
三種啟動方式有何區別?顯然,第一點不同就是地址空間不同,導致了三種啟動方式啟動時代碼加載空間隨之改變;此外System memory是個真正的ROM,里面固化了廠家出廠時的BootLoader代碼,不可重寫。FLASH和SRAM則可自己重寫程序完成啟動,但是由於芯片特性原因,兩者的用途也不一樣。FLASH就是我們常說的閃存,具有”掉電不失性”,燒寫代碼后,代碼不會因為掉電而丟失,下次上電啟動時仍然可以正常啟動,為常見的啟動方式,但是缺點是運行速度較慢;SRAM是“靜態隨機存儲器”,具有“掉電易失性”,屬於RAM的一種,那么顯然我們不可能將代碼長保存到SRAM中,因為下次上電后仍舊沒有代碼。這種啟動方式常用於程序調試,即上電后下載代碼,運行,進行調試,運行速度比FLASH快。
為什么FLASH不是RAM也可以運行程序?FLASH分為兩種,一種是Nor FLASH, 一種是Nand FLASH。這兩者區別就不一一贅述,網上資料很多,在這里我們只需要明確:Nor FLASH 支持片內執行,Nand FLASH不支持片內執行,而我們使用的STM32F4系列芯片內置的1MB的FLASH屬於Nor FLASH,所以便有從FLASH啟動這一說法。
解釋完這三個問題,我們便可以談談IAP了
關於IAP
IAP全稱為 In Application Programing,即“應用內編程”,按照我個人的理解就是:我們通常下載到開發板上的程序其實是兩部分——BOOT+APP, BOOT代碼負責必要的堆棧,中斷向量表等初始化,而APP才是我們真正需要的功能代碼,而STM32已經有.s啟動文件支持,所以我們無需關注BOOT部分,只需完成相應的功能代碼即可,通常無論你代碼內有多少功能,但你下載的文件只有一個,即BOOT+APP,且只能通過特定的方式寫入到FLASH或者SRAM內運行。而應用內編程,則打破了這個規則,原則上只要存儲空間無限大,我們就可以使用IAP可以下載到FLASH內部無限多個應用程序。
下面我簡單的畫一下兩者的運行機理:
原諒用電腦自帶畫圖畫的
應該不難看出,其實所謂的IAP,就是先寫一個APP作為偽BOOT,在系統初始化完成之后,引導系統加載真正的APP程序,使之運行,完成真正的APP程序的功能。這樣我們就可以下載多個APP在不同空間內,只要讓引導程序做出相應的跳轉工作,便可以實現運行對應APP功能。
講到這里,我們先來思考下面四個問題:
偽BOOT完成了哪些工作?
APP程序是如何下載到開發板上去的,對空間有特殊要求嗎?
偽BOOT到APP之間的跳轉如何完成?
APP程序需要具有哪些功能?
下來我們就來分析分析
剛才已經提到過,偽BOOT其實就是我們平時寫的APP程序,為什么我要叫他偽BOOT呢?因為他並不是一個真正的BOOT程序,他也是一個BOOT+APP程序,只不過這個程序在IAP編程中完成的工作很像一個BOOT程序,所以我起了個名字叫做偽BOOT。
我們先來看看一般BOOT程序完成的工作:單片機上電復位之后,程序指針PC指向0x00000000(映射到FLASH的0x08000000或者SRAM的0x20000000處),這里存放的是用戶堆棧棧頂地址,獲取到棧頂指針后,程序指針向后偏移4個字節,指向0x00000004(映射到FLASH的0X80000004或者SRAM的0X20000004),這里存放的是系統中斷復位的指針,然后程序跳轉到Reset_Handler執行SystemInit(系統復位)工作,在系統復位中系統關閉了中斷,初始化系統時鍾等等。然后跳轉到 __main進行堆棧初始化,最后跳轉到.C文件中的main函數中執行相應的功能。【這里只做概述,過兩天有時間了專門寫一篇關於STM32啟動流程分析的文章^-^】。
我們可以看到,系統初始化部分是必須的,關鍵我們要修改的就是最后這一步跳轉,如和讓程序跳轉跳轉到我們編寫的APP代碼中而不是跳到偽BOOT中的APP程序呢?其實這也是可以的,我們只需要將我們編寫的用戶APP程序中啟動代碼刪掉,其余代碼將偽BOOT中的用戶程序代碼覆蓋掉就可以,但這樣好像失去了IAP的意義。IAP本來就是期望用戶“不拆機在線升級”。這樣搞豈不是更復雜了?
所以,更常見的做法是:偽BOOT完成系統初始化后,進入APP程序,此時利用APP實現兩個功能:
檢測是否需要升級某個用戶APP
跳轉到指定APP運行空間運行
我們可以依靠UART,USB,IIC等一切板上通信外設完成第一個功能。如果需要升級或更新某個APP,則通過某種通信方式將我們編譯好的bin文件發送到單片機上,單片機偽BOOT中的程序接收這些數據並根據需要使用FLASH寫操作將這些代碼寫到指定的FLASH空間(這里我們只討論FLASH)。當需要執行某個用戶APP程序時,可以在偽BOOT的主程序中修改程序指針PC,使系統跳轉到指定用戶APP程序所在的FLASH空間,然后運行,就可以完成跳轉到相應的APP程序的功能。
道理很簡單。我們編寫偽BOOT程序使之有通信,FLASH讀寫,地址跳轉功能就可,在啟動啟動后,因為程序還運行在偽BOOT里,我們依靠BOOT程序中的功能,檢測是否需要更新用戶APP,需要更新則進行數據通信,並將數據通過FLASH操作寫入FLASH,然后執行程序跳轉指令,使系統跳轉到對應的APP程序空間,就可以執行對應的程序功能。
這里有幾個問題需要注意一下:
我們並沒有裁減掉用戶APPx中的boot代碼,所以某種程度來說,燒進FLASH中的至少是兩套或兩套以上的【boot+app】程序,只不過我們的偽BOOT是為了完成引導,其余的APP都是用戶功能程序罷了。
從問題1來看,既然每一個程序都是完整的,如果我們的偽BOOT通過串口接收到我們編譯好的完成工程的bin文件,並寫入到FLASH中0x08004000之后,那么0x08004000這個地址里面存放的是什么東西?用戶APPx main函數地址?錯!應該是用戶APPx程序的BOOT代碼的第一行,也就是程序代碼的用戶堆棧棧頂地址,那0x08004004理所當然就是APPx程序的BOOT代碼里的系統中斷復位指針。
從問題1和2我們可以推斷出,由偽BOOT引導后跳轉到用戶APP程序,系統又進行了一次復位操作,而且和偽BOOT是一模一樣的復位操作,但此時有個明顯的問題就是,我們的中斷向量表多套,每套中斷向量表都對應的是自己APP的中斷入口,但是用戶程序執行時遇到中斷,跳轉會跳到哪里去呢?如果我們在用戶APP中不加以修改,中斷向量表的偏移量仍舊是以0x08000000為基地址計算得到的,所以任何一個用戶APP都會跳轉到偽BOOT中的中斷向量,造成不可預料錯誤。但是幸好STM32提供了中斷向量偏移寄存器,我們可以通過修改這個寄存器的值,將每個APP程序的中斷向量對應到自己所在空間的正確地址。完成中斷操作。
FLASH空間是在編譯鏈接后就確定的,所以在你下載程序之前,你的程序地址已經確定,這樣我們就需要在keil的配置文件中更改ROM的地址,使APP程序可以燒寫到一個正確的FLASH空間,即不會破壞其他程序空間,又能被正確引導啟動,一般來講,偽BOOT文件大小越小越好,這樣可以為APP程序節留下很多空間。而我個人建議將偽BOOT文件單獨放在第一扇區,即0x08000000-0x08003FFF【16KB】,因為程序中在進行FLASH寫操作之前可能需要擦除,但往往擦除會擦除掉一整個扇區,如果對內存理解不到位或者程序編寫有誤,也會將偽BOOT擦除掉,使引導系統崩潰。
我們的傳輸的APP程序一定經過keil編譯過的完整的工程形成的二進制——bin文件。因為我們是將文件數據直接發送到開發板上,不經過特殊的解析,直接寫入到FLASH中,所以這就要求我們發送的文件一定是內存中最終存儲的二進制文件,而不是hex和axf文件。hex是十六進制文件,我們打開可以看到一串ASCII碼,對應着地址信息和數據,而axf則包含着調試器調試信息,是通過JLINK或者STLINK解析后下載到開發板上的。至於bin文件的生成,我們可以使用keil自帶的fromelf.exe工具。
而APP程序的功能絲毫沒有限制,和大家平時寫的一模一樣。這樣,就完成了整個IAP編程。
簡單總結一哈
IAP用途很大,比如無線下載,快速升級,遠程系統維護等等,這些都是IAP的具體應用,當然還有更多好玩的等你去發現。其實我學習IAP主要是想玩玩無線下載,因為環境問題整天抱着電腦跑太累了,如果再改一改代碼,也可以實現BOOT-APPx之間的任意跳轉,可以讓下載和引導隨心所欲。不過今天主要還是分享一下IAP編程的思想和一些細節問題,關於具體的編碼過程,后面有時間我會在下一篇文章里分享。
對啦,上面都是自己的理解,如果大家發現其中有什么講的不對的地方,歡迎指正,共同學習~
之前我們分析了IAP的基本工作原理和編程應該注意的細節問題,接着上篇,我們來看看具體的編碼問題。
正文
上篇基本將IAP工作的機理和程序組成以及運行路程分析過了,所以我們只看看關鍵模塊的編碼。
首先分析IAP,關鍵模塊有三部分:通訊,FLASH操作,引導跳轉。
一、通訊
我們先來談談通訊問題。可以將,無論什么通訊,都可以完成代碼的傳輸,USART也好,USB也好,CAN也好,等等,只要是通訊外設,你都可以用來傳輸外設,但是考慮到實用性,也許USART是簡單也是最最常用的。我們以就以USART為例來講通訊。
文件的傳輸一定要穩定,傳輸過程中不可以丟數據,否則傳輸的文件就有問題,導致最后的APP程序存在問題。所以我們使用USART時需要選擇合適波特率,要盡量大保證傳輸速率,但又不能太大導致丟幀。然后開啟中斷在中斷將接收到的數據存放到一個數組里即可,隨后處理即可。編碼相信大家都會,就不在這里復述了。
二、FLASH操作
我們需要將接受到的APP程序寫到合適的FLASH地址,以便后面引導啟動。
首先應該明確STM32F4的FLASH地址為0x08000000-0x080FFFFF,若果越過這個空間寫肯定是有問題的。下面我們看看FLASH寫入的步驟。
1.校驗寫入地址有效性
//寫入地址必須處於FLASH區間並且地址為4的倍數【按字寫入】
if(WriteAddr < FLASH_BASEADDR || WriteAddr%4) //addr error
return ;
1
2
3
4
2.FLASH解鎖,緩沖區除能
//必須要解鎖FLASH並保證數據緩存處於關閉狀態才可以進行FLASH寫入
FLASH_Unlock(); //ready to clear sector
FLASH_DataCacheCmd(DISABLE);
1
2
3
4
3.判斷是否要擦除緩沖區
//當指定地址內數據不為OXFFFFFFFF時,我們需要擦除這個地址內的內容以便我們重新寫入,這里要注意的是我們擦除的時候是以整個扇區為單位擦除的,所在扇區內容都會消失,這就是我們之前為什么建議大家將偽BOOT放在單獨的扇區進行處理的原因
if(FLASH_ReadWord(AddStart) != 0XFFFFFFFF)
{
FlashStatus = FLASH_EraseSector(FLASH_GetFlashSector(AddStart), VoltageRange_3);// vcc = 2.7~3.6v
if(FlashStatus != FLASH_COMPLETE)
break;//error
}
1
2
3
4
5
6
7
8
4.重寫【我們通過串口或其他外設接收到的數據為八位,而FLASH操作要求按字寫入,所以這塊一定要注意數據的轉化】
//調用系統函數重新將數據按字寫入
while(WriteAddr < AddEnd)
{
if(FLASH_ProgramWord(WriteAddr, *pBuffer)!= FLASH_COMPLETE)
break;//error
WriteAddr += 4;
pBuffer++;
}
1
2
3
4
5
6
7
8
9
10
4.失能緩存,FLASH上鎖,完成寫入操作
FLASH_DataCacheCmd(ENABLE); //write flash finsh
FLASH_Lock();
1
2
3
到這里我們的FLASH寫入模塊算是完成了,主要抓住FLASH操作的核心便可以完成FLASH的寫入操作。
三、引導跳轉
引導跳轉的核心步驟有兩步:
1.地址空間有效性校驗
if(((*(vu32*) LoadAddr)&0x2FFE0000) == 0x20000000) //check the addr of StackTop
1
我們來分析一下這句代碼。LoadAddr為我們偽BOOT接收到的代碼數據的第一個字,我們會想一下上篇文章提到的,正常下載一個程序到FLASH后存儲在0x08000000的是一個什么數據?用戶堆棧地址!對!就是用戶堆棧地址。我們的LoadAddr也是接收到用戶程序代碼的第一個字,本來也是存放在0x08000000地址空間內的,只不過現在被我們接收進行處理了,所以我們的LoadAddr就是接收到的程序的用戶地址空間那么這句話很顯然了,取LoadAddr空間內存放的數據,這是什么?這就是用戶地址空間棧頂指針 然后&0x2FFE0000和0x20000000判等。我們先來看看為什么要用棧頂指針和0x2FFE0000。上篇文章我們提到過STM32F4的SRAM空間范圍為0x20000000-0X2001FFFF共128KB,當用棧頂指針&0x2FFE0000后,我們忽略了低十六位,我們只關心棧頂指針高十六位是否在0x2000-0x2002之間,也就是SRAM空間。這就是棧頂指針的校驗。換句話說,讓程序的棧頂指針在SRAM時,我們便可以認為這是有效的棧頂指針。
2.保存復位中斷向量
typedef void(*LoadAddrFunVar)(void);
//...
LoadAddrFunVar StartLoad;
//...
StartLoad = (LoadAddrFunVar)*(vu32*)(LoadAddr+4); //load the addr
1
2
3
4
5
我們知道棧頂指針往后偏移四個字節便是復位中斷向量的位置,這行代碼的意思就是定義一個函數指針,並將這個函數指針指向復位中斷向量,我們通過調用函數的調用便可以實現復位中斷的執行,進而進行程序復位。
3.將用戶堆棧指針指向用戶堆棧棧頂
//set Main Stack value
__asm void MSR_MSP(u32 addr)
{
MSR MSP, r0
BX LR
}
//...
MSR_MSP(*(vu32*)LoadAddr);
1
2
3
4
5
6
7
8
這里調用了ARM的匯編指令,通過MSP指令將剛才校驗過的堆棧棧頂地址賦值給用戶堆棧寄存器MSP,然后返回。這一步很關鍵,相當於對新APP的用戶堆棧重新構建,保證堆棧的正確性。
4.引導跳轉
StartLoad(); //start
1
即調用剛才的函數,使程序跳轉到復位中斷,執行系統復位操作。如此一來,系統便會復位重新啟動,只不過此時不再加載0X08000000處的代碼,而是加載你剛才制定地址的代碼,此刻我們就完成了IAP的引導工作。
那么,我們的APP程序該怎么編寫呢?
其實我們的APP程序和我們平時寫的程序沒有任何區別,唯獨要增加兩個工作:
設置對應的ROM空間
中斷向量表重定向
我們首先需要通過KEIL->Options->Target->IROM1設置我們的APP程序需要寫入的FLASH空間
這里寫圖片描述
一般來講,這個空間可以設置成為我們之前的引導程序所占空間之外其余的任何FLASH空間,但是我們設置的時候需要考慮:
避免燒寫到引導程序所在扇區
地址必須為0X200倍數【76個中斷向量所占空間的擴展到2的整數次冪,否則覆蓋某個APP中斷向量代碼空間】
APP大小
比如圖中我將APP寫入到了扇區1【引導后面的扇區】,Size設置成剩余的FLASH大小。這是沒有問題的。
還有很重要的一步便是中斷向量表重定向,原因在上篇文章里已經講過,這里我們看看具體怎么操作。
STM32提供了中斷向量表的偏移設置寄存器SCB->VTOR,我們可以在程序開始的時候調用它完成重定向,也可以調用函數NVIC_SetVectorTable()完成中斷向量表的重定向。
SCB->VTOR = (FLASH_BASE |0x4000);
//or
NVIC_SetVectorTable(FLASH_BASE,0x4000);
1
2
3
這里的0x4000就是我代碼寫入FLASH地址0x08004000相對於FLASH_BASE[0x08000000]的偏移量。
那么我們的APP程序寫好了,如何傳輸呢?
這里要強調一下我們需要使用keil自帶的fromelf.exe,將編譯連接后生成的axf文件轉化為可以直接寫入FLASH的二進制數據文件——.bin文件。只有將程序轉化為存儲器中直接存儲的二進制文件,才能不經過任何處理寫入到存儲器后運行,而hex和axf文件則不滿足要求。如何使用fromelf.exe呢?我們點擊option->User->After Build/Rebuild,勾選RUN#1,后面輸入
//根據實際情況修改keil安裝目錄和鏈接輸出文件夾位置
keil安裝目錄\KEIL\ARM\ARMCC\bin\fromelf.exe --bin -o ..\OBJ\FileName.bin ..\OBJ\FileName.axf
1
2
這樣我們在build或者rebuild后會執行這段代碼,使用fromelf工具將axf轉化為bin文件。然后通過串口助手將bin文件發送到開發板就ok啦。
這樣,我們整個IAP從boot到APP都基完成啦~
總結一哈
以上就是IAP的完整的學習筆記啦~筆記1主要分享了IAP的工作和引導機理,筆記2主要進行了關鍵代碼解析。寫完這兩篇,個人對STM32的啟動,程序加載,存儲器存儲有了更深的了解,相信大家也對學到了不少知識。以上內容純屬個人見解,如果大家發現有什么不對的地方,歡迎指正哈~
---------------------
作者:灰同學
來源:CSDN
原文:https://blog.csdn.net/zat907943815/article/details/53997215?utm_source=copy
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
