IAP在線升級資料。
Bootloader+3App 1 簡介 2 Bootloader實現原理 3 APP實現與配置 3.1 APP1程序起始地址設置方法 3.2 中斷向量表的偏移量設置 3.3 *bin文件生成 3.4 步驟總結 4 關鍵點 附件:
1 簡介IAP(In Application Programming)即在應用編程,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程序的,本章我們討論3個APP程序的情況)。這樣我們就是要實現4個程序:Bootloader和3個APP。
2 Bootloader實現原理我們先來看看STM32正常的程序運行流程,如圖2.1所示: 圖2.1 STM32正常運行流程圖 STM32的內部閃存(FLASH)地址起始于0x08000000,一般情況下,程序文件就從此地址開始寫入。此外STM32是基于Cortex-M3內核的微控制器,其內部通過一張“中斷向量表”來響應中斷,程序啟動后,將首先從“中斷向量表”取出復位中斷向量執行復位中斷程序完成啟動,而這張“中斷向量表”的起始地址是0x08000004,當中斷來臨,STM32的內部硬件機制亦會自動將PC指針定位到“中斷向量表”處,并根據中斷源取出對應的中斷向量執行中斷服務程序。 在圖2.1中,STM32在復位后,先從0X08000004地址取出復位中斷向量的地址,并跳轉到復位中斷服務程序,如圖標號①所示;在復位中斷服務程序執行完之后,會跳轉到我們的main函數,如圖標號②所示;而我們的main函數一般都是一個死循環,在main函數執行過程中,如果收到中斷請求(發生重中斷),此時STM32強制將PC指針指回中斷向量表處,如圖標號③所示;然后,根據中斷源進入相應的中斷服務程序,如圖標號④所示;在執行完中斷服務程序以后,程序再次返回main函數執行,如圖標號⑤所示。 當加入IAP程序之后,程序運行流程如圖2.2所示: 圖2.2 加入IAP之后程序運行流程圖 在圖2.2所示流程中,STM32復位后,還是從0X08000004地址取出復位中斷向量的地址,并跳轉到復位中斷服務程序,在運行完復位中斷服務程序之后跳轉到IAP的main函數,如圖標號①所示,此部分同圖2.1一樣;在執行完IAP以后(即將新的APP代碼寫入STM32的FLASH,灰底部分。新程序的復位中斷向量起始地址為0X08000004+N+M),跳轉至新寫入程序的復位向量表,取出新程序的復位中斷向量的地址,并跳轉執行新程序的復位中斷服務程序,隨后跳轉至新程序的main函數,如圖標號②和③所示,同樣main函數為一個死循環,并且注意到此時STM32的FLASH,在不同位置上,共有兩個中斷向量表。 在main函數執行過程中,如果CPU得到一個中斷請求,PC指針仍強制跳轉到地址0X08000004中斷向量表處,而不是新程序的中斷向量表,如圖標號④所示;程序再根據我們設置的中斷向量表偏移量,跳轉到對應中斷源新的中斷服務程序中,如圖標號⑤所示;在執行完中斷服務程序后,程序返回main函數繼續運行,如圖標號⑥所示。 通過以上兩個過程的分析,我們知道IAP程序必須滿足兩個要求: 1) 新程序必須在IAP程序之后的某個偏移量為x的地址開始; 2) 必須將新程序的中斷向量表相應的移動,移動的偏移量為x;
3 APP實現與配置 本章設計3個APP的情況,因為就是分配的flash扇區不同,所以就舉例其中的一個。 3.1 APP1程序起始地址設置方法 隨便打開一個之前的實例工程,點擊Options for Target?Target選項卡,如圖3.1所示: 圖3.1 FLASH APP1 Target選項卡設置 默認的條件下,圖中IROM1的起始地址(Start)一般為0X08000000,大小(Size)為0X80000,即從0X08000000開始的512K空間為我們的程序存儲。而圖中,我們設置起始地址(Start)為0X08010000,即偏移量為0X10000(64K字節),因而,留給APP用的FLASH空間(Size)只有0X80000-0X10000=0X70000(448K字節)大小了。設置好Start和Szie,就完成APP1程序的起始地址設置。 APP2則為0X08020000+0X60000; App3則為0X08030000+0X50000; 其實就是為每個app程序分配了4k的空間。
3.2 中斷向量表的偏移量設置 之前我們講解過,在系統啟動的時候,會首先調用systemInit函數初始化時鐘系統,同時systemInit還完成了中斷向量表的設置,我們可以打開systemInit函數,看看函數體的結尾處有這樣幾行代碼: #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的main函數最開頭處添加如下代碼實現中斷向量表的起始地址的重設: SCB->VTOR = FLASH_BASE | 0x10000; 如果是APP2可以設置為SCB->VTOR = FLASH_BASE | 0x20000; 如果是APP3可以設置為SCB->VTOR = FLASH_BASE | 0x30000; 這樣,我們就完成了中斷向量表偏移量的設置。 3.3 *bin文件生成不過MDK默認生成的文件是.hex文件,并不方便我們用作IAP更新,我們希望生成的文件是.bin文件,這樣可以方便進行IAP升級。這里我們通過MDK自帶的格式轉換工具fromelf.exe,來實現.axf文件到.bin文件的轉換。該工具在MDK的安裝目錄\ARM\BIN40文件夾里面。 本章,我們通過在MDK點擊Options for Target?User選項卡,在Run User Programs After Build/Rebuild 欄,勾選Run#1和DOS16,并寫入:D:\Keil3.80a\ARM\BIN40\fromelf.exe --bin -o ..\OBJ\TEST.bin ..\OBJ\TEST.axf,如圖3.2所示: 圖3.2 *bin文件生成設置 通過這一步設置,我們就可以在MDK編譯成功之后,調用fromelf.exe(注意,我的MDK是安裝在D:\Keil3.80A文件夾下,如果你是安裝在其他目錄,請根據你自己的目錄修改fromelf.exe的路徑),根據當前工程的TEST.axf(如果是其他的名字,請記住修改,這個文件存放在OBJ目錄下面,格式為xxx.axf),生成一個TEST.bin的文件。并存放在axf文件相同的目錄下,即工程的OBJ文件夾里面。在得到.bin文件之后,我們只需要將這個bin文件傳送給單片機,即可執行IAP升級。 3.4 步驟總結1) 設置APP程序的起始地址和存儲空間大小
2) 設置中斷向量表偏移量
3) 設置編譯后運行fromelf.exe,生成.bin文件.
4 關鍵點1) IAP程序必須滿足兩個要求: 1.新程序必須在IAP程序之后的某個偏移量為x的地址開始; 2.必須將新程序的中斷向量表相應的移動,移動的偏移量為x;
2)STM32是按照半字讀寫數據到FLASH里面,所以串口收發數據時,必須設置一個收發完成標志,只有數據全部接受后方可執行更新。而且接收完成到收發數據之間也必須設置一個延時才行。 3)必須先更新后執行,程序中相應的設置一個標志位。Flag。 重點:一定要為每個app分配好偏移地址。 //保留0X08000000~0X0800FFFF的空間為IAP使用 #define FLASH_APP1_ADDR 0x08010000 //第一個應用程序起始地址(存放在FLASH) #define FLASH_APP2_ADDR 0x08020000 //第二個應用程序起始地址(存放在FLASH) #define FLASH_APP3_ADDR 0x08030000 //第三個應用程序起始地址(存放在FLASH)
附件:函數一:寫入指定起始地址的FLASH空間 //appxaddr:應用程序的起始地址 //appbuf:應用程序CODE. //appsize:應用程序大小(字節). void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 appsize) { u16 t; u16 i=0; u16 temp; u32 fwaddr=appxaddr;//當前寫入的地址 u8 *dfu=appbuf; for(t=0;t<appsize;t+=2) { temp=(u16)dfu[1]<<8; temp+=(u16)dfu[0]; dfu+=2;//偏移2個字節 iapbuf[i++]=temp; if(i==1024) { i=0; STMFLASH_Write(fwaddr,iapbuf,1024); fwaddr+=2048;//偏移2048 16=2*8.所以要乘以2. } } if(i)STMFLASH_Write(fwaddr,iapbuf,i);//將最后的一些內容字節寫進去. } 函數二:跳轉執行FLASH //跳轉到應用程序段 //appxaddr:用戶代碼起始地址. void iap_load_app(u32 appxaddr) { if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000) //檢查棧頂地址是否合法. { jump2app=(iapfun)*(vu32*)(appxaddr+4); //用戶代碼區第二個字為程序開始地址(復位地址) MSR_MSP(*(vu32*)appxaddr); //初始化APP堆棧指針(用戶代碼區的第一個字用于存放棧頂地址) jump2app(); //跳轉到APP. } } 函數三:串口中斷服務函數 if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)//接收到數據 { //USART_SendData(USART1, 'r'); res=USART_ReceiveData(USART1); if(USART_RX_CNT<USART_REC_LEN) { USART_RX_BUF[USART_RX_CNT]=res; USART_RX_CNT++; } }
全局變量: u8 USART_RX_BUF[USART_REC_LEN] __attribute__ ((at(0X20001000)));//串口接收緩沖,最大USART_REC_LEN個字節,起始地址為0X20001000. u16 USART_RX_STA=0; //接收狀態標記 u16 USART_RX_CNT=0; //接收的字節數
|