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
 

猜你喜欢

转载自www.cnblogs.com/linxw-blog/p/12655516.html