一、IAP是什么
IAP即為In Application Programming,解釋為在應用中編程,用戶自己的程序在運行過程中對User Flash的部分區域進行燒寫。即是一種對單片機flash擦寫的一種編程方案。
通常情況下,一片stm32單片機的flash只有一個用戶程序,而IAP編程則是將單片機的flash分成至少兩大區域,一部分叫做bootloader區,一部分叫做app用戶代碼區,還可留出一部分區域為代碼備份區。
二、IAP的應用場所
通常情況下我們給stm32單片機燒錄更新程序時是通過SWD、J-link或者通過設置BOOT引腳后,使用串口進行程序下載,這樣的方式直接一次性將程序文件下載到單片機的flash中,比較適合絕大部分的應用。
但是當產品投入實際應用時,封裝完成后在后期的使用過程中遇到某些程序上的bug或者是根據客戶需求需要增加一些功能的時候,使用傳統代碼燒錄的方法就可能需要拆除封裝,而使用IAP編程在bootloader區提前寫入與外部通信的接口用於升級單片機代碼,使得我們不用對已完成包裝的產品進行拆除既可以更新代碼,這樣既節約了成本,也更加方便快捷。
三、IAP編程的流程
IAP編程將Flash區分成的兩個區域,bootloader區和app用戶代碼區具有截然不同的功能。
bootloader區,主要實現接收程序文件,並將該程序寫於特定位置的Flash區域。而這里接收外部程序文件,就需要實時和外部通信了。Stm32單片機與外部通信大多是通過自身的串口接收和發送數據,不過Stm32單片機的串口可以外接多種通訊接口,例如422、485、GPRS及ESP8266等。即我們可以通過串口外接藍牙模塊、WiFi模塊或者是其他網絡模塊,就可以實現遠程的文件傳送更新單片機程序了。
app用戶代碼區則是主要實現我們所需要的功能操作,除此之外app用戶代碼區還需要實時檢查代碼運行情況,通過判斷更新程序的標志位來判斷是否需要升級程序。若是需要升級程序則進入bootloader區進行代碼更新;若不需要則繼續運行功能函數代碼即可。
因此IAP編程下的單片機運行流程如下圖:
根據運行流程,我們可以總結出簡單幾條bootloader設計過程中需要注意的地方:
1、精簡、程序盡可能精簡。在單片機Flash有限的情況下,bootloader代碼占用Flash的空間越小,則APP程序代碼就可占用更多,實現更多功能函數。
2、標志位不受復位的影響。
3、Bootloader中盡量不使用中斷。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
一、確定需要解決的問題
二、解決問題
1、准備好Bootloder和APP應用兩個程序。
2、對flash進行擦除和重寫
3、設置APP應用程序的中斷向量表偏移
4、改變APP用戶程序的代碼存放地址空間
5、在BootLoader程序中將PC指針跳轉到用戶代碼處,如下操作即可:
6、通過串口接收文件
參考鏈接
本次所采用的編譯環境為Keil,本來是想在IAR環境下開發的,但是還是用不太慣它的調試,所以還是換成了Keil。
本次用到的單片機是Stm32F103C8T6。
在知道了IAP編程的原理之后,需要知道具體實現的過程,這里推薦一篇博文
http://www.51hei.com/stm32/4315.html
博文中博主把IAP方案實現的原理以及所需要注意的問題和解決辦法說得很通透了,這里我就不再贅述了
一、確定需要解決的問題
實現IAP編程需要着手編寫兩個程序,一個是Bootloader程序,一個是APP應用程序。
需要對STM32的Flash進行擦除和寫入操作。
需要根據APP應用程序開始地址設置中斷向量表的偏移
需要改變代碼存放的地址空間(因為BootLoader要存放在0x08000000處,用戶程序要存放在0x08005000處,而默認的代碼存放的地址空間為0x08000000)。
在下載完更新文件之后需要進行PC指針的強制跳轉,跳轉時需要做什么
串口接收的用戶代碼數據是什么樣的代碼數據,是一種什么樣的文件,該如何得到該格式文件
二、解決問題
1、准備好Bootloder和APP應用兩個程序。
原子代碼:

1 #include "led.h" 2 #include "delay.h" 3 #include "key.h" 4 #include "sys.h" 5 #include "lcd.h" 6 #include "usart.h" 7 #include "stmflash.h" 8 #include "iap.h" 9 //ALIENTEK戰艦STM32開發板實驗48 10 //IAP實驗 Bootloader V1.0 代碼 11 //技術支持:www.openedv.com 12 //廣州市星翼電子科技有限公司 13 int main(void) 14 { 15 u8 t; 16 u8 key; 17 u16 oldcount=0; //老的串口接收數據值 18 u16 applenth=0; //接收到的app代碼長度 19 u8 clearflag=0; 20 21 uart_init(256000); //串口初始化為256000 22 delay_init(); //延時初始化 23 LCD_Init(); 24 LED_Init(); //初始化與LED連接的硬件接口 25 26 KEY_Init(); //按鍵初始化 27 28 POINT_COLOR=RED;//設置字體為紅色 29 LCD_ShowString(60,50,200,16,16,"Warship STM32"); 30 LCD_ShowString(60,70,200,16,16,"IAP TEST"); 31 LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK"); 32 LCD_ShowString(60,110,200,16,16,"2012/9/24"); 33 LCD_ShowString(60,130,200,16,16,"WK_UP:Copy APP2FLASH"); 34 LCD_ShowString(60,150,200,16,16,"KEY1:Erase SRAM APP"); 35 LCD_ShowString(60,170,200,16,16,"KEY0:Run SRAM APP"); 36 LCD_ShowString(60,190,200,16,16,"KEY2:Run FLASH APP"); 37 POINT_COLOR=BLUE; 38 //顯示提示信息 39 POINT_COLOR=BLUE;//設置字體為藍色 40 while(1) 41 { 42 if(USART_RX_CNT) 43 { 44 if(oldcount==USART_RX_CNT)//新周期內,沒有收到任何數據,認為本次數據接收完成. 45 { 46 applenth=USART_RX_CNT; 47 oldcount=0; 48 USART_RX_CNT=0; 49 printf("用戶程序接收完成!\r\n"); 50 printf("代碼長度:%dBytes\r\n",applenth); 51 }else oldcount=USART_RX_CNT; 52 } 53 t++; 54 delay_ms(10); 55 if(t==30) 56 { 57 LED0=!LED0; 58 t=0; 59 if(clearflag) 60 { 61 clearflag--; 62 if(clearflag==0)LCD_Fill(60,210,240,210+16,WHITE);//清除顯示 63 } 64 } 65 key=KEY_Scan(0); 66 if(key==KEY_UP) 67 { 68 if(applenth) 69 { 70 printf("開始更新固件...\r\n"); 71 LCD_ShowString(60,210,200,16,16,"Copying APP2FLASH..."); 72 if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000)//判斷是否為0X08XXXXXX. 73 { 74 iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,applenth);//更新FLASH代碼 75 delay_ms(100); 76 LCD_ShowString(60,210,200,16,16,"Copy APP Successed!!"); 77 printf("固件更新完成!\r\n"); 78 }else 79 { 80 LCD_ShowString(60,210,200,16,16,"Illegal FLASH APP! "); 81 printf("非FLASH應用程序!\r\n"); 82 } 83 }else 84 { 85 printf("沒有可以更新的固件!\r\n"); 86 LCD_ShowString(60,210,200,16,16,"No APP!"); 87 } 88 clearflag=7;//標志更新了顯示,並且設置7*300ms后清除顯示 89 } 90 if(key==KEY_DOWN) 91 { 92 if(applenth) 93 { 94 printf("固件清除完成!\r\n"); 95 LCD_ShowString(60,210,200,16,16,"APP Erase Successed!"); 96 applenth=0; 97 }else 98 { 99 printf("沒有可以清除的固件!\r\n"); 100 LCD_ShowString(60,210,200,16,16,"No APP!"); 101 } 102 clearflag=7;//標志更新了顯示,並且設置7*300ms后清除顯示 103 } 104 if(key==KEY_LEFT) 105 { 106 printf("開始執行FLASH用戶代碼!!\r\n"); 107 if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)//判斷是否為0X08XXXXXX. 108 { 109 iap_load_app(FLASH_APP1_ADDR);//執行FLASH APP代碼 110 }else 111 { 112 printf("非FLASH應用程序,無法執行!\r\n"); 113 LCD_ShowString(60,210,200,16,16,"Illegal FLASH APP!"); 114 } 115 clearflag=7;//標志更新了顯示,並且設置7*300ms后清除顯示 116 } 117 if(key==KEY_RIGHT) 118 { 119 printf("開始執行SRAM用戶代碼!!\r\n"); 120 if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x20000000)//判斷是否為0X20XXXXXX. 121 { 122 iap_load_app(0X20001000);//SRAM地址 123 }else 124 { 125 printf("非SRAM應用程序,無法執行!\r\n"); 126 LCD_ShowString(60,210,200,16,16,"Illegal SRAM APP!"); 127 } 128 clearflag=7;//標志更新了顯示,並且設置7*300ms后清除顯示 129 } 130 131 } 132 }
2、對flash進行擦除和重寫
在原子的例程中就包含了對Flash的擦除和重寫,在操作Flash之前一定要記得先釋放Flash的操作權限,即解鎖。通過閱讀原子例程的代碼可以清晰的知道,他實現的對Flash的操作主要是調用了stm32固件庫中的stm32f10x_flash.c中的三個庫函數:
void FLASH_Unlock(void);
FLASH_Status FLASH_ErasePage(uint32_t Page_Address);
FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)
3、設置APP應用程序的中斷向量表偏移
在推薦的博文中把APP應用程序的中斷向量表需要設置偏移的原因已經講的很清楚了,這里就不再贅述了。
首先我將Flash分成了兩個部分,從Flash起始地址0x8000000到0x8005000的20KB大小作為Bootloader區域;從0x8005000以后作為APP應用程序區域。
所以我們這里需要設置一下之前寫好的APP應用程序的中斷向量表。
注意:Bootloader程序的中斷向量表不需要做任何設置,只需要對APP應用程序的STM32工程進行設置。
設置中斷向量表偏移可以直接修改庫文件中對中斷向量表的操作,但是一般情況下我們不要隨意修改庫文件,所以我們只需要在APP應用程序的代碼中的主函數開頭添加一句話即可:
SCB->VTOR = FLASH_BASE | 0x5000;
因為我定義的APP用戶程序開始地址是0x8005000,所以中斷向量表偏移0x5000就可以了。
4、改變APP用戶程序的代碼存放地址空間
在Keil編譯環境下改變代碼存放的地址空間操作如下圖:
在IAR環境下則是修改stm32f10x_flash.icf文件中的參數。一般該文件在工程的目錄文件夾下,不在IAR的工程顯示目錄下。將該文件拖到IAR中修改兩個參數即可,如下圖。
5、在BootLoader程序中將PC指針跳轉到用戶代碼處,如下操作即可:

1 typedef void (*pFunction)(void); 2 pFunction Jump_To_Application; 3 uint32_t JumpAddress; 4 #define ApplicationAddress 0x08005000 5 6 //檢測棧頂的地址,判斷用戶代碼的堆棧地址是否落在0x2000000~0x2001ffff區間 7 if (((*(__IO uint32_t*)ApplicationAddress) & 0x2FFE0000 ) == 0x20000000) 8 { 9 /* 鎖定 Flash */ 10 FLASH_Lock(); 11 12 /* 跳轉至用戶程序 */ //ApplicationAddress + 4 對應的是app中斷向量表的第二項,復位地址 13 JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4); 14 15 Jump_To_Application = (pFunction) JumpAddress; //把地址強轉為函數指針 16 17 //設置主函數棧指針 將用戶代碼的棧頂地址設為棧頂指針 18 __set_MSP(*(__IO uint32_t*) ApplicationAddress); 19 20 //調用函數,實際失去app復位地址去執行復位操作---設置程序指針為復位地址 21 Jump_To_Application(); 22 }
6、通過串口接收文件
這里我們需要通過串口接收更新代碼的文件,然后將文件內容寫入指定的Flash地址中,那么這里接收的數據最好是可以直接寫入Flash中去的。
而我們常用的燒寫代碼的文件有**.hex文件和.bin文件,hex文件中不僅包含了代碼數據,還包含了代碼的位置信息,所以若是我們采用hex文件則需要對接收到的數據進行處理,去掉里面的位置信息,然后再寫於相應的地址空間里,這樣操作就顯得麻煩了許多。而bin文件里的數據全部都是代碼數據,也就是我們可以直接讀取bin文件中的數據然后直接寫入Flash中。
所以,這里我選擇的是使用bin文件,即將APP用戶程序的可執行文件轉換成bin文件之后,再運行bootloader程序,通過按鍵觸發更新操作,然后通過串口工具發送文件給串口,串口接收到該文件之后將其寫入指定的Flash地址中,然后再跳轉到該起始地址開始運行程序,即通過串口實現了流水燈程序的寫入。
如此,現在想要更新該程序,比如想把流水燈效果改為呼吸燈,那么我們只需要先編寫好呼吸燈的程序並生成bin文件,然后通過串口上傳該文件即可更新程序,達到不用通過燒寫器燒寫程序即可改變運行的程序。
Keil環境下直接通過配置即可生成bin文件,首先我們得先找到Keil安裝目錄下自帶的fromelf.exe所在的目錄,然后將生成的.axf**文件轉換為bin文件即可。具體配置如下圖:
F:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o …\OBJ\firstApp.bin …\OBJ\測試.axf