Bootloader+3App
1
简介
IAP
(
In Application Programming
)即在应用编程,
IAP
是用户自己的程序在运行过程中对
User Flash
的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。通常实现
IAP
功能时,即用户程序运行中作自身的更新操作,需要在设计固件程序时编写两个项目代码,第一个项目程序不执行正常的功能操作,而只是通过某种通信方式
(
如
USB
、
USART)
接收程序或数据,执行对第二部分代码的更新;第二个项目代码才是真正的功能代码。这两部分项目代码都同时烧录在
User Flash
中,当芯片上电后,首先是第一个项目代码开始运行,它作如下操作:
1
)检查是否需要对第二部分代码进行更新
2
)如果不需要更新则转到
4
)
3
)执行更新操作
4
)跳转到第二部分代码执行
第一部分代码必须通过其它手段,如
JTAG
或
ISP
烧入;第二部分代码可以使用第一部分代码
IAP
功能烧入,也可以和第一部分代码一起烧入,以后需要程序更新是再通过第一部分
IAP
代码更新。
我们将第一个项目代码称之为Bootloader程序,第二个项目代码称之为APP程序,他们存放在STM32 FLASH的不同地址范围,一般从最低地址区开始存放Bootloader,紧跟其后的就是APP程序(注意,如果FLASH容量足够,是可以设计很多APP程序的,本章我们讨论
3
个APP程序的情况)。这样我们就是要实现4个程序:Bootloader和
3
个APP。
2 Bootloader
实现原理
我们先来看看STM32正常的程序运行流程,如图2.1所示:
图
2.1 STM32
正常运行流程图
STM32
的内部闪存(
FLASH
)地址起始于
0x08000000
,一般情况下,程序文件就从此地址开始写入。此外
STM32
是基于
Cortex-M3
内核的微控制器,其内部通过一张“中断向量表”来响应中断,程序启动后,将首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动,而这张“中断向量表”的起始地址是
0x08000004
,当中断来临,
STM32
的内部硬件机制亦会自动将
PC
指针定位到“中断向量表”处,并根据中断源取出对应的中断向量执行中断服务程序。
在图2.1中,STM32在复位后,先从0X08000004地址取出复位中断向量的地址,并跳转到复位中断服务程序,如图标号①所示;在复位中断服务程序执行完之后,会跳转到我们的main函数,如图标号②所示;而我们的main函数一般都是一个死循环,在main函数执行过程中,如果收到中断请求(发生重中断),此时STM32强制将PC指针指回中断向量表处,如图标号③所示;然后,根据中断源进入相应的中断服务程序,如图标号④所示;在执行完中断服务程序以后,程序再次返回main函数执行,如图标号⑤所示。
当加入IAP程序之后,程序运行流程如图2.2所示:
图
2.2
加入IAP之后程序运行流程图
在图2.2所示流程中,STM32复位后,还是从0X08000004地址取出复位中断向量的地址,并跳转到复位中断服务程序,在运行完复位中断服务程序之后跳转到IAP的main函数,如图标号①所示,此部分同图2.1一样;在执行完IAP以后(即将新的APP代码写入STM32的FLASH,灰底部分。新程序的复位中断向量起始地址为0X08000004+N+M),跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的main函数,如图标号②和③所示,同样main函数为一个死循环,并且注意到此时STM32的FLASH,在不同位置上,共有两个中断向量表。
在main函数执行过程中,如果CPU得到一个中断请求,PC指针仍强制跳转到地址0X08000004中断向量表处,而不是新程序的中断向量表,如图标号④所示;程序再根据我们设置的中断向量表偏移量,跳转到对应中断源新的中断服务程序中,如图标号⑤所示;在执行完中断服务程序后,程序返回main函数继续运行,如图标号⑥所示。
通过以上两个过程的分析,我们知道
IAP
程序必须满足两个要求:
1
) 新程序必须在
IAP
程序之后的某个偏移量为
x
的地址开始;
2
) 必须将新程序的中断向量表相应的移动,移动的偏移量为
x
;
3 APP
实现与配置
本章设计3
个APP的情况,因为就是分配的flash扇区不同,所以就举例其中的一个。
3.1
APP1
程序起始地址设置方法
随便打开一个之前的实例工程,点击Options for TargetàTarget选项卡,如图3.1所示:
图
3.1 FLASH APP1 Target
选项卡设置
默认的条件下,图中
IROM1
的起始地址(
Start
)一般为
0X08000000
,大小(
Size
)为
0X80000
,即从
0X08000000
开始的
512K
空间为我们的程序存储。而图中,我们设置起始地址(
Start
)为
0X08010000
,即偏移量为
0X10000
(
64K
字节),因而,留给
APP
用的
FLASH
空间(
Size
)只有
0X80000-0X10000=0X70000
(
448K
字节)大小了。设置好
Start
和
Szie
,就完成
APP1
程序的起始地址设置。
APP2则为
0X08020000+0X60000
;
App3
则为
0X08030000+0X50000;
其实就是为每个
app
程序分配了
4k
的空间。
3.2
中断向量表的偏移量设置
之前我们讲解过,在系统启动的时候,会首先调用systemInit函数初始化时钟系统,同时systemInit还完成了中断向量表的设置,我们可以打开systemInit函数,看看函数体的结尾处有这样几行代码:
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET;
/* Vector Table Relocation in Internal SRAM. */
#else
CB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;
/* Vector Table Relocation in Internal FLASH. */
#endif
从代码可以理解,VTOR寄存器存放的是中断向量表的起始地址。默认的情况VECT_TAB_SRAM是没有定义,所以执行SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; 对于FLASH APP,我们设置为FLASH_BASE+偏移量0x10000,所以我们可以在FLASH APP的main函数最开头处添加如下代码实现中断向量表的起始地址的重设:
SCB->VTOR = FLASH_BASE | 0x10000;
如果是APP2可以设置为SCB->VTOR = FLASH_BASE | 0x20000;
如果是APP3可以设置为SCB->VTOR = FLASH_BASE | 0x30000;
这样,我们就完成了中断向量表偏移量的设置。
3.3 *bin
文件生成
不过MDK默认生成的文件是.hex文件,并不方便我们用作IAP更新,我们希望生成的文件是.bin文件,这样可以方便进行IAP升级。这里我们通过MDK自带的格式转换工具fromelf.exe,来实现.axf文件到.bin文件的转换。该工具在MDK的安装目录\ARM\BIN40文件夹里面。
本章,我们通过在MDK点击Options for TargetUser选项卡,在Run User Programs After Build/Rebuild 栏,勾选Run#1和DOS16,并写入:D:\Keil3.80a\ARM\BIN40\fromelf.exe –bin -o ..\OBJ\TEST.bin ..\OBJ\TEST.axf,如图3.2所示:
图3.2 *bin文件生成设置
通过这一步设置,我们就可以在MDK编译成功之后,调用fromelf.exe(注意,我的MDK是安装在D:\Keil3.80A文件夹下,如果你是安装在其他目录,请根据你自己的目录修改fromelf.exe的路径),根据当前工程的TEST.axf(如果是其他的名字,请记住修改,这个文件存放在OBJ目录下面,格式为xxx.axf),生成一个TEST.bin的文件。并存放在axf文件相同的目录下,即工程的OBJ文件夹里面。在得到.bin文件之后,我们只需要将这个bin文件传送给单片机,即可执行IAP升级。
3.4
步骤总结
1
) 设置
APP
程序的起始地址和存储空间大小
2
) 设置中断向量表偏移量
3
) 设置编译后运行
fromelf.exe
,生成
.bin
文件
.
4
关键点
1
) IAP
程序必须满足两个要求:
1.
新程序必须在IAP
程序之后的某个偏移量为x的地址开始;
2.
必须将新程序的中断向量表相应的移动,移动的偏移量为x
;
2
)
STM32
是按照半字读写数据到
FLASH
里面,所以串口收发数据时,必须设置一个收发完成标志,只有数据全部接受后方可执行更新。而且接收完成到收发数据之间也必须设置一个延时才行。
3
)必须先更新后执行,程序中相应的设置一个标志位。
Flag
。
重点:一定要为每个
app
分配好偏移地址。
//
保留
0X08000000~0X0800FFFF
的空间为
IAP
使用
#define FLASH_APP1_ADDR 0x08010000 //
第一个应用程序起始地址
(
存放在
FLASH)
#define FLASH_APP2_ADDR 0x08020000 //
第二个应用程序起始地址
(
存放在
FLASH)
#define FLASH_APP3_ADDR 0x08030000 //
第三个应用程序起始地址
(
存放在
FLASH)
附件:
函数一:写入指定起始地址的
FLASH
空间
//appxaddr:
应用程序的起始地址
//appbuf:
应用程序
CODE.
//appsize:
应用程序大小
(
字节
).
void iap_write_appbin(u32 appxaddr,u8 *appbuf,u32 appsize)
{
u16 t;
u16 i=0;
u16 temp;
u32 fwaddr=appxaddr;//
当前写入的地址
u8 *dfu=appbuf;
for(t=0;t<appsize;t+=2)
{
temp=(u16)dfu[1]<<8;
temp+=(u16)dfu[0];
dfu+=2;//
偏移
2
个字节
iapbuf[i++]=temp;
if(i==1024)
{
i=0;
STMFLASH_Write(fwaddr,iapbuf,1024);
fwaddr+=2048;//
偏移
2048 16=2*8.
所以要乘以
2.
}
}
if(i)STMFLASH_Write(fwaddr,iapbuf,i);//
将最后的一些内容字节写进去
.
}
函数二:跳转执行
FLASH
//
跳转到应用程序段
//appxaddr:
用户代码起始地址
.
void iap_load_app(u32 appxaddr)
{
if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000) //
检查栈顶地址是否合法
.
{
jump2app=(iapfun)*(vu32*)(appxaddr+4); //
用户代码区第二个字为程序开始地址
(
复位地址
)
MSR_MSP(*(vu32*)appxaddr); //
初始化
APP
堆栈指针
(
用户代码区的第一个字用于存放栈顶地址
)
jump2app(); //
跳转到
APP.
}
}
函数三:串口中断服务函数
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)//
接收到数据
{
//USART_SendData(USART1, ‘r’);
res=USART_ReceiveData(USART1);
if(USART_RX_CNT<USART_REC_LEN)
{
USART_RX_BUF[USART_RX_CNT]=res;
USART_RX_CNT++;
}
}
全局变量:
u8 USART_RX_BUF[USART_REC_LEN] __attribute__ ((at(0X20001000)));//
串口接收缓冲
,
最大
USART_REC_LEN
个字节
,
起始地址为
0X20001000.
u16 USART_RX_STA=0; //
接收状态标记
u16 USART_RX_CNT=0; //
接收的字节数