什么是IAP升级? IAP,即In Application Programming,IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写。简单来说,就是开发者代码出bug了或者添加新功能了,能够利用预留的通讯接口,对代码进行升级
UART、SPI、IIC、USB等等,当然还有wifi、4G、蓝牙等无线通讯手段,都可以作为IAP升级的方式,今天主要介绍如何使用串口对固件进行升级
STM32的代码启动过程要想设计IAP,首先需要对MCU的代码启动过程有个了解,先来看看STM32的代码启动过程是怎样的吧
在《Cortex-M3权威指南》有讲述:芯片复位后首先会从向量表里面取出两个值(下图来自Cortex-M3权威指南):
启动文件源代码分析- ;******************** (C) COPYRIGHT 2011 STMicroelectronics ********************
- ;* File Name : startup_stm32f10x_hd.s
- ;* Author : MCD Application Team
- ;* Version : V3.5.0
- ;* Date : 11-March-2011
- ;* Description : STM32F10x High Density Devices vector table for MDK-ARM
- ;* toolchain.
- ;* This module performs:
- ;* (上电复位后会做下面的几件事情)
- ;* - Set the initial SP(设置堆栈,就是设置MSP的值)
- ;* - Set the initial PC == Reset_Handler(设置PC的值)
- ;* - Set the vector table entries with the exceptions ISR address(设置中断向量表的地址)
- ;* - Configure the clock system and also configure the external (设置系统时钟;如果芯片外部由挂载SRAM,还需要配置SRAM,默认是没有挂外部SRAM的)
- ;* SRAM mounted on STM3210E-EVAL board to be used as data
- ;* memory (optional, to be enabled by user)
- ;* - Branches to __main in the C library (which eventually (调用C库的__main函数,然后调用main函数执行用户的)
- ;* calls main()).
- ;* After Reset the CortexM3 processor is in Thread mode,
- ;* priority is Privileged, and the Stack is set to Main.
- ;* <<< Use Configuration Wizard in Context Menu >>>
- ;*******************************************************************************
- ; ------------------分配栈空间----------------
- Stack_Size EQU 0x00000400 ;EQU指令是定义一个标号;标号名是Stack_Size; 值是0x00000400(有点类似于C语言的#define)。Stack_Size标号用来定义栈的大小
- AREA STACK, NOINIT, READWRITE, ALIGN=3 ;AREA指令是定义一个段;这里定义一个 段名是STACK,不初始化,数据可读可写,2^3=8字节对齐的段(详细的说明可以查看指导手册)
- Stack_Mem SPACE Stack_Size ;SPACE汇编指令用来分配一块内存;这里开辟内存的大小是Stack_Size;这里是1K,用户也可以自己修改
- __initial_sp ;在内存块后面声明一个标号__initial_sp,这个标号就是栈顶的地址;在向量表里面会使用到
-
- ; <h> Heap Configuration
- ; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
- ; </h>
- ; ------------------分配堆空间----------------
- ;和分配栈空间一样不过大小只是512字节
- Heap_Size EQU 0x00000200
- AREA HEAP, NOINIT, READWRITE, ALIGN=3
- __heap_base ;__heap_base堆的起始地址
- Heap_Mem SPACE Heap_Size ;分配一个空间作为堆空间,如果函数里面有调用malloc等这系列的函数,都是从这里分配空间的
- __heap_limit ;__heap_base堆的结束地址
- PRESERVE8 ;PRESERVE8 指令作用是将堆栈按8字节对齐
- THUMB;THUMB作用是后面的指令使用Thumb指令集
- ; ------------------设置中断向量表----------------
- ; Vector Table Mapped to Address 0 at Reset
- AREA RESET, DATA, READONLY ;定义一个段,段名是RESET的只读数据段
- ;EXPORT声明一个标号可被外部的文件使用,使标号具有全局属性
- EXPORT __Vectors ;声明一个__Vectors标号允许其他文件引用
- EXPORT __Vectors_End ;声明一个__Vectors_End标号允许其他文件引用
- EXPORT __Vectors_Size ;声明一个__Vectors_Size标号允许其他文件引用
-
- ;DCD 指令是分配一个或者多个以字为单位的内存,并且按四字节对齐,并且要求初始化
- ;__Vectors 标号是 0x0000 0000 地址的入口,也是向量表的起始地址
- __Vectors DCD __initial_sp ;* Top of Stack 定义栈顶地址;单片机复位后会从这里取出值给MSP寄存器,
- ;* 也就是从0x0000 0000 地址取出第一个值给MSP寄存器 (MSP = __initial_sp)
- ;* __initial_sp的值是链接后,由链接器生成
- DCD Reset_Handler ;* Reset Handler 定义程序入口的值;单片机复位后会从这里取出值给PC寄存器,
- ;* 也就是从0x0000 0004 地址取出第一个值给PC程序计数器(pc = Reset_Handler)
- ;* Reset_Handler是一个函数,在下面定义
- ;后面的定义是中断向量表的入口地址了这里就不多介绍了,想要了解的可以参考《STM32中文手册》和《Cortex-M3权威指南》
- DCD NMI_Handler ; NMI Handler
- DCD HardFault_Handler ; Hard Fault Handler
- DCD MemManage_Handler ; MPU Fault Handler
- DCD BusFault_Handler ; Bus Fault Handler
- DCD UsageFault_Handler ; Usage Fault Handler
- .....由于文件太长这里省略了部分向量表的定义,完整的可以查看工程里的启动文件
- DCD DMA2_Channel1_IRQHandler ; DMA2 Channel1
- DCD DMA2_Channel2_IRQHandler ; DMA2 Channel2
- DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3
- DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
- __Vectors_End ;__Vectors_End向量表的结束地址
- __Vectors_Size EQU __Vectors_End - __Vectors ;定义__Vectors_Size标号,值是向量表的大小
- AREA |.text|, CODE, READONLY ;定义一个代码段,段名是|.text|,属性是只读
- ;PROC指令是定义一个函数,通常和ENDP成对出现(标记程序的结束)
- ; Reset handler
- Reset_Handler PROC ;定义 Reset_Handler函数;复位后赋给PC寄存器的值就是Reset_Handler函数的入口地址值。也是系统上电后第一个执行的程序
- EXPORT Reset_Handler [WEAK] ;*[WEAK]指令是将函数定义为弱定义。所谓的弱定义就是如果其他地方有定义这个函数,
- ;*编译时使用另一个地方的函数,否则使用这个函数
-
- ;*IMPORT 表示该标号来自外部文件,跟 C 语言中的 EXTERN 关键字类似
- IMPORT __main ;*__main 和 SystemInit 函数都是外部文件的标号
- IMPORT SystemInit ;* SystemInit 是STM32函数库的函数,作用是初始化系统时钟
- LDR R0, =SystemInit
- BLX R0
- LDR R0, =__main ;* __main是C库的函数,主要是初始化堆栈和代码重定位,然后跳到main函数执行用户编写的代码
- BX R0
- ENDP
-
- ; Dummy Exception Handlers (infinite loops which can be modified)
- ;下面定义的都是异常服务函中断服务函数
- NMI_Handler PROC
- EXPORT NMI_Handler [WEAK]
- B .
- ENDP
- .....由于文件太长这里省略了部分函数的定义,完整的可以查看工程里的启动文件
- SysTick_Handler PROC
- EXPORT SysTick_Handler [WEAK]
- B .
- ENDP
- Default_Handler PROC
- EXPORT WWDG_IRQHandler [WEAK]
- EXPORT PVD_IRQHandler [WEAK]
- .....由于文件太长这里省略了部分中断服务函数的定义,完整的可以查看工程里的启动文件
- EXPORT DMA2_Channel2_IRQHandler [WEAK]
- EXPORT DMA2_Channel3_IRQHandler [WEAK]
- EXPORT DMA2_Channel4_5_IRQHandler [WEAK]
- WWDG_IRQHandler
- PVD_IRQHandler
- TAMPER_IRQHandler
- .....由于文件太长这里省略了部分标号的定义,完整的可以查看工程里的启动文件
- DMA2_Channel1_IRQHandler
- DMA2_Channel2_IRQHandler
- DMA2_Channel3_IRQHandler
- DMA2_Channel4_5_IRQHandler
- B .
- ENDP
- ALIGN ;四字节对齐
- ;*******************************************************************************
- ; User Stack and Heap initialization
- ;*******************************************************************************
- ;下面函数是初始化堆栈的代码
- IF :DEF:__MICROLIB
- ;如果定义了__MICROLIB宏编译下面这部分代码,__MICROLIB在MDK工具里面定义
- ;这种方式初始化堆栈是由 __main 初始化的
- EXPORT __initial_sp ;栈顶地址 (EXPORT将标号声明为全局标号,供其他文件引用)
- EXPORT __heap_base ;堆的起始地址
- EXPORT __heap_limit ;堆的结束地址
-
- ELSE
- ;由用户初始化堆
- ;否则编译下面的
- IMPORT __use_two_region_memory ;__use_two_region_memory 由用户实现
- EXPORT __user_initial_stackheap
-
- __user_initial_stackheap
-
- LDR R0, = Heap_Mem ;堆的起始地址
- LDR R1, =(Stack_Mem + Stack_Size);栈顶地址
- LDR R2, = (Heap_Mem + Heap_Size);堆的结束地址
- LDR R3, = Stack_Mem ;栈的结束地址
- BX LR
- ALIGN
- ENDIF
- END
- ;******************* (C) COPYRIGHT 2011 STMicroelectronics *****END OF FILE*****
复制代码
STM32的启动步骤如下: 1、上电复位后,从 0x0000 0000 地址取出栈顶地址赋给MSP寄存器(主堆栈寄存器),即MSP = __initial_sp。这一步是由硬件自动完成的 2、从0x0000 0004 地址取出复位程序的地址给PC寄存器(程序计数器),即PC = Reset_Handler。这一步也是由硬件自动完成调用SystemInit函数初始化系统时钟 3、跳到C库的__main函数初始化堆栈(初始化时是根据前面的分配的堆空间和栈空间来初始化的)和代码重定位(初始RW 和ZI段),然后跳到main函数执行应用程序
IAP设计思路大体分为两部分设计,bootloader、APP代码设计,bootloader用于检查APP区代码是否需要更新,以及跳转到APP区执行APP程序 调研了一下群里的小伙伴,下面这个流程比较通用一些,大概是下图所示升级流程:
升级流程图
Flash分区是以STM32F103RET6为主控做的flash分区,主要功能:
 boot区:0x0800 0000 到 0x0800 b7FF 地址的flash块划分给bootloader,用于升级固件,大小是46kb 用户参数区:0x0800 B800 到 0x0800 BFFF 的flash块划分为用户参数区(parameters),用于存储用户的一些参数,大小是2Kb APP区:0x0800 C000 到 0x0804 3FFF 的flash块划分为APP区 ,(application)用于存放用户功能应用代码,大小是224Kb APP缓存区:0x0804 4000 到 0x0807 BFFF 的flash块划分为APP缓存区 (update region),用于暂存下发的固件,大小跟应用程序区一样 224kb 未定义:0x0807 C000 到 0x0807 FFFF 的flash块划分未定义区,可以根据具体用途定义,大小是16Kb
代码实现硬件: - fallingstar-board(已开源,打板验证)
软件: 内部flash读写操作这部分比较简单,直接上代码: 读flash操作: - /************************************************************
- * @brief 读取2字节数据
- * @param[in] uint32_t faddr
- * @return NULL
- * @github
- * @date 2021-xx-xx
- * @version v1.0
- * @note NULL
- ***********************************************************/
- uint16_t BSP_FLASH_ReadHalfWord(uint32_t raddr)
- {
- return *(__IO uint16_t*)raddr;
- }
- /************************************************************
- * @brief 读取n(uint16_t)字节数据
- * @param[in] uint32_t ReadAddr
- * @param[out] uint16_t *pBuffer
- * @param[in] uint16_t len
- * @return NULL
- * @github
- * @date 2021-xx-xx
- * @version v1.0
- * @note NULL
- ***********************************************************/
- void BSP_FLASH_Read (uint32_t ReadAddr, uint16_t *pBuffer, uint16_t len )
- {
- uint16_t i;
-
- for(i=0;i<len;i++)
- {
- pBuffer[i]=BSP_FLASH_ReadHalfWord(ReadAddr); //读取2个字节.
- ReadAddr+=2; //偏移2个字节.
- }
- }
复制代码
写操作,注意写之前要保证是没有写过的区域即可: - /************************************************************
- * @brief 写入n(uint16_t)字节数据
- * @param[in] uint32_t ReadAddr
- * @param[out] uint16_t *pBuffer
- * @param[in] uint16_t len
- * @return NULL
- * @github
- * @date 2021-xx-xx
- * @version v1.0
- * @note NULL
- ***********************************************************/
- void BSP_FLASH_Write_NoCheck ( uint32_t WriteAddr, uint16_t * pBuffer, uint16_t len )
- {
- uint16_t i;
-
- for(i=0;i<len;i++)
- {
- HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD,WriteAddr,pBuffer[i]);
- WriteAddr+=2; //地址增加2.
- }
- }
- /************************************************************
- * @brief 写入n(uint16_t)字节数据
- * @param[in] uint32_t WriteAddr
- * @param[in] uint16_t *pBuffer
- * @param[in] uint16_t len
- * @return NULL
- * @github
- * @date 2021-xx-xx
- * @version v1.0
- * @note NULL
- ***********************************************************/
- void BSP_FLASH_Write(uint32_t WriteAddr,uint16_t * pBuffer,uint16_t len )
- {
- uint32_t SECTORError = 0;
- uint16_t sector_off; //扇区内偏移地址(16位字计算)
- uint16_t sector_remain; //扇区内剩余地址(16位字计算)
- uint16_t i;
- uint32_t secor_pos; //扇区地址
- uint32_t offaddr; //去掉0X08000000后的地址
-
- if(WriteAddr<FLASH_BASE||(WriteAddr>=(FLASH_BASE+1024*STM32_FLASH_SIZE)))return;//非法地址
-
- HAL_FLASH_Unlock(); //解锁
-
- offaddr=WriteAddr-FLASH_BASE; //实际偏移地址.
- secor_pos=offaddr/STM_SECTOR_SIZE; //扇区地址 0~127 for STM32F103RBT6
- sector_off=(offaddr%STM_SECTOR_SIZE)/2; //在扇区内的偏移(2个字节为基本单位.)
- sector_remain=STM_SECTOR_SIZE/2-sector_off; //扇区剩余空间大小
- if(len<=sector_remain)sector_remain=len;//不大于该扇区范围
-
- while(1)
- {
- BSP_FLASH_Read(secor_pos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//读出整个扇区的内容
-
- for(i=0;i<sector_remain;i++)//校验数据
- {
- if(STMFLASH_BUF[sector_remain+i]!=0XFFFF)
- break;//需要擦除
- }
- if(i<sector_remain)//需要擦除
- {
- //擦除这个扇区
- /* Fill EraseInit structure*/
- EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
- EraseInitStruct.PageAddress = secor_pos*STM_SECTOR_SIZE+FLASH_BASE;
- EraseInitStruct.NbPages = 1;
- HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);
- for(i=0;i<sector_remain;i++)//复制
- {
- STMFLASH_BUF[i+sector_off]=pBuffer[i];
- }
- BSP_FLASH_Write_NoCheck(secor_pos*STM_SECTOR_SIZE+FLASH_BASE,STMFLASH_BUF,STM_SECTOR_SIZE/2);//写入整个扇区
- }
- else
- BSP_FLASH_Write_NoCheck(WriteAddr,pBuffer,sector_remain);//写已经擦除了的,直接写入扇区剩余区间.
- if(len==sector_remain)
- break;//写入结束了
- else//写入未结束
- {
- secor_pos++; //扇区地址增1
- sector_off=0; //偏移位置为0
- pBuffer+=sector_remain; //指针偏移
- WriteAddr+=sector_remain; //写地址偏移
- len-=sector_remain; //字节(16位)数递减
-
- if(len>(STM_SECTOR_SIZE/2))
- sector_remain=STM_SECTOR_SIZE/2;//下一个扇区还是写不完
- else
- sector_remain=len;//下一个扇区可以写完了
- }
- };
- HAL_FLASH_Lock();//上锁
- }
复制代码
串口DMA+空闲中断接收不定长数据在上篇文章的基础上,我们对结构体做点修改,增加bin文件数据总长度记录,以及bin文件接收完成标志(小伙伴们可以采用其他办法,不必拘泥于我教程中的方式): - #define Max_RecLen 1024*3
- typedef struct{
- uint8_t RxBuffer[Max_RecLen]; //DMA接收缓冲区
- uint16_t RecDat_len; //单包数据长度
- uint32_t Cur_WriteAddr; //APP缓冲区地址
- uint16_t BinLen; //bin文件数据长度
- uint8_t rec_endFlag; //单包数据接收结束标志
- uint8_t Binrec_endFlag; //bin文件接收结束标志
- uint16_t DMA_TIMCNT; //bin文件下发超时计数器
-
- }UserUartDMA_Typedef;
复制代码
然后在串口中断中: - /**
- * @brief This function handles USART1 global interrupt.
- */
- void USART1_IRQHandler(void)
- {
- /* USER CODE BEGIN USART1_IRQn 0 */
- uint32_t idle_flag_temp = 0;
- uint16_t len_temp = 0;
- /* USER CODE END USART1_IRQn 0 */
- HAL_UART_IRQHandler(&huart1);
- /* USER CODE BEGIN USART1_IRQn 1 */
- idle_flag_temp = __HAL_UART_GET_FLAG(&huart1,UART_FLAG_IDLE);
-
- if(idle_flag_temp)
- {
- __HAL_UART_CLEAR_FLAG(&huart1,UART_FLAG_IDLE);
- HAL_UART_DMAStop(&huart1);
-
- UserUartDma.DMA_TIMCNT = 0;
- UserUartDma.Binrec_endFlag=0;
- len_temp = __HAL_DMA_GET_COUNTER(&hdma_usart1_rx);
-
- UserUartDma.RecDat_len = Max_RecLen - len_temp;
- UserUartDma.BinLen+=UserUartDma.RecDat_len;
- UserUartDma.rec_endFlag = 1;
-
- }
- __HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE);
- HAL_UART_Receive_DMA(&huart1,UserUartDma.RxBuffer,Max_RecLen);
- /* USER CODE END USART1_IRQn 1 */
- }
复制代码
IAP代码设计扇区擦除,用于写入前擦除相应扇区 - /******************************************************
- * Brief : 擦除APP区
- * Parameter :
- * *startaddr:APP起始地址
- * *pages :要擦除的page = APPSIZE/PAGESIZE
- * Return :None.
- *******************************************************/
- void APPReigion_Erase(uint32_t startaddr,uint16_t pages)
- {
- uint32_t SECTORError = 0;
- //擦除APP区
- /* Fill EraseInit structure*/
- EraseInitStruct.TypeErase = FLASH_TYPEERASE_PAGES;
- EraseInitStruct.PageAddress = startaddr;
- EraseInitStruct.NbPages = pages;
- HAL_FLASHEx_Erase(&EraseInitStruct, &SECTORError);
- }
复制代码
升级标志获取与擦除 - /******************************************************
- * Brief : 写入升级标志
- * Parameter :
- * *addr:标志存放地址
- * *pdata:写入的数据
- * Return :None.
- *******************************************************/
- void APP_UpdateFlag_Write(uint32_t addr,uint16_t *pdata)
- {
- BSP_FLASH_Write(addr,pdata,1);
- }
- /******************************************************
- * Brief : 获取升级标志
- * Parameter :
- * *addr:标志存放地址
- * Return :None.
- *******************************************************/
- uint16_t APP_UpdateFlag_Read(uint32_t addr)
- {
- uint16_t flag_temp;
- BSP_FLASH_Read(addr,&flag_temp,1);
-
- return flag_temp;
- }
复制代码
接下来就是APP缓冲区数据写入,APP区与APP缓冲区数据倒腾了 - /******************************************************
- * Brief : Bin文件写入app缓冲区
- * Parameter :
- * StartAddr: 起始地址
- * *pBin_DataBuf: 要传输的数据
- * packBufLength:单包数据长度
- * Return : None.
- *******************************************************/
- void IAP_WriteBin(uint32_t StartAddr,uint8_t * pBin_DataBuf,uint32_t packBufLength)
- {
- uint16_t pack_len, packlen_Ctr=0, dataTemp;
- uint8_t * pData = pBin_DataBuf;
-
- for (pack_len = 0; pack_len < packBufLength; pack_len += 2 )
- {
- dataTemp = ( uint16_t ) pData[1]<<8;
- dataTemp += ( uint16_t ) pData[0];
- pData += 2; //偏移2个字节
- ulBuf_Flash_App [ packlen_Ctr ++ ] = dataTemp;
- }
- BSP_FLASH_Write ( UserUartDma.Cur_WriteAddr, ulBuf_Flash_App, packlen_Ctr );
- UserUartDma.Cur_WriteAddr += (packlen_Ctr*2); //偏移packlen_Ctr 16=2*8.所以要乘以2.
- packlen_Ctr = 0;
- }
- /******************************************************
- * Brief : Bin文件从app缓冲区写入app区
- * Parameter :
- * SrcStartAddr: app缓冲区起始地址
- * DstStartAddr: APP区起始地址
- * BinLength:bin文件长度
- * Return : None.
- *******************************************************/
- void IAP_WriteBinToAPPReigon(uint32_t SrcStartAddr,uint32_t DstStartAddr,uint32_t BinLength)
- {
- uint16_t data_temp = 0;
- uint32_t count=0;
-
-
- HAL_FLASH_Unlock(); //解锁
-
- APPReigion_Erase(APP_START_ADDR,APPSIZE/PAGESIZE);
- HAL_Delay(10);
-
- for(count=0;count<BinLength;count=count+2)
- {
- BSP_FLASH_Read (SrcStartAddr, &data_temp, 1);
- BSP_FLASH_Write_NoCheck(DstStartAddr, &data_temp,1);
- SrcStartAddr+=2;
- DstStartAddr+=2;
- }
- //BSP_FLASH_Write_NoCheck(DstStartAddr, ,1);
- HAL_FLASH_Lock();//上锁
- }
复制代码
最后,要设置APP程序的运行地址 - /******************************************************
- * Brief : 设置栈顶指针
- * Parameter :
- * ulAddr:
- * Return : None.
- *******************************************************/
- __asm void MSR_MSP ( uint32_t ulAddr )
- {
- MSR MSP, r0 //set Main Stack value
- BX r14
- }
- /******************************************************
- * Brief : IAP执行
- * Parameter :
- * ulAddr_App: APP起始地址
- * Return : None.
- *******************************************************/
- void IAP_ExecuteApp ( uint32_t ulAddr_App )
- {
- pIapFun_TypeDef pJump2App;
-
- if ( ( ( * ( __IO uint32_t * ) ulAddr_App ) & 0x2FFE0000 ) == 0x20000000 ) //检查栈顶地址是否合法.
- {
- pJump2App = ( pIapFun_TypeDef ) * ( __IO uint32_t * ) ( ulAddr_App + 4 ); //用户代码区第二个字为程序开始地址(复位地址)
- MSR_MSP( * ( __IO uint32_t * ) ulAddr_App ); //初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
- pJump2App (); //跳转到APP.
- }
- }
复制代码
IAP相关的代码就这些,主要是数据的倒腾,其他倒也没什么复杂的 目前小飞哥对整包bin文件传输完成判断,单包采用DMA+空闲中断的方式,整包文件传输采用的是串口中断在5s内没有数据过来,认为一包数据接收完成,置位接收标志,这部分小伙伴可以自行判断 - static void Systick_Config(void)
- {
- HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq() / 1000); //1ms
- HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
- HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
- }
- void HAL_SYSTICK_Callback(void)
- {
- UserUartDma.DMA_TIMCNT++;
- if(UserUartDma.DMA_TIMCNT>5000)
- {
- UserUartDma.Binrec_endFlag = 1;
- UserUartDma.DMA_TIMCNT = 0;
- }
- TIMX_IRQHandler_user();
- }
复制代码
bin文件传输完成后,写入需要更新标志,重新跳转至boot区,检查是否APP代码需要更新:
- uint32_t Task_02()
- {
-
- if(UserUartDma.rec_endFlag)
- {
- PRINT_INFO("update firmware\n");
- PRINT_INFO("APP 长度:%d字节\n", UserUartDma.RecDat_len);
-
- UserUartDma.rec_endFlag = 0;
- IAP_WriteBin(UserUartDma.Cur_WriteAddr,UserUartDma.RxBuffer,UserUartDma.RecDat_len);
-
- memset(UserUartDma.RxBuffer,0,UserUartDma.RecDat_len);
- UserUartDma.RecDat_len = 0;
- }
- if(UserUartDma.BinLen!=0)
- {
- if(UserUartDma.Binrec_endFlag)
- {
- UserUartDma.Binrec_endFlag = 0;
-
- APP_UpdateFlag_Write(APP_Len_ADDR,&UserUartDma.BinLen); //写入升级标志
- APP_UpdateFlag_Write(APP_UpdateFlag_ADDR,&APP_UPDATE_FLAG); //写入升级标志
- UserUartDma.BinLen = 0;
- IAP_ExecuteApp(FLASH_BASE); //跳转8000000,重新启动
- HAL_Delay(2000);
- }
-
- }
- }
复制代码
MCU复位之后,初始化执行过程中,对APP升级标志进行检测: - PRINT_INFO("-----IAP Menu--------------\n");
- PRINT_INFO("-----Download APP BIN------\n");
- PRINT_INFO("-----Restart To RUN APP----\n");
- PRINT_INFO("\n\n\n");
-
- if(0x5aa5==APP_UpdateFlag_Read(APP_UpdateFlag_ADDR))
- {
- PRINT_INFO("-----Restart.....5-------\n");
- HAL_Delay(1000);
- PRINT_INFO("-----Restart.....4-------\n");
- HAL_Delay(1000);
- PRINT_INFO("-----Restart.....3-------\n");
- HAL_Delay(1000);
- PRINT_INFO("-----Restart.....2-------\n");
- HAL_Delay(1000);
- PRINT_INFO("-----Restart.....1-------\n");
- HAL_Delay(1000);
-
- PRINT_INFO("the device need update\n");
- IAP_WriteBinToAPPReigon(APP_Temp_ADDR,APP_START_ADDR,APP_UpdateFlag_Read(APP_Len_ADDR));
- PRINT_INFO("firmware write OK\n");
- PRINT_INFO("please double click the button the execute the application\n");
-
- HAL_FLASH_Unlock(); //解锁
- APPReigion_Erase(APP_Temp_ADDR,APPSIZE/PAGESIZE); //擦除APP缓冲区
- APPReigion_Erase(Flash_UNUSED,1); //清除APP更新标志
- HAL_FLASH_Lock(); //上锁
-
- PRINT_INFO ( "开始执行 APP\n" );
- //执行FLASH APP代码
- IAP_ExecuteApp(APP_START_ADDR);
- }
复制代码
倒计时5秒后,代码更新,并执行 - PRINT_INFO("-----IAP Menu--------------\n");
- PRINT_INFO("-----Download APP BIN------\n");
- PRINT_INFO("-----Restart To RUN APP----\n");
- PRINT_INFO("\n\n\n");
-
- if(0x5aa5==APP_UpdateFlag_Read(APP_UpdateFlag_ADDR))
- {
- PRINT_INFO("-----Restart.....5-------\n");
- HAL_Delay(1000);
- PRINT_INFO("-----Restart.....4-------\n");
- HAL_Delay(1000);
- PRINT_INFO("-----Restart.....3-------\n");
- HAL_Delay(1000);
- PRINT_INFO("-----Restart.....2-------\n");
- HAL_Delay(1000);
- PRINT_INFO("-----Restart.....1-------\n");
- HAL_Delay(1000);
-
- PRINT_INFO("the device need update\n");
- IAP_WriteBinToAPPReigon(APP_Temp_ADDR,APP_START_ADDR,APP_UpdateFlag_Read(APP_Len_ADDR));
- PRINT_INFO("firmware write OK\n");
- PRINT_INFO("please double click the button the execute the application\n");
-
- HAL_FLASH_Unlock(); //解锁
- APPReigion_Erase(APP_Temp_ADDR,APPSIZE/PAGESIZE); //擦除APP缓冲区
- APPReigion_Erase(Flash_UNUSED,1); //清除APP更新标志
- HAL_FLASH_Lock(); //上锁
-
- PRINT_INFO ( "开始执行 APP\n" );
- //执行FLASH APP代码
- IAP_ExecuteApp(APP_START_ADDR);
- }
- else//不需要更新,执行APP
- {
- IAP_ExecuteApp(APP_START_ADDR);
- }
复制代码
bin文件生成
输入: C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o IAP.bin UART_CircleQueueTest\UART_CircleQueueTest.axf
参数意义: 输出bin文件名称 - UART_CircleQueueTest\UART_CircleQueueTest.axf:
axf文件目录及文件 要注意boot区的地址范围,根据前面的设计,46K,拿出计算器.... 
APP代码是用户功能代码,实现业务逻辑,本次测试用的比较简单,接收到APP代码之后,会自动重启,更新APP区代码,如下:

 注意APP BIN文件的flash地址设置
OK,至此,设计部分就完成了,涉及的内容还是非常多的 本文仅仅用于原理性介绍及IAP功能演示,与工程中实际使用的还是有很大区别的,后面会继续给大家出工程应用方便的教程,会涉及到签名、验签、加解密、校验固件完整性等等
转载自:Embedded小飞哥
如有侵权请联系删除
|