STM32F103-串口IAP


一、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 }
View Code

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 }
View Code

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
 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM