单片机固件在线升级(IAP)

目录

  • 前言
  • 一、IAP简介
  • 二、Bootloader编写
  • 2.1 Flash读写
  • 2.2 IAP程序
  • 2.3 Bootloader程序配置
  • 三、APP程序配置
  • 3.1 APP 程序起始地址设置
  • 3.2 中断向量表的偏移量设置
  • 3.3 设置编译后生成.bin 文件
  • 四、IAP补充
  • 4.1 GD32修改方法
  • 4.2 STM32F0修改方法

  • 前言

    主要介绍IAP,以及怎么实现


    一、IAP简介

      IAP(In Application Programming)即在线应用编程,IAP 是用户自己的程序在运行过程中对Flash 的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。通常实现 IAP 功能时,即用户程序运行中作自身的更新操作,需要在设计固件程序时编写两个项目代码,第一个项目程序不执行正常的功能操作,而只是通过某种通信方式(如 USB、USART)接收程序或数据,执行对第二部分代码的更新;第二个项目代码才是真正的功能代码。
      我们将第一个项目代码称之为 Bootloader 程序,第二个项目代码称之为 APP 程序,他们存放在 STM32F4 FLASH 的不同地址范围,一般从最低地址区开始存放 Bootloader,紧跟其后的就是 APP 程序。第一部分代码必须通过其它手段,如 JTAG 或 ISP 烧入;第二部分代码可以使用第一部分代码 IAP 功能烧入,也可以和第一部分代码一起烧入,以后需要程序更新时再通过第一部分 IAP代码更新。

      这两部分项目代码都同时烧录在 User Flash 中,当芯片上电后,首先是第一个项目代码开始运行,它作如下操作:

    ①检查是否需要对第二部分代码进行更新
    ②如果不需要更新则转到
    ③执行更新操作
    ④跳转到第二部分代码执行

      当加入 IAP 程序之后,程序运行流程如图所示:

      在上图所示流程中,STM32F4 复位后,先是从 0X08000004 地址取出复位中断向量的地址,并跳转到复位中断服务程序,在运行完复位中断服务程序之后跳转到 IAP 的 main 函数,如图标号①所示;在执行完 IAP 以后(即将新的 APP 代码写入 STM32F4的 FLASH,灰底部分。新程序的复位中断向量起始地址为0X08000004+N+M),跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的 main 函数,如图标号②和③所示。
      在 main 函数执行过程中,如果 CPU 得到一个中断请求,PC 指针仍强制跳转到地址0X08000004 中断向量表处,而不是新程序的中断向量表,如图标号④所示;程序再根据我们设置的中断向量表偏移量,跳转到对应中断源新的中断服务程序中,如图标号⑤所示;在执行完中断服务程序后,程序返回 main 函数继续运行,如图标号⑥所示。

    二、Bootloader编写

    2.1 Flash读写

      由于程序最终烧写到内部flash里,所以IAP涉及到flash的读写

    #define STM32_FLASH_SIZE 256 	 		//所选STM32的FLASH容量大小(单位为K)
    #define STM32_FLASH_BASE 0x08000000 	//STM32 FLASH的起始地址
    
    #if STM32_FLASH_SIZE<256
    #define STM_SECTOR_SIZE 1024 //字节
    #else 
    #define STM_SECTOR_SIZE	2048
    #endif	
    
    u16 STMFLASH_BUF[STM_SECTOR_SIZE/2];//最多是2K字节
    
    //从指定地址开始读出指定长度的数据
    //ReadAddr:起始地址
    //pBuffer:数据指针
    //NumToWrite:半字(16位)数
    void STMFLASH_Read(u32 ReadAddr,u16 *pBuffer,u16 NumToRead)   	
    {
    	u16 i;
    	for(i=0;i<NumToRead;i++)
    	{
    		pBuffer[i]=*(vu16*)ReadAddr;//读取2个字节.
    		ReadAddr+=2;//偏移2个字节.	
    	}
    }
    
    //不检查的写入
    //WriteAddr:起始地址
    //pBuffer:数据指针
    //NumToWrite:半字(16位)数   
    void STMFLASH_Write_NoCheck(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)   
    { 			 		 
    	u16 i;
    	for(i=0;i<NumToWrite;i++)
    	{
    		FLASH_ProgramHalfWord(WriteAddr,pBuffer[i]);
    	    WriteAddr+=2;//地址增加2.
    	}  
    } 
    
    //从指定地址开始写入指定长度的数据
    //WriteAddr:起始地址(此地址必须为2的倍数!!)
    //pBuffer:数据指针
    //NumToWrite:半字(16位)数(就是要写入的16位数据的个数.)
    void STMFLASH_Write(u32 WriteAddr,u16 *pBuffer,u16 NumToWrite)	
    {
    	u32 secpos;	   //扇区地址
    	u16 secoff;	   //扇区内偏移地址(16位字计算)
    	u16 secremain; //扇区内剩余地址(16位字计算)	   
     	u16 i;    
    	u32 offaddr;   //去掉0X08000000后的地址
    	if(WriteAddr<STM32_FLASH_BASE||(WriteAddr>=(STM32_FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址
    	FLASH_Unlock();						//解锁
    	offaddr=WriteAddr-STM32_FLASH_BASE;		//实际偏移地址.
    	secpos=offaddr/STM_SECTOR_SIZE;			//扇区地址  0~127 for STM32F103RBT6
    	secoff=(offaddr%STM_SECTOR_SIZE)/2;		//在扇区内的偏移(2个字节为基本单位.)
    	secremain=STM_SECTOR_SIZE/2-secoff;		//扇区剩余空间大小   
    	if(NumToWrite<=secremain)secremain=NumToWrite;//不大于该扇区范围
    	while(1) 
    	{	
    		STMFLASH_Read(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容
    		for(i=0;i<secremain;i++)//校验数据
    		{
    			if(STMFLASH_BUF[secoff+i]!=0XFFFF)break;//需要擦除  	  
    		}
    		if(i<secremain)//需要擦除
    		{
    			FLASH_ErasePage(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE);//擦除这个扇区
    			for(i=0;i<secremain;i++)//复制
    			{
    				STMFLASH_BUF[i+secoff]=pBuffer[i];	  
    			}
    			STMFLASH_Write_NoCheck(secpos*STM_SECTOR_SIZE+STM32_FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区  
    		}else STMFLASH_Write_NoCheck(WriteAddr,pBuffer,secremain);//写已经擦除了的,直接写入扇区剩余区间. 				   
    		if(NumToWrite==secremain)break;//写入结束了
    		else//写入未结束
    		{
    			secpos++;				//扇区地址增1
    			secoff=0;				//偏移位置为0 	 
    		   	pBuffer+=secremain;  	//指针偏移
    			WriteAddr+=secremain;	//写地址偏移	   
    		   	NumToWrite-=secremain;	//字节(16位)数递减
    			if(NumToWrite>(STM_SECTOR_SIZE/2))secremain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完
    			else secremain=NumToWrite;//下一个扇区可以写完了
    		}	 
    	};	
    	FLASH_Lock();//上锁
    }
    

    2.2 IAP程序

    #define FLASH_APP1_ADDR		0x08010000  	//第一个应用程序起始地址(存放在FLASH)
    typedef  void (*iapfun)(void);				//定义一个函数类型的参数.   
    iapfun jump2app; 
    u16 iapbuf[1024];  
     
    //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);//将最后的一些内容字节写进去.  
    }
    
    //跳转到应用程序段
    //appxaddr:用户代码起始地址.
    void iap_load_app(u32 appxaddr)
    {
    	if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)	//检查栈顶地址是否合法.
    	{ 
    		jump2app=(iapfun)*(vu32*)(appxaddr+4);		//用户代码区第二个字为程序开始地址(复位地址)		
    		__set_MSP(*(vu32*)appxaddr);					//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
    		jump2app();									//跳转到APP.
    	}
    }		 
    

      IAP运行和跳转,其中USART_RX_BUF为串口接收APP缓存,applenth为APP长度

    if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.
    {	 
    	iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,applenth);//更新FLASH代码   
    	printf("固件更新完成!\r\n");	
    }
    printf("开始执行FLASH用户代码!!\r\n");
    if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.
    {	 
    	iap_load_app(FLASH_APP1_ADDR);//执行FLASH APP代码
    }
    

    2.3 Bootloader程序配置

      这里Bootloader程序大概38K,最好不要超过0x10000即64K字节,因为程序是从0X08000000开始,而APP程序配置是从0X08010000开始。当然也可以减少Bootloader程序大小,增大APP空间

    三、APP程序配置

      app部分如果实验用,可以就用点灯尝试

    3.1 APP 程序起始地址设置

      点击 Options for Target→Target 选项卡,设置IROM1起始地址(Start)为 0X08010000,留给APP用的FLASH空间(Size)只有 0X100000-0X10000=0XF0000(960K 字节)大小

    3.2 中断向量表的偏移量设置

      在系统启动的时候,会首先调用 SystemInit 函数初始化时钟系统,同时SystemInit 还完成了中断向量表的设置,如下所示

    #ifdef VECT_TAB_SRAM
      SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
    #else
      SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
    #endif 
    

      VTOR寄存器存放的是中断向量表的起始地址,可以在 FLASH APP 的main函数最开头处添加如下代码实现中断向量表的起始地址的重设:

    SCB->VTOR = FLASH_BASE | 0x10000; /* Vector Table Relocation in Internal FLASH. */	 
    

    3.3 设置编译后生成.bin 文件

      点击Options for Target→User选项卡,在After Build/Rebuild 栏,勾选 Run #1,并写入下行脚本,这样就可以使用MDK自带的fromelf.exe 转换工具,将.axf 文件转换成.bin 文件。

    fromelf.exe --bin -o "$L@L.bin" "#L"
    

      最后用串口助手与板子连接,将.bin文件通过串口发送过去

    四、IAP补充

      上面是M4内核的方法,M0要修改

    4.1 GD32修改方法

    1. 重印射基地址
    #define SYSCFG_MemoryRemap_Flash                ((uint8_t)0x00)
    #define SYSCFG_MemoryRemap_SystemMemory         ((uint8_t)0x01)
    #define SYSCFG_MemoryRemap_SRAM                 ((uint8_t)0x03)
    
    #define CPUID_STM32F0xx		0x410cc200
    #define CPUID_STM32E230xx	0x411cd200
    
    #define VECTOR_SIZE 0xB4
    #define DEFAULT_APP_START_ADDR	0x08001800		//默认APP起始地址
    #define DEFAULT_APP_MAX_SIZE		0x2800		//默认APP空间54K
    
    static void GD32E230_VTORRemap()
    {
    	if(SCB->CPUID == CPUID_STM32F0xx)
    		{
    			//注:F0系列单片机没有VOTR寄存器,故此处将中断向量表映射在SRAM处
    			//memcpy((void*)0x20000000, (void*)( FLASH_BASE | 0x02000), VECTOR_SIZE);		
    			//SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_SRAM);
    			memcpy((void*)0x20000000, (void*)(DEFAULT_APP_START_ADDR), VECTOR_SIZE);		
    			SYSCFG->CFGR1 |= ((uint32_t)0x00000003);
    		}
    		else		
    			SCB->VTOR = DEFAULT_APP_START_ADDR;		//GD32E230有SCB->VTOR寄存器
    }
    
    /**
      * @brief  控制程序跳转到指定位置开始执行 。
      * @param  Addr 程序执行地址。
      * @retval 程序跳转状态。
      */
    void JumpToApplication(uint32_t Addr)
    {
        static pFunction Jump_To_Application;
        __IO uint32_t JumpAddress;
        /* Test if user code is programmed starting from address "ApplicationAddress" */
        if (((*(__IO uint32_t *)Addr) & 0x2FFE0000 ) == 0x20000000)		//检查栈顶地址是否合法.
        {
    //        RCC_DeInit(); //关闭外设
    //        DMA_DeInit(DMA1_Channel5);
    //		  DMA_DeInit(DMA1_Channel4);
    //        USART_DeInit(USART1);
    //        TIM_DeInit(TIM3);
            /* 关闭滴答定时器且禁止中断  */
            SysTick->CTRL &= ~ SysTick_CTRL_ENABLE_Msk;
    
            /* Jump to user application */
            JumpAddress = *(__IO uint32_t *) (Addr + 4);									//用户代码区第二个字为程序开始地址(复位地址)
            Jump_To_Application = (pFunction) JumpAddress;
            __set_PRIMASK(1);																						//关闭所有中断
            /* Initialize user application's Stack Pointer */
            __set_MSP(*(__IO uint32_t *)Addr);														//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
            Jump_To_Application();
        }
        else
        {
            Uart.Fault = fu_no_app;
        }
    }
    
    1. 函数调用
    void main(void)
    {
    	GD32E230_VTORRemap();
    	//条件成立执行以下
    	JumpToApplication(DEFAULT_APP_START_ADDR);
    }
    

    4.2 STM32F0修改方法

    1. 创建函数
    #define APPLICATION_ADDRESS     (uint32_t)0x0800A000
    
    #define SYSCFG_MemoryRemap_Flash                ((uint8_t)0x00)
    #define SYSCFG_MemoryRemap_SystemMemory         ((uint8_t)0x01)
    #define SYSCFG_MemoryRemap_SRAM                 ((uint8_t)0x03)
    
    #if   (defined ( __CC_ARM ))
    __IO uint32_t VectorTable[48] __attribute__((at(0x20000000)));
    #elif (defined (__ICCARM__))
    #pragma location = 0x20000000
    __no_init __IO uint32_t VectorTable[48];
    #elif defined   (  __GNUC__  )
    __IO uint32_t VectorTable[48] __attribute__((section(".RAMVectorTable")));
    #elif defined ( __TASKING__ )
    __IO uint32_t VectorTable[48] __at(0x20000000);
    #endif
    
    //IAP函数申明
    typedef  void (*pFunction)(void);
    pFunction Jump_To_Application;
    void APP_Restart();
    
    //IAP地址修改
    static void STM32F0xx_VectorRemap()
    {
        uint32_t i = 0;
        /* Relocate by software the vector table to the internal SRAM at 0x20000000 ***/
        /* Copy the vector table from the Flash (mapped at the base of the application
        load address 0x0800A000) to the base address of the SRAM at 0x20000000. */
        for(i = 0; i < 48; i++)
        {
            VectorTable[i] = *(__IO uint32_t*)(APPLICATION_ADDRESS + (i<<2));
        }
    
        /* Enable the SYSCFG peripheral clock*/
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
        /* Remap SRAM at 0x00000000 */
        SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_SRAM);
    }
    
    //IAP函数
    void APP_Restart()
    {
            uint32_t JumpAddress;
            /* Jump to user application */
            JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);
            Jump_To_Application = (pFunction) JumpAddress;
    
            /* Initialize user application's Stack Pointer */
            __set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
    
            /* Jump to application */
            Jump_To_Application();
    }
    
    1. 主函数调用
    void main(void)
    {
    	STM32F0xx_VectorRemap();
    	/*一系列初始化*/
    	while(1)
    	{
    		if(/*条件成立*/)
    			APP_Restart();
    	}
    }
    

    作者:别问,问就是全会

    物联沃分享整理
    物联沃-IOTWORD物联网 » 单片机固件在线升级(IAP)

    发表回复