此文檔内容摘自: http://www.51hei.com/stm32/4315.html
可參考文檔:https://blog.csdn.net/gin_love/article/details/82015646
--基于STM32F103ZET6的UART通訊實作
一、什麼是IAP,為什麼要IAP
IAP即為In Application Programming(在應用中程式設計),一般情況下,以STM32F10x系列晶片為主要制器的裝置在出廠時就已經使用J-Link仿真器将應用代碼燒錄了,如果在裝置使用過程中需要進行應用代碼的更換、更新等操作的話,則可能需要将裝置傳回原廠并拆解出來再使用J-Link重新燒錄代碼,這就增加了很多不必要的麻煩。站在使用者的角度來說,就是能讓使用者自己來更換裝置裡邊的代碼程式而廠家這邊隻需要提供給使用者一個代碼檔案即可。
而IAP卻能很好的解決掉這個難題,一片STM32晶片的Code(代碼)區内一般隻有一個使用者程式。而IAP方案則是将代碼區劃分為兩部分,兩部分區域各存放一個程式,一個叫bootloader(引導加載程式),另一個較user application(使用者應用程式)。bootloader在出廠時就固定下來了,在需要變更user application時隻需要通過觸發bootloader對userapplication的擦除和重新寫入即可完成使用者應用的更換。如圖1-1所示
圖 1-1 |
在程式執行初始進入bootloader,在bootloader裡面檢測條件是否被觸發(可通過按鍵是否被按下、序列槽是否接收到特定的資料、U盤是否插入等等),如果有則進行對user application進行擦除和重新寫入操作,如果沒有則直接跳轉到user application執行應用;如果有則進行擦除使用者代碼并重新寫入新的使用者代碼。
二、STM32F103ZET6硬體條件
STM32F103ZET6的啟動方式有三種:内置FLASH啟動、内置SRAM啟動、系統存儲器ROM啟動,通過BOOT0和BOOT1引腳的設定可以選擇從哪中方式啟動,這裡選擇内置的FLASH啟動。其FLASH的位址為0x08000000—0x0807ffff,共512KB,這些都能從晶片資料手冊中直接得到。而這裡首要的一個問題是中斷的問題。正常情況下發生中斷的過程為:發生中斷(中斷請求)à到中斷向量表查找中斷函數入口位址à跳轉到中斷函數à執行中斷函數à中斷傳回。也就是說在STM32的内置的Flash中有一個中斷向量表來存放各個中斷服務函數的入口位址,内置Flash的配置設定情況大緻如下圖2-1。
圖2-1 |
在隻有一個程式的情況下,程式執行的走向應該如圖2-2所示(借用網友的原圖)。
圖2-2 |
STM32F10x有一個中斷向量表,這個中斷向量表存放在代碼開始部分的後4個位元組處(即0x08000004),代碼開始的4個位元組存放的是堆棧棧頂的位址,當發生中斷後程式通過查找該表得到相應的中斷服務程式入口位址,然後再跳到相應的中斷服務程式中執行。上電後從0x08000004處取出複位中斷向量的位址,然後跳轉到複位中斷程式的入口(标号①所示),執行結束後跳轉到main函數中(标号②所示)。在執行main函數的過程中發生中斷,則STM32強制将PC指針指回中斷向量表處(标号③所示),從中斷向量表中找到相應的中斷函數入口位址,跳轉到相應的中斷服務函數(标号④所示),執行完中斷函數後再傳回到main函數中來(标号⑤所示)。
若在STM32F103x中使用IAP方案,則内置的Flash配置設定情況大緻如下圖2-3。
圖2-3 |
在内置的Flash裡面添加一個BootLoader程式,BootLoader程式和user application各有一個中斷向量表,假設BootLoader程式占用的空間為N+M位元組,則程式的走向應該如圖2-2所示(借用網友的原圖并做改動,其中虛線部分為原圖步驟④⑤的走向,本人改為指向灰色部分)。
圖2-2 |
上電初始程式依然從0x08000004處取出複位中斷向量位址,執行複位中斷函數後跳轉到IAP的main(标号①所示),在IAP的main函數執行完成後強制跳轉到0x08000004+N+M處(标号②所示),最後跳轉到新的main函數中來(标号③所示),當發生中斷請求後,程式跳轉到新的中斷向量表中取出新的中斷函數入口位址,再跳轉到新的中斷服務函數中執行(标号④⑤所示),執行完中斷函數後再傳回到main函數中來(标号⑥所示)。
對于步驟④⑤,網友認為是:“在main執行的過程中,如果CPU得到一個中斷請求,PC指針仍強制跳轉到位址0x08000004中斷向量表處,而不是新的中斷向量表,如圖示号④所示,程式再根據我們設定的中斷向量表偏移量,跳轉到對應中斷源新的中斷服務程式中,如圖示号⑤所示”。我對此的了解是:“當發生中斷後,程式從0x08000004(舊)處的中斷向量表中得到相應的中斷服務函數入口位址,繼而跳轉到相應的中斷服務程式”。但是舊的中斷向量清單裡邊存放的是IAP程式中斷函數的入口位址,它是如何得到user程式中斷函數的入口位址呢?是以我覺得此種說法是錯誤的。“當發生中斷時PC指針強制會跳轉到0x08000004處”這種說法并沒有錯,隻是忽略了後續的一些知識要點而導緻這個說法出現沖突。
對于步驟④⑤我認為的是,在main函數的執行過程中,如果CPU得到一個中斷請求,PC指針本來應該跳轉到0x08000004處的中斷向量表,由于我們設定了中斷向量表偏移量為N+M,是以PC指針被強制跳轉到0x08000004+N+M處的中斷向量表中得到相應的中斷函數位址(待求證),再跳轉到相應新的中斷服務函數,執行結束後傳回到main函數中來。
三、實作過程
STM32F103ZET6的Flash位址為0x08000000—0x0807ffff共512KB,把這512KB的空間分為兩塊,第一塊大小為32KB存放BootLoader程式,剩餘的空間存放使用者程式(根據實際情況配置設定這兩塊空間的大小,BootLoader程式占用的空間越小越好,則BootLoader位址為0x08000000—0x08007fff,使用者程式位址為0x08008000—0x0807ffff。BootLoader流程圖大緻應該如下:
1、初始化時鐘。
2、初始化中斷向量表位址。
3、初始化按鍵。 (使用按鍵觸發方式,上電時如果按鍵被按下則進行使用者程式更新操作)
4、初始化序列槽。
5、檢測按鍵是否被按下,是則執行步驟6,否則執行步驟10。
6、擦除使用者程式(擦除0x08008000—0x0807ffff位址空間Flash)。
7、從序列槽讀取新的使用者代碼資料,把代碼寫入使用者程式空間。
8、檢測序列槽資料接收完畢?是則執行步驟9,否則跳回步驟7。
9、使用者程式更新完畢,等待重新上電或硬體複位。
10、跳轉到使用者程式(強制将PC指針跳轉到0x08008000+4處)。
到這裡首先要解決的問題就有:
1、如何進行對STM32的Flash進行擦除和寫入操作。
2、中斷向量表偏移如何設定。
3、如何改變代碼存放的位址空間(因為BootLoader要存放在0x08000000處,使用者程式要存放在0x08008000處,而預設的代碼存放的位址空間為0x08000000)。
4、怎麼進行PC指針的強制跳轉,跳轉時需要做些什麼。
5、序列槽接收的使用者代碼資料是什麼樣的代碼資料,是一種什麼樣的檔案。
問題的解決:
1、使用STM32的固件庫函數,隻需調用幾個庫函數即可輕松解決,使用的固件庫為stm32f10x_flash.c檔案,對Flash的操作過程簡要為:Flash解鎖àFlash擦除àFlash寫入àFlash上鎖。(對Flash程式設計的更詳細操作參考STM32F10xxx閃存程式設計手冊)
①解鎖:
FLASH_Unlock(); //解鎖Flash
FLASH_SetLatency(FLASH_Latency_2); //因為系統時鐘為72M是以要設定兩個時鐘周期的延時
②擦除:
for(i=0;i<240;i++)
{
if(FLASH_ErasePage(FLASH_ADDR+i*2048) != FLASH_COMPLETE) //一定要判斷是否擦除成功
return ERROR;
}
說明:FLASH_ErasePage(uint32_t Page_Address)即為Flash擦除操作,按頁擦除,每頁2KB,Page_Address為頁的起始位址,如0x08000000是第一頁起始位址,0x08000800為第二頁起始位址,這裡的操作擦除了0x08008000—0x0807ffff位址空間的Flash。
③寫入:
unsigned char buf[1024]; //假設待寫入的代碼資料
unsigned short temp; //臨時資料
for(i=0;i<512;i++)
{
temp = (buf[2*i+1]<<8) | buf[2*i]; //2個位元組整合為1個半字
if(FLASH_ProgramHalfWord(ADDR,temp) != FLASH_COMPLETE) //判斷是否寫入成功
{
Return ERROR;
}
ADDR +=2; //位址要加2,因為每次寫入的是2個位元組(1個半字)
}
說明:因為STM32的Flash寫入為雙位元組(1個半字)寫入,FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)函數即為對位址為Address寫入1個半字的Data,每次寫入完成後位址要加2。
④上鎖:
FLASH_Lock(); //Flash 上鎖,一個固件庫函數即可實作。
2、關于中斷向量表的偏移設定,對于BootLoader程式隻需設定中斷向量表的指向在0x08000000處,對于使用者程式需要設定中斷向量表的指向在0x08008000處即可。
①在BootLoader程式的中斷向量表指向設定中應有這麼一句:
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0); //設定中斷向量表指向
其中NVIC_VectTab_FLASH是個宏定義,的值為0x08000000。
②在使用者程式的中斷向量表指向設定用應有這麼一句:
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x8000); //設定中斷向量表指向
3、确認代碼存放的位址空間,在IAR和在Keil中的設定是不同的,網上有在Keil中設定的方法,設立介紹在IAR軟體環境下的設定方法。
①在固件庫目錄\STM32F10x_StdPeriph_Lib_V3.5.0\Project\STM32F10x_StdPeriph_Template\EWARM下找到一個stm32f10x_flash.icf檔案,将其複制到工程目錄中來,在打開IAR工程,将配置檔案添加到工程中,如下圖3-2所示
圖3-1 |
②在工程中打開stm32f10x_flash.icf該檔案,修改兩個參數即可改變代碼存放的位址空間,圖下圖3-2所示。
圖3-2 |
4、關于PC指針的強制跳轉,想在BootLoader程式中将PC指針跳轉到使用者代碼處,可選擇下面的操作
typedef void (*pFunction)(void);
pFunction Jump_To_Application;
uint32_t JumpAddress;
#define ApplicationAddress 0x08008000
if (((*(__IO uint32_t*)ApplicationAddress) & 0x2FFE0000 ) == 0x20000000) //--------①
{
JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4); //--------②
Jump_To_Application = (pFunction) JumpAddress; //--------③
__set_MSP(*(__IO uint32_t*) ApplicationAddress); //--------④
Jump_To_Application(); //--------⑤
}
①因為使用者程式開始位置(0x08008000處)的前4個位元組存放的是堆棧的位址,堆棧位址必定是指向RAM空間的,而STM32的RAM空間起始位址為0x20000000,是以要進行判斷。
②程式跳轉位址的确認,前面已經說過0x08008004處的4個位元組存放的是複位函數的入口位址,該句的意思為獲得(ApplicationAddress + 4)位址處的資料,即為獲得新的複位函數入口位址。
③令Jump_To_Application這個函數指針指向複位函數入口位址。
④堆棧的初始化,重新設定棧頂代位址,把棧頂位址設定為使用者代碼指向的棧頂位址。
⑤跳轉到新的複位函數。
5、通過序列槽來接收代碼資料,就是PC機通過序列槽将代碼資料發送到STM32中去。這裡就涉及到兩個問題:
①資料怎麼得來。
②資料傳輸的過程需要遵循的協定,什麼時候開始,什麼時候結束。
解決①:一般我們就将*.hex檔案使用JFlash-ARM打開再通過Jlink仿真器燒錄到STM32晶片中,但是*.hex檔案裡邊包含的資料不純粹是代碼資料還有一些别的東西,而*.bin檔案資料就全部是代碼資料。
在IAR軟體環境中打開一個使用者工程,先設定好中斷向量表偏移和代碼存放的位址空間後(前面已介紹過這兩種方法)。設定工程如下圖3-3所示,确認後重新編譯工程,在工程的\Debug\Exe目錄下會相應生成一個xxx.bin檔案,這就是所需要的代碼檔案。
圖3-3 |
②資料通過序列槽來傳輸檔案常用的協定有XModem、YModem、ZModem這三種協定,在PC端使用這些協定傳輸檔案隻需要PC的超級終端或者終端工具SecureCRT即可,但是在STM32這邊的程式設計會增加一些困難(因為要先去讀懂、解析這些協定,在通過程式設計來實作)。也可選擇自己定義一套簡單的傳輸協定,但同樣會有一些困難(因為要在PC端進行檔案和序列槽程式設計)。總之不管通過什麼辦法都行,隻要能将xxx.bin檔案資料通過序列槽全部發送到STM32并且STM32能夠全部接收到這些資料并寫入Flash即可(我選擇後者,自定義傳輸協定并用VC進行檔案和序列槽程式設計)。
四、結束語
總的來說STM32的IAP方案實作需要在進行使用者程式之前加一段Bootloader程式,BootLoader程式的作用就是:
①什麼都不做,直接跳轉到使用者程式。
②删除原有的使用者程式,讀取*.bin檔案資料并将資料重新寫入新的使用者程式。
對于使用者程式相比普通的程式設計隻需要做三步改動即可
①改變中斷向量表。
②改變代碼存放的位址空間
③修改生成*.bin檔案
使用通過UART的IAP方案并不是很好的選擇,這隻是IAP方案的一個機制,因為能使用PC機通過序列槽更新程式,同樣能通過Jlink燒寫程式,并且自定義的序列槽通訊協定在沒有校CRC校驗的情況下不能及時發現資料傳輸過程發生的錯誤。這裡推薦使用SD卡(或U盤)進行使用者程式更新,将*.bin檔案複制到SD卡(或U盤)中,STM32再通過讀取SD卡(或U盤)的*.bin檔案進行使用者程式更新,這也避免了STM32與PC笨重的通訊,隻需插一個SD卡(或U盤)更顯得人性化一些,但需要去弄懂STM32如何與SD卡(或U盤)的通訊。