STM32F0系列IAP在线升级分享



  • ​ 最近在项目中遇到一个比较有意思的需求,要实现远程给STM32F0控制板升级固件,这样我们就能够快速响应BUG,不至于出现严重问题就返修。

    ​ IAP, In Application Programming,指在应用编程,单片机程序自己可以往程序存储器里写数据或修改程序。简单的来说,就是不需要别人动手,芯片自己来操刀烧代码!
    ​ 觉得这东西很有意思,就贴出来水一水😸

    1.环境

    芯片 STM32F0系列(RAM8KB,Flash64KB)
    上位机 SIM868(联网)
    IDE KEIL5
    SDK HAL(主要因为是用Cube配置环境省事)

    2.原理

    2.1 单片机运行原理

    ​ 正常的程序流程是这样的:
    0_1526815930208_1.png
    ​ 上图描述了单片机是怎么跑起来的,我们烧录代码是把bin文件烧录到flash中的,这里烧录的起始地址是0x08000000,bin文件的构造如上图大概是:起始地址开头49*4个字节存放一张中断向量表,后面接着程序部分。

    ​ 单片机刚上电,内核从0x08000000这个地址开始执行,首先遇到RESET向量,于是它就从这个地址读出RESET函数地址,然后跳转到这个地方去,开始复位,如上图①。在复位中,执行单片机各种环境的初始化,结束后就跳转到main,如②。在main执行中,如果单片机遇到异常(包括各种中断)则单片机根据中断源,跳转到中断向量表的相应地方,如③。然后在中断表中找到中断程序位置并跳转过去,执行完后再回到main,如④⑤。

    2.2 IAP原理

    ​ 根据原理可以知道,整个运行过程中断向量表是核心,指挥程序跳来跳去不跑飞。实现IAP关键就在于怎么放置代码和中断表。

    ​ 考虑到F0的RAM只有8k。而flash有64k,我的APP有十几k,没法在RAM中运行。一开始想了两种实现方式:

    • 1)一个简单BootLoader + 两个APP,每个APP都有独立的UPDATE模块,BootLoader 只负责跳转,这样在APP1中更新APP2....以此往复可以实现固件更新。
    • 2)一个复杂BootLoader + 一个APP + 一块flash缓存区,在Bootloader中更新APP,对于APP完全不知道BOOT的存在。

    ​ 首先要保证的是,以后更新app时,Keep it simple,把app发过去,它就能更新。因为编译器编译出来的bin文件的中断表,偏移量是以keil配置来的。所以,相应的函数程序必须得存放在这个地址上,比如表中复位记录指向0x080000F0,那么在0x080000F0实际地址上就必须要有复位的函数,不然跳转过来看到一堆错的东西那程序就跑飞了。所以方式1要求每次更新的app,在编译时需要切换表的偏移。。。所以理所应当就选方案二啦。(其实还有一个可行的方法,就是在BootLoader中修改app的表偏移,这个我还没试过)

    因为STM32F1和F4系列都是支持中断表重定向的,而F0系列它没有SCB->VECTOR寄存器,不支持中断重定向,用F0实现IAP还要麻烦一点。用到SYSCFG_CFGR1寄存器,如下:

    0_1526815959389_2.png

    ​ MEM_MODE可以改变启动方式,通过配置这个可以选择从RAM或者ROM启动,实际上就是定义了中断入口的偏移:从ROM启动的话偏移0x08000000,从RAM启动的话偏移0x20000000。只能选择这两个位置,相比于用SCB->VECTOR可以设置任意位置就差多了,不过对于我们足够啦,一个地方放BootLoader的表,另一个地方放APP的表。

    ​ 先定义好空间分配:

    起始地址 大小 内容
    0x08000000 0x3c00(15kB) BootLoader
    0x08003C00 0x5c00(23kB) APP
    0x08009800 0x5c00(23kB) APP缓存区
    0x0800F400 0xC00(3kB) 用户FLASH储存区
    0x20000000 0xC8(200B) APP中断表拷贝
    0x200000CC 0x1F34(7.8kB) 程序堆栈空间

    ​ IAP启动流程如下:(红色线表示程序流,绿色表示数据流)

    0_1526816017564_4.png

    ​ BootLoader烧录的起始地址是ROM的开始地址(0x08000000),上电后也是默认从这个地方启动。上电后进入Boot的复位中断,跳转到Boot的主程序(如①),在这里检测升级,如果需要升级则从串口把升级包下载到flash的缓存,经过校验后再把缓存区写到APP中,即使升级中途失败也不影响老的APP。

    ​ 然后跳转到APP的复位中断向量(如②),在表中查到复位函数的位置,再跳转到那里执行复位(如③),复位(一般在里面都只初始化寄存器、RAM)完成后,就跳转到main函数了(如④)。

    ​ 注意,这个时候,还没切换中断入口,这个时候中断来了的话,就又跳回Boot了(因为这个时候中断入口偏移是0x08000000,那里存放这Boot的中断向量表),所以刚才在Boot最后一步跳转到app前,必须先禁止所有中断。

    ​ 在APP的main函数中,第一件事就是要让自己拿到所有控制权(关键是那张表),所以要把中断入口切换到RAM,在此之前还要提前把自己的中断表拷贝到RAM的中断入口处(0x20000000),切换过来后,这大好河山就是APP的了hhhh。

    ​ 一切都步入正轨了:在APP执行过程中,如果来了中断,则会跳转到RAM头上,而那里存放着APP的中断表(如⑤),然后查表再跳转到APP的相应中断函数(如⑥),最后中断函数执行完,会自己跳回APP(如⑦)。

    3. 实现

    ​ 实现起来不难,主要复杂的地方在怎么从串口可靠升级(这里又回到在黑老师课上学的可靠传输了😷 ),下面贴几行比较重要的:

    3.1BootLoader跳转到APP

    
    #define ADDRESS_FLASH_APP1 (ADDRESS_FLASH_BASE + SIZE_IAP) //0x08003C00
    #define SIZE_APP1 0x5C00 //23kB
    
    typedef  void (*iapfun)(void);	
    
    /*
    *如果APP合法,则跳转到APP
    */
    void iap_load_app(void)
    {
    	/*检查栈顶地址是否合法*/
    	if(((*(vu32*)ADDRESS_FLASH_APP1)&0x2FFE0000)==0x20000000)	
    	{ 
    		jump2app=(iapfun)*(vu32*)(ADDRESS_FLASH_APP1+4);
            /*初始化APP堆栈指针*/
    		MSR_MSP(*(vu32*)ADDRESS_FLASH_APP1);
    		/*跳转到APP*/
    		jump2app();									
    	}
    }	
    
    int main(void)
    {
       /*
       .....(boot的主函数,包括串app下载、校验、升级)
       */
      	/*禁止所有中断*/
    	RCC->CIR = 0x00000000U;
    	/*跳转到app*/
        iap_load_app();
        /*一般是不会走到这的,到这了说明跳转失败,重启*/
        HAL_NVIC_SystemReset();
    }
    

    3.2 APP中初始化

    #define ADDRESS_FLASH_APP1 (ADDRESS_FLASH_BASE + SIZE_IAP) //0x08003C00
    #define SIZE_APP1 0x5C00 //23kB
    #define ADDRESS_RAM 0x20000000
    #define VECTOR_NUM 48
    
    void iap_init(void)
    {
        uint32_t i = 0;  
        /* Relocate by software the vector table to the internal SRAM at 0x20000000 ***/          
        for(i = 0; i < VECTOR_NUM; i++)  
        {  
        *((uint32_t*)(ADDRESS_RAM + (i << 2)))=*(__IO uint32_t*)(ADDRESS_FLASH_APP1 + (i<<2));  
        }  
        
        /*使能外设时钟*/
        __HAL_RCC_SYSCFG_CLK_ENABLE();
        /*设置RAM启动*/
        __HAL_SYSCFG_REMAPMEMORY_SRAM();//设置为RAM启动模式
    }
    
    int main(void)
    {
      /*IAP配置*/
      iap_init();
      
      /*
      	至此,APP已经启动了,从这里以后,APP不知道BOOT的存在
       .....
      */
     
      return 0;
    }
    

 

Copyright © 2018 bbs.dian.org.cn All rights reserved.

与 Dian 的连接断开,我们正在尝试重连,请耐心等待