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程序的内存。

STM32F4串口IAP学习笔记

                                         图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后,程序的运行流程就变成了:

STM32F4串口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程序起始位置和大小分配:

STM32F4串口IAP学习笔记

0x08000000是FLASH的起始地址,根据程序的大小分配Size,注意为0x200的整数倍

FLASH APP程序起始位置和大小分配:

STM32F4串口IAP学习笔记

Start:由Bootloader程序的Start+Size确定,0x08000000+8000=0x08008000

Size:由Flash的大小减去Bootloader程序的大小确定,FLASH为1024K,0x100000-0x8000=0xF8000

SRAM APP程序的起始位置和大小分配与上面的流程相似,就不废话了。