IAP固件升级原理及实现详解
什么是IAP升級?
IAP,即In Application Programming,IAP是用戶自己的程序在運行過程中對User Flash的部分區域進行燒寫。簡單來說,就是開發者代碼出bug了或者添加新功能了,能夠利用預留的通訊接口,對代碼進行升級
UART、SPI、IIC、USB等等,當然還有wifi、4G、藍牙等無線通訊手段,都可以作為IAP升級的方式,今天主要介紹如何使用串口對固件進行升級
STM32的代碼啟動過程
要想設計IAP,首先需要對MCU的代碼啟動過程有個了解,先來看看STM32的代碼啟動過程是怎樣的吧
此部分參考:https://www.cnblogs.com/gulan-zmc/p/12248509.html
在《Cortex-M3權威指南》有講述:芯片復位后首先會從向量表里面取出兩個值(下圖來自Cortex-M3權威指南):
-
從0x0000 0000地址取出MSP(主堆棧寄存器)的值
-
從0x0000 0004地址取出PC(程序計數器)的值
-
然后取出第一條指令執行
啟動文件源代碼分析
;********************?(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?????0x00000200AREA????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?ResetAREA????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?HandlerDCD?????MemManage_Handler??????????;?MPU?Fault?HandlerDCD?????BusFault_Handler???????????;?Bus?Fault?HandlerDCD?????UsageFault_Handler?????????;?Usage?Fault?Handler.....由于文件太長這里省略了部分向量表的定義,完整的可以查看工程里的啟動文件DCD?????DMA2_Channel1_IRQHandler???;?DMA2?Channel1DCD?????DMA2_Channel2_IRQHandler???;?DMA2?Channel2DCD?????DMA2_Channel3_IRQHandler???;?DMA2?Channel3DCD?????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,?=SystemInitBLX?????R0??????????????LDR?????R0,?=__main???????????????????????;*?__main是C庫的函數,主要是初始化堆棧和代碼重定位,然后跳到main函數執行用戶編寫的代碼BX??????R0ENDP;?Dummy?Exception?Handlers?(infinite?loops?which?can?be?modified) ;下面定義的都是異常服務函中斷服務函數 NMI_Handler?????PROCEXPORT??NMI_Handler????????????????[WEAK]B???????.ENDP .....由于文件太長這里省略了部分函數的定義,完整的可以查看工程里的啟動文件 SysTick_Handler?PROCEXPORT??SysTick_Handler????????????[WEAK]B???????.ENDPDefault_Handler?PROCEXPORT??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_IRQHandlerB???????.ENDPALIGN????;四字節對齊;******************************************************************************* ;?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_stackheapLDR?????R0,?=??Heap_Mem??????????????;堆的起始地址LDR?????R1,?=(Stack_Mem?+?Stack_Size);棧頂地址LDR?????R2,?=?(Heap_Mem?+??Heap_Size);堆的結束地址LDR?????R3,?=?Stack_Mem??????????????;棧的結束地址BX??????LRALIGNENDIFEND;*******************?(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讀寫
-
串口DMA+空閑中斷
內部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?STM32F103RBT6sector_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++;????//扇區地址增1sector_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+空閑中斷接收不定長數據
麻煩小伙伴移步:
串口DMA+空閑中斷接收不定長數據
在上篇文章的基礎上,我們對結構體做點修改,增加bin文件數據總長度記錄,以及bin文件接收完成標志(小伙伴們可以采用其他辦法,不必拘泥于我教程中的方式):
#define?Max_RecLen?1024*3typedef?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代碼設計
-
bootloader代碼設計
扇區擦除,用于寫入前擦除相應扇區
/****************************************************** *?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?valueBX?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);?//1msHAL_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
參數意義:
-
C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe: fromelf.exe文件目錄地址,一般keil下自帶有
-
--bin -o IAP.bin :
輸出bin文件名稱
-
UART_CircleQueueTest\UART_CircleQueueTest.axf:
axf文件目錄及文件
要注意boot區的地址范圍,根據前面的設計,46K,拿出計算器....
-
APP代碼設計
APP代碼是用戶功能代碼,實現業務邏輯,本次測試用的比較簡單,接收到APP代碼之后,會自動重啟,更新APP區代碼,如下:
-
boot區代碼執行效果
-
APP1代碼執行效果
-
接下來再進行一次更新
注意APP BIN文件的flash地址設置
OK,至此,設計部分就完成了,涉及的內容還是非常多的
本文僅僅用于原理性介紹及IAP功能演示,與工程中實際使用的還是有很大區別的,后面會繼續給大家出工程應用方便的教程,會涉及到簽名、驗簽、加解密、校驗固件完整性等等
特別感謝
感謝群友大帥比“肥嘟嘟左衛門”特為小伙伴們寫的上位機測試軟件,非常的nice,上位機軟件還在更新中,歡迎小伙伴們進群聊,催更!!!
資料獲取
歡迎關注公眾號,后臺回復“IAP”即可獲取測試boot、APP源碼,歡迎添加小飛哥好友,進群一起交流!
總結
以上是生活随笔為你收集整理的IAP固件升级原理及实现详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 光棍.com市场推广策划书(爆笑)
- 下一篇: 用python证明给定范围的欧拉常数