STM32F4串口IAP学习笔记
一、IAP简介
IAP就相当于一个用户自定义的bootloader,这样一来,芯片上就有两个bootloader,一个是芯片出厂前固化的(关于这个bootloader究竟在哪儿,我暂时还不清楚)。另一个是用户自定义的,用户可以在程序运行的过程中对内部flash部分的区域进行烧写,主要用于产品发布后,固件程序进行更新升级。因此设计固件程序时需要编写两个项目代码:第一个是bootloader程序,主要通过外设通信(UART、USB、ETH、SD卡等)来接收程序或数据,这段程序通过JLINK或者ISP烧入;第二个称为APP程序。这段程序根据地址的不同,可以放在SRAM段和FLASH段。若放在FLASH段,一般从最低地址区开始存放BootLoader,接着是APP 程序。若放在SRAM段,则将SRAM分为三部分,第一部分给Bootloader使用,第二部分给APP程序使用,第三部分用作为APP程序的内存。
图1.1 STM32内存地址映射
二、IAP程序流程
------------------------------------------------------------------------------------------------------------------------
BOOT1 | BOOT0 | 启动方式
X | 0 | 从STM32内置flash启动,JTAG或者SWD固化程序位置
1 | 1 | 从STM32内置SRAM启动,由于SRAM没有程序存储能力,这个模式一般用于程序debug
0 | 1 | 从STM32内置ROM启动,使用串口借助bootloader下载程序至flash,即ISP
------------------------------------------------------------------------------------------------------------------------
通过设置BOOT1和BOOT0的电平就可以设置STM32启动时从哪个位置开始启动,通常默认从FLASH启动。
当没有IAP时,程序从0x0800000处启动,然后进入0x08000004处的复位中断,并跳转到0x08000004+n处的复位中断程序,在复位中断程序执行结束后,才会跳转到main函数进行死循环。在循环时发生中断就会进入对应的中断服务程序执行,结束后再次返回main函数。
加入IAP后,程序的运行流程就变成了:
图2.1 IAP程序流程
程序在第一次复位中断后进入的是IAP程序的main函数,判断程序是否需要更新,若需要更新,则通过串口等通讯接口接收新的APP程序,存放到某个缓冲区内,通过缓冲区内的数据对FLASH或者RAM中的APP程序进行更新,更新结束后跳转到新的APP程序进行执行。若不需要更新,则直接跳转到APP程序进行执行。
三、IAP程序解析
在一个IAP程序中比较重要的三个部分分别是:
1.接收数据部分
以串口接收数据为例:
#define USART_REC_LEN 122800 //定义最大接收字节数 120K字节
#if EN_USART1_RX //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 USART_RX_BUF[USART_REC_LEN]__attribute__((at(0X20001000)));
//接收缓冲,最大USART_REC_LEN个字节.起始地址为0X20001000.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0, 接收到的有效字节数目
u16 USART_RX_STA=0; //接收状态标记
u32 USART_RX_CNT=0; //接收数据计数器
void USART1_IRQHandler(void)
{
u8 res;
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntEnter();
#endif
if(USART1->SR&(1<<5))//接收到数据
{
res=USART1->DR;
if(USART_RX_CNT<USART_REC_LEN)
{
USART_RX_BUF[USART_RX_CNT]=res;
USART_RX_CNT++;
}
}
#if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
OSIntExit();
#endif
}
#endif
这段程序的作用是在SRAM中划分120K的USART_RX_BUF[ ]缓冲区用于存储USART接收到的二进制APP程序文件,USART_RX_CNT是计数作用,用于在将程序写入FLASH时判断是否写完。
2.更新程序部分
//0x20001000是USART_RX_BUF的起始地址,新的APP程序接收到该地址空间,
//起始地址+4是复位中断
if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000)
{
iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,applenth);
//更新flash代码
}
u32 iapbuf[512];//临时缓冲区
//appxaddr:应用程序的起始地址,传入值为FLASH_APP1_ADDR,这是宏定义好的APP在FLASH中的起始地址
//appbuf:应用程序code
//appsize:应用程序大小(字节)
void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 appsize)
{
u32 t;
u16 i=0;
u32 temp;
u32 fwaddr=appxaddr;//当前写入的地址
u8 *dfu = appbuf;
for(t=0;t<appsize;t+=4)
{
//appbuf是u8类型的缓冲区
//iapbuf是u32类型的,所以一次放四个appbuf
temp=(u32)dfu[3]<<24;
temp|=(u32)dfu[2]<<16;
temp|=(u32)dfu[1]<<8;
temp|=(u32)dfu[0];
dfu+=4;
iapbuf[i++]=temp;
if(i==512)//临时缓冲区满
{
i=0;
STMFLASH_Write(fwaddr,iapbuf,512);
fwaddr+=2048;//偏移2048 (512*4)
}
}
//写剩余的数据
if(i)STMFLASH_Write(fwaddr,iapbuf,i);
}
重要代码分析:
if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000)
0X20001000是USART_RX_BUF缓冲区指定的起始地址,即接收到的新的APP程序的起始地址,将0X20001000+4强转成(vu32*)指针并取值跟0xFF000000进行与运算,判断程序的是否在0x08XXXXXX处,即FLASH代码。
3.跳转程序部分
//从新的APP起始地址+4取出复位中断的值判断是不是flash代码
if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)
{
iap_load_app(FLASH_APP1_ADDR);//执行FLASH APP代码
}
typedef void(*iapfun)(void);
void iap_load_app(u32 appxaddr)
{
if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)//判断栈定地址值是否在0x2000 0000 - 0x 2000 2000之间
{
jump2app=(iapfun)*(vu32*)(appxaddr+4);
MSR_MSP(*(vu32*)appxaddr);
//初始化 APP 堆栈指针(用户代码区的第一个字用于存放栈顶地址)
jump2app();
}
}
重要代码分析:
typedef void(*iapfun)(void);
iapfun是我们创建的一个类型别名,可通过iapfun声明一个返回值是void,参数类型是void的函数指针。
jump2app=(iapfun)*(vu32*)(appxaddr+4);
将appxaddr+4的值强转成(vu32*)指针,并从该地址取值,强转成iapfun类型函数指针,就可以通过jump2app()函数直接跳转到APP程序的复位中断地址。
MSR_MSP(*(vu32*)appxaddr);
由图2.1知道,在复位中断向量之前存放的是栈顶地址,因为这是新的APP程序,所以需要重新指定一下栈顶地址(但我觉得不指定也行,只不过栈的大小会变小,未经过验证)
Bootloader程序起始位置和大小分配:
0x08000000是FLASH的起始地址,根据程序的大小分配Size,注意为0x200的整数倍
FLASH APP程序起始位置和大小分配:
Start:由Bootloader程序的Start+Size确定,0x08000000+8000=0x08008000
Size:由Flash的大小减去Bootloader程序的大小确定,FLASH为1024K,0x100000-0x8000=0xF8000