平台芯片:STM32F407
IDE软件: Keil 5.25
目录
1. IAP简介
IAP就相当于一个用户自定义的bootloader,这样一来,芯片上就有两个bootloader,。另一个是用户自定义的,用户可以在程序运行的过程中对内部flash部分的区域进行烧写,主要用于产品发布后,固件程序进行更新升级。因此设计固件程序时需要编写两个项目代码:
第一个是bootloader程序,主要通过外设通信(UART、USB、ETH、SD卡等)来接收程序或数据,这段程序通过JLINK或者ISP烧入;
第二个称为APP程序。这段程序根据地址的不同,可以放在SRAM段和FLASH段。若放在FLASH段,一般从最低地址区开始存放BootLoader,接着是APP 程序。若放在SRAM段,则将SRAM分为三部分,第一部分给Bootloader使用,第二部分给APP程序使用,第三部分用作为APP程序的内存。
2. APP程序编写
主要是初始化LED和串口,指示程序运行的作用。
注意:语句SCB->VTOR = FLASH_BASE | 0x10000 可以实现中断向量表的起始地址的重设。0x10000 大小要和APP程序偏移量对应
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
int main(void)
{
SCB->VTOR = FLASH_BASE | 0x10000; //实现中断向量表的起始地址的重设
delay_init(168); //初始化延时函数
uart_init(460800); //初始化串口波特率为 460800
LED_Init(); //初始化LED
while(1)
{
delay_ms(1000);
GPIO_ToggleBits(GPIOA,GPIO_Pin_8);
printf("The APP code is running \r\n");
}
}
我这里APP程序执行的起始地址为)0x801000, 大小预留为 0xF0000
然后生成 .bin文件。
keil配置生成bin文件过程可以参考博客:
3. Boolloader程序编写
参考原子的教程
接收到的串口数据(即APP代码)写入到FLASH中
//appxaddr:应用程序的起始地址
//appbuf:应用程序CODE.
//appsize:应用程序大小(字节).
void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 appsize) //将接收到的数据写入到Flash中
{
u32 t;
u16 i=0;
u32 temp;
u32 fwaddr=appxaddr;//当前写入的地址
u8 *dfu=appbuf;
for(t=0;t<appsize;t+=4)
{
temp=(u32)dfu[3]<<24;
temp|=(u32)dfu[2]<<16;
temp|=(u32)dfu[1]<<8;
temp|=(u32)dfu[0];
dfu+=4;//偏移4个字节
iapbuf[i++]=temp;
if(i==512)
{
i=0;
STMFLASH_Write(fwaddr,iapbuf,512);
fwaddr+=2048;//偏移2048 512*4=2048
}
}
if(i)
STMFLASH_Write(fwaddr,iapbuf,i);//将最后的一些内容字节写进去.
}
IAP跳转至APP程序函数:
//跳转到应用程序段; 实现 IAP -> APP 的跳转
//appxaddr:用户代码起始地址.
void iap_load_app(uint32_t appxaddr)
{
if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000) //检查栈顶地址是否合法.
{
//函数指针
jump2app = (iapfun)*(vu32*)(appxaddr+4); //用户代码区第二个字为程序开始地址(复位地址)
MSR_MSP(*(vu32*)appxaddr); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
jump2app(); //跳转到APP.
}
}
整体程序实现:
思路是:根据串口接收计数变量判断是否接受完整个APP文件数据,接收完之后就可以串口缓存的APP程序写入FLASH,然后跳转至APP执行。
我的示例代码如下:
int main(void)
{
uint16_t oldcount=0; //老的串口接收数据值
uint16_t applenth=0; //接收到的app代码长度
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
delay_init(168); //初始化延时函数
uart_init(460800); //初始化串口波特率为 460800
LED_Init(); //初始化LED
while(1)
{
// delay_ms(500);
// printf("Hello \r\n");
delay_ms(100);
GPIO_ToggleBits(GPIOA,GPIO_Pin_8);
if(USART_RX_CNT) //接收到数据
{
if(oldcount == USART_RX_CNT) //新周期内,没有收到任何数据,认为本次数据接收完成.
{
applenth = USART_RX_CNT;
oldcount = 0;
USART_RX_CNT = 0; //计数清零
printf("用户程序接收完成!\r\n");
printf("代码长度:%dBytes\r\n", applenth);
}
else //数据有变化,继续接收
oldcount = USART_RX_CNT;
}
//有数据接收过来
if(applenth)
{
iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,applenth); //更新FLASH中的APP代码
printf("固件更新完成!\r\n");
oldcount =0;
printf("开始执行FLASH用户代码!!\r\n");
iap_load_app(FLASH_APP1_ADDR);//执行FLASH APP代码
}
}
}
4. 下载实验
打开串口助手,波特率设置和bootloader一致,打开文件(即前面生成的APP的.bin文件),发送文件
发送完成后可以看到,APP程序开始运行了
以上就完成了IAP程序的最简单移植,更可靠的细节需要根据项目要求不断优化。