通过BootLoader实现OTA升级是我们单片机常用的实现方式,其实我们可以将其多元化构建成多个系统,一方面,实现OTA可以保证升级过程出现任何错误时,原系统可恢复,也可以充分利用flash空间,实现系统可恢复操作,另一方面,可以实现多个单独应用程序的运行,以及相互切换操作,实现完全剥离的模块化开发。
BootLoader ,本意就是引导加载程序。它是系统运行之前运行的一段引导程序。通过这段小程序,可以配置硬件设备、为用户选择系统、加载驱动等等操作,其中OTA 就需要通过它来进行引导,就好像我们安装系统时候使用光盘或者U盘启动的道理一样。
三个近似的概念:
OTA ,Over The Air ,空中升级的意思。
UDF ,Device Firmware Update,指设备固件更新。
IAP,In-Application Programming,应用内编程。
如果您遇到这个三个概念,很可能就是是指的同一件事情就是升级,指对自己进行编程更新,名称强调的重点不同而已。包谷换玉米,没什么差异。
基础例程,请参考STM32的串口IAP例程。
STM32的OTA升级,首先要理解存储区域的划分、大小以及读写方式,以F413为例
扇区 | 地址 | 存储空间 |
---|---|---|
扇区0 | ox8000000 - 0x8003FFF | 16KB |
扇区1 | ox8004000 - 0x8007FFF | 16KB |
扇区2 | ox8008000 - 0x800BFFF | 16KB |
扇区3 | ox800C000 - 0x800FFFF | 16KB |
扇区4 | ox8010000 - 0x801FFFF | 64KB |
扇区5 | ox8020000 - 0x803FFFF | 128KB |
扇区6 | ox8040000 - 0x805FFFF | 128KB |
扇区7 | ox8060000 - 0x807FFFF | 128KB |
扇区8 | ox8080000 - 0x809FFFF | 128KB |
扇区9 | ox80A0000 - 0x80BFFFF | 128KB |
扇区10 | ox80C0000 - 0x80DFFFF | 128KB |
扇区11 | ox80E0000 - 0x80FFFFF | 128KB |
了解了存储空间,就要安按照软件项目的需求,以扇区块为单位进行分配。软件工程可以分为一个BootLoader工程+多个APP工程的模式为系统布局。例如系统分区如下:
地址分配 | 备注 |
---|---|
BOOTLOADER_ADDR (0x08000000) | 启动文件起始地址,从扇区0开始 |
OTA_FLAG_ADDR (0x0800C000) | 标志参数起始地址,从扇区3开始 |
APP1_ADDR (0x08020000) | 第一个应用程序起始地址,从扇区5开始 |
APP2_ADDR (0x08060000) | 第二个应用程序起始地址,从扇区7开始 |
BootLoader在0x8000000的位置启动,引导程序文件一般情况是必须烧录进去的,可以根据自己定义的标志去获取状态,来跑入自己的boot流程,或者直接跳转到自己定义的APP程序。这个标志可以是flash上的一个值、一个电平、按键、网络数据等等,只要是系统可以识别到的,都是可以的。
下面是以一个蓝牙OTA的框架的举例实现:
int main(void)
{
//初始化
UserBootloaderInit();
//读flash OTA标志
branch = UserCheckFlashFlag();
if( branch == 1 )
{
//跳转到APP1
UserGoToApp1();
}
else if( branch == 2 )
{
//跳转到APP2
UserGoToApp2();
}
//检查蓝牙状态
CheckBtConnect();
while (1)
{
//定时器//处理状态变换
UserTimer();
//OTA数据下载和更新处理
UserDfuCodeHandle();
}
}
这里的流程是通过APP启动,重启后进入BootLoader流程的 升级流程是流程如下:
->接收OTA命令 (APP)
->写OTA标志(APP)
->复位系统进入BOOTLOADER
->读OTA标志启动OTA模式
->删除OTA标志
->等待接收文件
->接收文件,超时和结束符判断接文件结束
->效验文件、判断APP存储位置
->将位置参数和APP写入flash
->写入flash完成后再效验完整性
->最后后通知用户,复位重启后可以进入新系统
APP工程是用户使用的主功能程序,一般方法都是可以通过在运行中的APP中启动OTA行为。APP工程可以通过AT指令、电平、按键、网络数据控制等方法启动OTA 功能也可以通过跳转实现多个自定义系统的切换,需要注意的是代码存储空间是否够用。
//跳转到APP2首地址
void UserGoToApp2(void)
{
//判断是否为0X08XXXXXX.
if(((*(vu32*)(FLASH_APP2_ADDR+4))&0xFF000000)==0x08000000)
{
iap_load_app(FLASH_APP2_ADDR);//执行跳转
}
else
{
printf("App address error! \n");
}
}
//跳转到应用程序段
//appxaddr:用户代码起始地址.
void iap_load_app(u32 appxaddr)
{
printf("-->Jump addr:0x%x first value:0x%x \n\n\n\n" ,appxaddr,(*(vu32*)appxaddr));
//关闭端口
CloseSystemPort();
//检查栈顶地址是否合法
if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)
{
//用户代码区第二个字为程序开始地址
jump2app=(iapfun)*(vu32*)(appxaddr+4);
//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
MSR_MSP(*(vu32*)appxaddr);
//跳转到APP.
jump2app();
}
}
在APP的存储控制中,应用程序的中断向量配置必须正确,否者程序一遇到任何中断都会跑飞,甚至会然你觉得根本没有进入程序。
首先,STM32的中断向量偏移在设备外设访问层系统源文件system_stm32f4xx.c中修改,如下面实例,通过宏开关去控制不同的偏移,来决定存放中断向量表的首地址位置。
#ifdef __BURN_APP1__
#define VECT_TAB_OFFSET 0x20000 /*!< Vector Table base offset field.
This value must be a multiple of 0x200. */
#else //APP2
#define VECT_TAB_OFFSET 0x60000 /*!< Vector Table base offset field.
This value must be a multiple of 0x200. */
#endif
其次,在Keil中的选项中要对应配置程序的起点位置,这一点千万不要忘记,这个也会影响生成的执行文件,如下图。