1,固件升级方案综述
单片机的固件升级方式有很多种。1、ICP:简单说就是在单片机开发时使用烧录器升级程序,比如使用J-Link烧录单片机程序。2、ISP:在单片机内部实现了基于通信接口(如串口、I2C、SPI等等)的FLASH引导程序,配合厂家提供的烧录软件工具或自行开发的软件实现程序烧录。3、IAP:是指单片机程序开发好之后在运行过程中由外部用户发起的在线升级,这种升级方式一般由用户自行设计升级方案,方案灵活性和自由度较高,在智能家居、汽车电子、物联网设备中常用的OTA即空中下载技术原理也与之类似。
2,划分FALSH存储区域
在STM32系列单片机中,程序存储在内部FLASH中,按照不同的单片机型号FLASH大小有所不同,有64KB、128KB、512KB等等。以STM32F407VET6系列单片机为例,内置FLASH大小为512KB,存储地址为0x08000000-0x0807FFFF。单片机每次程序复位时从0x08000000的位置开始执行主程序,如果不做IAP则这512KB空间都可以用来存储用户编写的APP程序。
若要实现IAP功能则必须将FLASH空间划分为几个部分,每部分都存储一个可以独立运行的程序文件(可以理解为几个独立的单片机工程):
1、引导程序,每次复位时程序默认执行此程序,在接下来的执行过程中可以跳转到用户编写的程序,因此这部分程序是固化在以0x08000000为起始的区域中。在引导程序中可以对电路系统作出一些自检和初始化检查的工作,因此该程序又称为bootloader或boot程序,需要注意的是在设计bootloader时要提前规定好程序空间的大小,比如规定程序存储区域为0x08000000-0x8007FFF,则bootlader程序存储空间为32KB,编写boot程序时要注意这一点
2、用户需要升级的新程序,这部分包含了用户的业务代码,复杂的运算逻辑和算法实现均在这一部分完成,称为APP程序,该部分程序一般存储在bootloader区域之后的FLASH中。用一个不是特别恰当的例子类比bootloader和APP:bootloader相当于电脑组装时的BIOS,APP则相当于操作系统,电脑开机时首先运行BIOS,完成后跳转运行到操作系统。
3、升级之前的老版APP备份。这部分相当于电脑系统更新前对老系统的备份,一旦在升级过程中发生错误需要还原到备份系统,防止系统升级失败成砖。同样的APP与APP备份将剩余的FLASH平分,以上述booloader为例,APP程序及其备份所占区域为:(512-32)/2=240KB,因此编写的APP程序编译后的占用的FLASH空间不得超过240KB,这一点可以通过查验.map文件确认,对于不同FLASH大小的芯片可以类比以上计算方法确认自己的程序大小上限(在此插入一句,改变编译器的优化等级可以改变最后的程序大小,但是高的优化等级对程序编写规范要求更高,因此优化等级应该在一开始设计APP之前就确定好,中途变更会带来不可预测的问题)。
以STM32F407VET6单片机为例划分后的FALSH存储框图如下所示:
3,BOOTLADER设计
根据上面的描述,bootloader主要有完成以下功能:
1、系统自检
2、实现APP程序跳转
3、升级过程中接收APP文件并存储到对应的FLASH区域
功能1、3对于不同的系统要求不同,自检的内容以及实现文件传输的物理层接口和链路协议不同,不在此过多描述。下面主要给出APP跳转的部分代码:
define APP_ADDR 0x08008000 //应用程序起始地址 typedef void (*pFunction)(void); //重定义pFunction为void(*)(void)函数指针类型void jump(void){ uint32_t APP_ADDR_Buff=0; //缓存APP地址数值 uint32_t APP_ADDR_Value=0; //APP地址的内容 uint32_t Jump_ADDR; //跳转的目标地址 pFunction Jump_APP; //跳转的目标函数指针 APP_ADDR_Buff = APPLICATION_ADDRESS; //用户程序的首地址 APP_ADDR_Value = (*(volatile uint32_t*)APP_ADDR_Buff);//取出首地址里面的值 if (( APP_ADDR_Value & 0x2FFE0000 ) == 0x20000000) //判断APP首地址里面存的栈顶地址值是否合法 { DISABLE_INTERRUPTS(); //关总中断,使用不同的库写法不同,不可直接复制 RCC_DeInit();//将外设RCC寄存器重设为缺省值,使用不同的库写法不同,不可直接复制 Jump_ADDR = *(volatile uint32_t*)(APP_ADDR_Buff + 4);//APP起始地址第二个字为程序开始地址(新程序复位地址) //指针函数指向用户程序地址,也就是PC指针goto到用户程序起始地址 Jump_APP = (pFunction)Jump_ADDR; //取出程序地址给指针函数 __set_MSP(*(volatile uint32_t*)APP_ADDR_Buff); //初始化APP的堆栈指针 Jump_APP(); //执行指针函数,实现程序跳转 } else { ErrorHandle(); //抛出异常 }}int main(void){
SystemInit();//系统时钟初始化 SYSInit(); //系统初始化 delay_ms(200); if(ReadProgramAPPFlag()) //如果需要更新APP { APP_FlashWrite(); //接收APP文件数据,并将APP程序存储到指定位置 if(APP_Check()) //APP文件校验通过,将新的APP程序更新到备份区域 APP_Backup(); else //否则恢复备份区 APP_Restore(); ResetProgramAPPFlag(); //对完成升级的标志复位 } jump(); //正常情况下运行到这一步时APP区域已经正确写入程序文件 while(1); }
其中ReadProgramDoneFlag()是判断程序应该是先接收升级文件再跳转还是直接跳转的标志,在APP中如果有升级需求则对这个标志置位,在bootloader中完成文件接收之后对标志复位,需要注意的是这个标志位不是全局变量也不是局部变量,要保证程序跳转,初始化堆栈之后这个标志的值不受影响,因此该标志变量最佳选择是写在外部EEPROM或内置FLASH中,读写标志的操作其实是对EEPROM或FLASH的读写。
4,编写APP程序
APP程序中实现了用户的业务代码,和由APP跳转回bootloader的逻辑,实际的操作还是对上文中程序存储Flag的读写,这部分逻辑实现的流程图如下图所示:
由于APP程序对应的是另外一个工程文件,因此在工程设置中要将FLASH的偏移地址向下移动,空出bootlader的区域,比如上文中bootloader区域是0x08000000-0x08008000,因此APP工程的FLASH起始地址是0x8008000,偏移量是0x8000,这一点非常重要。