一、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,"[email protected]"); 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
原文地址:https://www.cnblogs.com/linxw-blog/p/12655516.html