1.IAP簡介
IAP,即線上應用程式設計,IAP是使用者自己的程式在執行過程中對User Flash的部分割槽域進行燒寫,目的是為了在產品釋出後,可以方便地透過遺留的通訊口對產品中的韌體程式進行更新升級。
通常在實現IAP功能時,即使用者程式執行中作自身的更新操作, 需要在設計韌體程式時編寫
兩個專案程式碼, 第一個專案程式不執行正常的功能操作, 而只是透過某種通訊方式(如 USB、 USART)接收程式或資料, 執行對第二部分程式碼的更新; 第二個專案程式碼才是真正的功能程式碼。
這兩部分專案程式碼都同時燒錄在 User Flash 中, 當晶片上電後, 首先是第一個專案程式碼開始執行, 它作如下操作:
1.檢查是否需要對第二部分程式碼進行更新
2.檢查不需要更新則轉到4
3.執行更新操作
4.跳轉到第二部分程式碼執行
第一部分程式碼必須透過其它手段, 如 JTAG 或 ISP 燒入; 第二部分程式碼可以使用第一部分程式碼 IAP 功能燒入, 也可以和第一部分程式碼一起燒入, 以後需要程式更新時再透過第一部分 IAP 程式碼更新。
第一個專案程式碼稱為bootloader,第二個專案程式碼稱為APP程式。
他們存放在 STM32 FLASH 的不同地址範圍, 一般從最低地址區開始存放 Bootloader, 緊跟其後的就是 APP 程式(注意, 如果 FLASH 容量足夠,是可以設計很多 APP 程式)。
2.STM32正常的程式執行流程
STM32的內部flash的地址起始於0x08000000,STM32內部透過一張"中斷向量表"來響應中斷
程式啟動後:
首先從中斷向量表中取出復位中斷向量執行復位中斷程式完成啟動, 這張“中斷向量表” 的起始地址是 0x08000004, 當中斷來臨, STM32 的內部硬體機制亦會自動將 PC 指標定位到“中斷向量表” 處, 並根據中斷源取出對應的中斷向量執行中斷服務程式。
在上圖中, STM32 在復位後, 先從 0X08000004 地址取出復位中斷向量的地址, 並跳轉到復位中斷服務程式, 如圖示號①所示; 在復位中斷服務程式執行完之後, 會跳轉到們的 main 函式, 如圖示號②所示; 而我們的 main 函式一般都是一個死迴圈, 在 main 函式執行過程中, 如果收到中斷請求(發生重中斷) ,此時 STM32 強制將 PC 指標指回中斷向量表處, 如圖示號③所示; 然後, 根據中斷源進入相應的中斷服務程式, 如圖示號④所示; 在執行完中斷服務程式以後,程式再次返回 main 函式執行, 如圖示號⑤所示。
3.加入IAP後程式的執行流程
在該流程圖中, STM32 復位後, 還是從 0X08000004 地址取出復位中斷向量的地址, 並跳轉到復位中斷服務程式, 在執行完復位中斷服務程式之後跳轉到IAP 的 main 函式, 如圖示號①所示, 此部分同前面 STM32 正常流程圖一樣; 在執行完 IAP 以後(即將新的 APP 程式碼寫入 STM32 的 FLASH, 灰底部分。 新程式的復位中斷向量起始地址為 0X08000004+N+M) , 跳轉至新寫入程式的復位向量表, 取出新程式的復位中斷向量的地址, 並跳轉執行新程式的復位中斷服務程式, 隨後跳轉至新程式的 main 函式, 如圖示號②和③所示, 同樣 main 函式為一個死迴圈, 到此時 STM32 的 FLASH, 在不同位置上, 共有兩個中斷向量表。
在 main 函式執行過程中, 如果 CPU 得到一箇中斷請求, PC 指標仍強制跳轉到地址 0X08000004 中斷向量表處, 而不是新程式的中斷向量表, 如圖示號④所示; 程式再根據我們設定的中斷向量表偏移量, 跳轉到對應中斷源新的中斷服務程式中, 如圖示號⑤所示; 在執行完中斷服務程式後, 程式返回 main 函式繼續執行, 如圖示號⑥所示。
透過以上兩個過程的分析, 我們知道 IAP 程式必須滿足兩個要求:
1.新程式必須在IAP程式之後的某個偏移量為x的地址開始
2.必須將新程式的中斷向量表相應的移動,移動的偏移量為x;
我們有 2 個 APP 程式, 一個為 FLASH 的 APP, 程式在 FLASH 中執行, 另外一個位 SRAM 的 APP, 程式執行在 SRAM 中, 上圖雖然是針對 FLASH APP來說的, 但是在 SRAM 裡面執行的過程和 FLASH 基本一致, 只是需要設定向量表的地址為 SRAM 的地址。
3.1 中斷向量表的偏移量設定方法
系統啟動的時候,會首先呼叫SystemInit函式初始化時鐘系統,同時完成中斷向量表的設定,程式碼如下
/* Configure the Vector Table location add offset address ------------------*/
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
#endif
VTOR暫存器存放的是中斷向量表的起始地址。預設情況下VECT_TAB_SRAM是沒有定義的,所以執行SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;
對於flash app,設定為FLASH_BASE+偏移量0x10000(具體flash app的地址空間配置見下章),所以需要再FLASH APP的main函式最開頭處新增如下程式碼實現中斷向量表的起始地址的重設
當使用SRAM APP的時候,我們設定起始地址為:SRAM_BASE + 0x1000,同樣新增到SRAM APP的main函式開始處,新增如下:
以上就完成了中斷向量表偏移量的設定,如果此處設定和後面程式空間的配置不匹配,就可能會出現跳轉不到正確程式起始地址導致app執行不起來的情況。
4.應用程式空間配置
我的主控晶片是STM32F4ZGT6,有1024KB的Flash和192KB的SRAM。
Flash程式和SRAM程式和普通程式一樣,只是IROM空間和IRAM空間需要特殊配置一下。
此處先介紹一下Keil MDK中IROM1、IROM2、IRAM1和IRAM2表示的意義:
1.IROM1:表示內部只讀儲存器區域1(Internal ROM 1)。這是一個用於存放程式程式碼的區域,通常是Flash記憶體的一部分。在Keil中,IROM1的配置包括起始地址和大小,用於指定程式碼儲存的具體位置和空間大小。
2.IROM2:表示內部只讀儲存器區域2(Internal ROM 2)。這通常用於指定另一個程式碼儲存區域,可能用於存放程式的第二部分或者其他不需要連續儲存的程式碼段。在某些應用中,比如有多個Flash區域的微控制器,IROM2可以用來指定第二個Flash區域的配置。
3.IRAM1:表示內部隨機訪問儲存器區域1(Internal RAM 1)。這是用於存放變數的RAM區域,通常是微控制器內部的SRAM。IRAM1的配置包括起始地址和大小,用於指定變數儲存的具體位置和空間大小。
4.IRAM2:表示內部隨機訪問儲存器區域2(Internal RAM 2)。這可能是微控制器內部的另一個RAM區域,用於存放特定的變數或者資料,以實現更快的訪問速度或者隔離不同功能的記憶體需求。在一些高階的微控制器中,IRAM2可能指的是特定用途的記憶體區域,比如CCM(Core Coupled Memory)。
4.1 IAP-Bootloader空間配置
此處IROM1size設定成64k,明確一下範圍
實際編譯出來的bin為27k,預留出64k是足夠的,還是看具體需求,只要確保APP起始地址在
BootLoader以後,而且偏移量為0x200的倍數即可(M3權威指南P104頁有介紹)
4.2 Flash APP空間配置
IROM1從Flash 64K地址位置起始,預留960k,如果有兩個以上的flash app還可以再行劃分IRAM1預留了128k的記憶體空間(0x20000)
4.3 SRAM_APP空間配置
SRAM app將儲存地址設定了SRAM上,即0x20001000儲存起始,尺寸大小為0x19000
SRAM app的執行範圍緊隨儲存地址後面,從0x2001A000,執行大小為0x6000
5.我的IAP BootLoader程式
5.1 IAP的main函式
主函式功能流程如下:
key0的功能是將RXBUF中的flash app資料複製到預設的flash地址:
iap_write_appbin(FLASH_APP1_ADDR,USART1_RX_BUF,applenth);//更新FLASH程式碼
函式實現原型
//appxaddr:應用程式的起始地址
//appbuf:應用程式CODE.
//appsize:應用程式大小(位元組).
void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 appsize)
{
u32 t;
u16 i=0;
u32 temp;
u32 fwaddr=appxaddr;//當前寫入的地址
u8 *dfu=appbuf;
for(t=0;t<appsize;t+=4)
{
temp=(u32)dfu[3]<<24;
temp|=(u32)dfu[2]<<16;
temp|=(u32)dfu[1]<<8;
temp|=(u32)dfu[0];
dfu+=4;//偏移4個位元組
iapbuf[i++]=temp;
if(i==512)
{
i=0;
STM32_FLASH_Write(fwaddr,iapbuf,512);
fwaddr+=2048;//偏移2048 512*4=2048
}
}
if(i)STM32_FLASH_Write(fwaddr,iapbuf,i);//將最後的一些內容位元組寫進去.
}
key2的功能是執行flash app,就是直接跳轉到預設的flash地址
if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)//判斷是否為0X08XXXXXX.
{
iap_load_app(FLASH_APP1_ADDR);//執行FLASH APP程式碼
}else
{
printf("非FLASH應用程式,無法執行!\r\n");
LCD_ShowString(30,210,200,16,16,"Illegal FLASH APP!");
}
函式實現:
typedef void (*iapfun)(void);
iapfun jump2app;
u32 iapbuf[512]; //512位元組快取
//設定棧頂地址
//addr:棧頂地址
__asm void MSR_MSP(u32 addr)
{
MSR MSP, r0 //set Main Stack value
BX r14
}
//跳轉到應用程式段
//appxaddr:使用者程式碼起始地址.
void iap_load_app(u32 appxaddr)
{
if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000) //檢查棧頂地址是否合法.
{
jump2app=(iapfun)*(vu32*)(appxaddr+4); //使用者程式碼區第二個字為程式開始地址(復位地址)
MSR_MSP(*(vu32*)appxaddr); //初始化APP堆疊指標(使用者程式碼區的第一個字用於存放棧頂地址)
jump2app(); //跳轉到APP.
}
}
key3的功能是執行SRAM app程式碼,直接跳到SRAM的地址上即可:
if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x20000000)//判斷是否為0X20XXXXXX.
{
iap_load_app(0X20001000);//SRAM地址
}else
{
printf("非SRAM應用程式,無法執行!\r\n");
LCD_ShowString(30,210,200,16,16,"Illegal SRAM APP!");
}
5.2 USART驅動
usart採用中斷接受,將接受到的資料存在快取的buf裡
#define USART1_REC_LEN 55*1024 //定義最大接收位元組數 55K
u8 USART1_RX_BUF[USART1_REC_LEN] __attribute__ ((at(0X20001000)));//接收緩衝,最大USART_REC_LEN個位元組,起始地址為0X20001000.
// usart的中斷服務子函式
void USART1_IRQHandler(void) //串列埠1中斷服務程式
{
u8 r;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中斷
{
r =USART_ReceiveData(USART1);//(USART1->DR); //讀取接收到的資料
if(USART1_RX_CNT<USART1_REC_LEN)
{
USART1_RX_BUF[USART1_RX_CNT]=r;
USART1_RX_CNT++;
}
}
}
6.功能測試
使用串列埠工具,開啟編譯好的flash_app的bin檔案,點選傳送檔案就可以編譯好的bin檔案燒錄到板子中。
然後按下key1,key2就可以執行起flash app了。
7.Q&A:
1.我編譯出的bin檔案大小為64628大小,透過usart燒寫flash app後,按下key1_up後,正常應該是顯示
但是實際顯示了Illegal FLASH APP!
猜測原因應該是資料沒接收全,排查IAP程式發現,usart的buf大小設定最大為55k,而我的程式
為64k,所以將這個buf大小改為64k應該就可以了, 實測確實是這個原因
2.當flash app或sram app執行有問題的話,有限排查IROM和IRAM的配置,檢查是否出現記憶體越界或衝突的情況