IAP在線升級(jí)資料。
Bootloader+3App 1 簡(jiǎn)介 2 Bootloader實(shí)現(xiàn)原理 3 APP實(shí)現(xiàn)與配置 3.1 APP1程序起始地址設(shè)置方法 3.2 中斷向量表的偏移量設(shè)置 3.3 *bin文件生成 3.4 步驟總結(jié) 4 關(guān)鍵點(diǎn) 附件:
1 簡(jiǎn)介IAP(In Application Programming)即在應(yīng)用編程,IAP是用戶自己的程序在運(yùn)行過程中對(duì)User Flash的部分區(qū)域進(jìn)行燒寫,目的是為了在產(chǎn)品發(fā)布后可以方便地通過預(yù)留的通信口對(duì)產(chǎn)品中的固件程序進(jìn)行更新升級(jí)。通常實(shí)現(xiàn)IAP功能時(shí),即用戶程序運(yùn)行中作自身的更新操作,需要在設(shè)計(jì)固件程序時(shí)編寫兩個(gè)項(xiàng)目代碼,第一個(gè)項(xiàng)目程序不執(zhí)行正常的功能操作,而只是通過某種通信方式(如USB、USART)接收程序或數(shù)據(jù),執(zhí)行對(duì)第二部分代碼的更新;第二個(gè)項(xiàng)目代碼才是真正的功能代碼。這兩部分項(xiàng)目代碼都同時(shí)燒錄在User Flash中,當(dāng)芯片上電后,首先是第一個(gè)項(xiàng)目代碼開始運(yùn)行,它作如下操作: 1)檢查是否需要對(duì)第二部分代碼進(jìn)行更新 2)如果不需要更新則轉(zhuǎn)到4) 3)執(zhí)行更新操作 4)跳轉(zhuǎn)到第二部分代碼執(zhí)行 第一部分代碼必須通過其它手段,如JTAG或ISP燒入;第二部分代碼可以使用第一部分代碼IAP功能燒入,也可以和第一部分代碼一起燒入,以后需要程序更新是再通過第一部分IAP代碼更新。 我們將第一個(gè)項(xiàng)目代碼稱之為Bootloader程序,第二個(gè)項(xiàng)目代碼稱之為APP程序,他們存放在STM32 FLASH的不同地址范圍,一般從最低地址區(qū)開始存放Bootloader,緊跟其后的就是APP程序(注意,如果FLASH容量足夠,是可以設(shè)計(jì)很多APP程序的,本章我們討論3個(gè)APP程序的情況)。這樣我們就是要實(shí)現(xiàn)4個(gè)程序:Bootloader和3個(gè)APP。
2 Bootloader實(shí)現(xiàn)原理我們先來(lái)看看STM32正常的程序運(yùn)行流程,如圖2.1所示: 圖2.1 STM32正常運(yùn)行流程圖 STM32的內(nèi)部閃存(FLASH)地址起始于0x08000000,一般情況下,程序文件就從此地址開始寫入。此外STM32是基于Cortex-M3內(nèi)核的微控制器,其內(nèi)部通過一張“中斷向量表”來(lái)響應(yīng)中斷,程序啟動(dòng)后,將首先從“中斷向量表”取出復(fù)位中斷向量執(zhí)行復(fù)位中斷程序完成啟動(dòng),而這張“中斷向量表”的起始地址是0x08000004,當(dāng)中斷來(lái)臨,STM32的內(nèi)部硬件機(jī)制亦會(huì)自動(dòng)將PC指針定位到“中斷向量表”處,并根據(jù)中斷源取出對(duì)應(yīng)的中斷向量執(zhí)行中斷服務(wù)程序。 在圖2.1中,STM32在復(fù)位后,先從0X08000004地址取出復(fù)位中斷向量的地址,并跳轉(zhuǎn)到復(fù)位中斷服務(wù)程序,如圖標(biāo)號(hào)①所示;在復(fù)位中斷服務(wù)程序執(zhí)行完之后,會(huì)跳轉(zhuǎn)到我們的main函數(shù),如圖標(biāo)號(hào)②所示;而我們的main函數(shù)一般都是一個(gè)死循環(huán),在main函數(shù)執(zhí)行過程中,如果收到中斷請(qǐng)求(發(fā)生重中斷),此時(shí)STM32強(qiáng)制將PC指針指回中斷向量表處,如圖標(biāo)號(hào)③所示;然后,根據(jù)中斷源進(jìn)入相應(yīng)的中斷服務(wù)程序,如圖標(biāo)號(hào)④所示;在執(zhí)行完中斷服務(wù)程序以后,程序再次返回main函數(shù)執(zhí)行,如圖標(biāo)號(hào)⑤所示。 當(dāng)加入IAP程序之后,程序運(yùn)行流程如圖2.2所示: 圖2.2 加入IAP之后程序運(yùn)行流程圖 在圖2.2所示流程中,STM32復(fù)位后,還是從0X08000004地址取出復(fù)位中斷向量的地址,并跳轉(zhuǎn)到復(fù)位中斷服務(wù)程序,在運(yùn)行完復(fù)位中斷服務(wù)程序之后跳轉(zhuǎn)到IAP的main函數(shù),如圖標(biāo)號(hào)①所示,此部分同圖2.1一樣;在執(zhí)行完IAP以后(即將新的APP代碼寫入STM32的FLASH,灰底部分。新程序的復(fù)位中斷向量起始地址為0X08000004+N+M),跳轉(zhuǎn)至新寫入程序的復(fù)位向量表,取出新程序的復(fù)位中斷向量的地址,并跳轉(zhuǎn)執(zhí)行新程序的復(fù)位中斷服務(wù)程序,隨后跳轉(zhuǎn)至新程序的main函數(shù),如圖標(biāo)號(hào)②和③所示,同樣main函數(shù)為一個(gè)死循環(huán),并且注意到此時(shí)STM32的FLASH,在不同位置上,共有兩個(gè)中斷向量表。 在main函數(shù)執(zhí)行過程中,如果CPU得到一個(gè)中斷請(qǐng)求,PC指針仍強(qiáng)制跳轉(zhuǎn)到地址0X08000004中斷向量表處,而不是新程序的中斷向量表,如圖標(biāo)號(hào)④所示;程序再根據(jù)我們?cè)O(shè)置的中斷向量表偏移量,跳轉(zhuǎn)到對(duì)應(yīng)中斷源新的中斷服務(wù)程序中,如圖標(biāo)號(hào)⑤所示;在執(zhí)行完中斷服務(wù)程序后,程序返回main函數(shù)繼續(xù)運(yùn)行,如圖標(biāo)號(hào)⑥所示。 通過以上兩個(gè)過程的分析,我們知道IAP程序必須滿足兩個(gè)要求: 1) 新程序必須在IAP程序之后的某個(gè)偏移量為x的地址開始; 2) 必須將新程序的中斷向量表相應(yīng)的移動(dòng),移動(dòng)的偏移量為x;
3 APP實(shí)現(xiàn)與配置 本章設(shè)計(jì)3個(gè)APP的情況,因?yàn)榫褪欠峙涞膄lash扇區(qū)不同,所以就舉例其中的一個(gè)。 3.1 APP1程序起始地址設(shè)置方法 隨便打開一個(gè)之前的實(shí)例工程,點(diǎn)擊Options for Target?Target選項(xiàng)卡,如圖3.1所示: 圖3.1 FLASH APP1 Target選項(xiàng)卡設(shè)置 默認(rèn)的條件下,圖中IROM1的起始地址(Start)一般為0X08000000,大小(Size)為0X80000,即從0X08000000開始的512K空間為我們的程序存儲(chǔ)。而圖中,我們?cè)O(shè)置起始地址(Start)為0X08010000,即偏移量為0X10000(64K字節(jié)),因而,留給APP用的FLASH空間(Size)只有0X80000-0X10000=0X70000(448K字節(jié))大小了。設(shè)置好Start和Szie,就完成APP1程序的起始地址設(shè)置。 APP2則為0X08020000+0X60000; App3則為0X08030000+0X50000; 其實(shí)就是為每個(gè)app程序分配了4k的空間。
3.2 中斷向量表的偏移量設(shè)置 之前我們講解過,在系統(tǒng)啟動(dòng)的時(shí)候,會(huì)首先調(diào)用systemInit函數(shù)初始化時(shí)鐘系統(tǒng),同時(shí)systemInit還完成了中斷向量表的設(shè)置,我們可以打開systemInit函數(shù),看看函數(shù)體的結(jié)尾處有這樣幾行代碼: #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寄存器存放的是中斷向量表的起始地址。默認(rèn)的情況VECT_TAB_SRAM是沒有定義,所以執(zhí)行SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; 對(duì)于FLASH APP,我們?cè)O(shè)置為FLASH_BASE+偏移量0x10000,所以我們可以在FLASH APP的main函數(shù)最開頭處添加如下代碼實(shí)現(xiàn)中斷向量表的起始地址的重設(shè): SCB->VTOR = FLASH_BASE | 0x10000; 如果是APP2可以設(shè)置為SCB->VTOR = FLASH_BASE | 0x20000; 如果是APP3可以設(shè)置為SCB->VTOR = FLASH_BASE | 0x30000; 這樣,我們就完成了中斷向量表偏移量的設(shè)置。 3.3 *bin文件生成不過MDK默認(rèn)生成的文件是.hex文件,并不方便我們用作IAP更新,我們希望生成的文件是.bin文件,這樣可以方便進(jìn)行IAP升級(jí)。這里我們通過MDK自帶的格式轉(zhuǎn)換工具fromelf.exe,來(lái)實(shí)現(xiàn).axf文件到.bin文件的轉(zhuǎn)換。該工具在MDK的安裝目錄\ARM\BIN40文件夾里面。 本章,我們通過在MDK點(diǎn)擊Options for Target?User選項(xiàng)卡,在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文件生成設(shè)置 通過這一步設(shè)置,我們就可以在MDK編譯成功之后,調(diào)用fromelf.exe(注意,我的MDK是安裝在D:\Keil3.80A文件夾下,如果你是安裝在其他目錄,請(qǐng)根據(jù)你自己的目錄修改fromelf.exe的路徑),根據(jù)當(dāng)前工程的TEST.axf(如果是其他的名字,請(qǐng)記住修改,這個(gè)文件存放在OBJ目錄下面,格式為xxx.axf),生成一個(gè)TEST.bin的文件。并存放在axf文件相同的目錄下,即工程的OBJ文件夾里面。在得到.bin文件之后,我們只需要將這個(gè)bin文件傳送給單片機(jī),即可執(zhí)行IAP升級(jí)。 3.4 步驟總結(jié)1) 設(shè)置APP程序的起始地址和存儲(chǔ)空間大小
2) 設(shè)置中斷向量表偏移量
3) 設(shè)置編譯后運(yùn)行fromelf.exe,生成.bin文件.
4 關(guān)鍵點(diǎn)1) IAP程序必須滿足兩個(gè)要求: 1.新程序必須在IAP程序之后的某個(gè)偏移量為x的地址開始; 2.必須將新程序的中斷向量表相應(yīng)的移動(dòng),移動(dòng)的偏移量為x;
2)STM32是按照半字讀寫數(shù)據(jù)到FLASH里面,所以串口收發(fā)數(shù)據(jù)時(shí),必須設(shè)置一個(gè)收發(fā)完成標(biāo)志,只有數(shù)據(jù)全部接受后方可執(zhí)行更新。而且接收完成到收發(fā)數(shù)據(jù)之間也必須設(shè)置一個(gè)延時(shí)才行。 3)必須先更新后執(zhí)行,程序中相應(yīng)的設(shè)置一個(gè)標(biāo)志位。Flag。 重點(diǎn):一定要為每個(gè)app分配好偏移地址。 //保留0X08000000~0X0800FFFF的空間為IAP使用 #define FLASH_APP1_ADDR 0x08010000 //第一個(gè)應(yīng)用程序起始地址(存放在FLASH) #define FLASH_APP2_ADDR 0x08020000 //第二個(gè)應(yīng)用程序起始地址(存放在FLASH) #define FLASH_APP3_ADDR 0x08030000 //第三個(gè)應(yīng)用程序起始地址(存放在FLASH)
附件:函數(shù)一:寫入指定起始地址的FLASH空間 //appxaddr:應(yīng)用程序的起始地址 //appbuf:應(yīng)用程序CODE. //appsize:應(yīng)用程序大小(字節(jié)). void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 appsize) { u16 t; u16 i=0; u16 temp; u32 fwaddr=appxaddr;//當(dāng)前寫入的地址 u8 *dfu=appbuf; for(t=0;t<appsize;t+=2) { temp=(u16)dfu[1]<<8; temp+=(u16)dfu[0]; dfu+=2;//偏移2個(gè)字節(jié) 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);//將最后的一些內(nèi)容字節(jié)寫進(jìn)去. } 函數(shù)二:跳轉(zhuǎn)執(zhí)行FLASH //跳轉(zhuǎn)到應(yīng)用程序段 //appxaddr:用戶代碼起始地址. void iap_load_app(u32 appxaddr) { if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000) //檢查棧頂?shù)刂肥欠窈戏? { jump2app=(iapfun)*(vu32*)(appxaddr+4); //用戶代碼區(qū)第二個(gè)字為程序開始地址(復(fù)位地址) MSR_MSP(*(vu32*)appxaddr); //初始化APP堆棧指針(用戶代碼區(qū)的第一個(gè)字用于存放棧頂?shù)刂? jump2app(); //跳轉(zhuǎn)到APP. } } 函數(shù)三:串口中斷服務(wù)函數(shù) if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)//接收到數(shù)據(jù) { //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個(gè)字節(jié),起始地址為0X20001000. u16 USART_RX_STA=0; //接收狀態(tài)標(biāo)記 u16 USART_RX_CNT=0; //接收的字節(jié)數(shù)
|