1. 什么是差分/增量升級?
借用網上的介紹:適合嵌入式的差分升級又叫增量升級,顧名思義就是通過差分算法將源版本與目標版本之間差異的部分提取出來制作成差分包,然后在設備通過還原算法將差異部分在源版本上進行還原從而升級成目標版本的過程。
差分升級方案不僅可以節省MCU內部的資源空間、還可以節省下載流程及下載和升級過程中的功耗。
也就是說,差分升級是拿以前舊設備內的bin,和當前新版本的bin用某種算法算出他們的差異部分,然后在借助壓縮算法,生產一個極其小的差分包,然后將這個差分包下載到設備中,設備在根據解壓算法、差分還原算法,生產一個完整的新版本bin,然后將這個新版本bin刷到執行區執行代碼。
差分升級一般來說,可以極大的減少下載量,特別是對于嵌入式STM32等單片機來說,可以極大的減少維護成本,因為嵌入式設備的升級維護一般都是空中ota升級,比如藍牙、紅外等,下載速度受到波特率、包長等限制,更新固件包非常的慢,而差分升級可以讓下載的過程極大的縮小。
正常的維護版本,即使改的再多,生成的差分包bin理論上在原bin的5%左右,比如一個300k的bin,改的很多的情況下差分包也不過15k左右,而我實際測試,版本維護平均都在5~10k左右。
2. 差分升級實現原理
差分升級過程:
1、使用舊版本bin文件和新版本bin文件制作差分包
2、將差分包下載到設備內
3、設備使用差分算法還原出新版本bin
4、設備將新版本bin進行crc驗證后刷到代碼執行區
5、設備重啟并以新版本運行
在過程中有2個關鍵點:
第一:如何使用舊版本bin文件和新版本bin文件制作差分包?
該過程我們使用穩定的開源差分算法bsdiff+lzma生成差分包,該算法被大量使用,穩定安全,并且我們已在項目中批量使用,經過長時間的驗證無任何問題。一般來說,該過程都是使用上位機來完成,嵌入式設備無需關心,我們已經做好了上位機軟件,可以供大家隨意使用,稍后會進行介紹。
第二:設備收到差分包后如何還原出新版本的bin文件?
該過程就是我們要講解的重點過程,相對應的,嵌入式設備中,我們依然使用開源差分算法bsdiff+lzma來還原新版本文件,代碼全開源,并且我已做成了庫、抽象出了極簡的接口,移植起來費不了多少功夫。
基本是市場上所有的單片機如stm32、瑞薩、華大、復旦微等都可以使用,但是有內存限制,要求ram至少要10k以上,然后是該庫本身的消耗大概是5k的rom。
3. 關鍵點一:差分包制作過程
對于差分包的制作,我已經開發好了上位機軟件,界面如下圖所示:
上位機這邊主要實現使用開源算法bsdiff制作舊版本bin和新版本bin的差分包,然后在使用lzma壓縮算法來壓縮差分包,最終生成一個差分bin,使用方法上位機界面提示的很清楚,最終效果如下圖所示:
4. 關鍵點二:嵌入式設備中差分算法庫的移植(還原差分包)
4.1. 移植開關算法庫代碼
代碼已開源,地址:
https://gitee.com/qq791314247/mcu_bsdiff_upgrade
整體代碼如下圖所示:
如上圖所示,99%的代碼用戶都不用去關心,用戶只需要提供一個flash寫入接口即可,也就是該庫給定用戶flash地址、數據內容指針、數據內容長度,用戶將該段數據寫入到flash即可,移植起來特別簡單,花不了幾分鐘的功夫,這也是我花大力氣抽象接口的原因。
4.2. 使用該庫的流程
4.2.1. 使用庫的接口
對于整個庫的代碼,我們只需要關心一個接口iap_patch,iap_patch在文件”user_interface.h”中。
該接口介紹也比較清晰,差分包的還原,只需要調用這一個接口即可。
/** *@brief用戶使用差分升級時唯一需要關心的接口 * *@paramold設備中執行區代碼所在的地址,用戶可指定flash執行區的地址,方便算法讀出來當前 *運行中的代碼 *@paramoldsize設備中執行區代碼的長度,用戶可在差分包bin頭獲取 *@parampatch設備中已經下載的差分包所在的flash地址,或者ram地址,只要能讓算法讀出來即可 *注意,下載的差分包自帶image_header_t格式的文件頭,真正的差分包需要偏 *移sizeof(image_header_t)的長度 *@parampatchsize設備中已經下載的差分包的長度,用戶可在差分包bin頭獲取 *@paramnewfile新文件的大小,用戶需填入新版本bin的長度,用戶亦可以差分包bin頭獲取 *@returnint然后錯誤碼,0成功,1失敗 */ externintiap_patch(constuint8_t*old,uint32_toldsize,constuint8_t*patch, uint32_tpatchsize,uint32_tnewfile);
另外,使用該接口還原時所需要的一些信息可以在差分包文件頭中獲取,上位機在制作差分包時,會自動在差分包的bin頭加上64字節的文件頭,以便于告訴嵌入式設備舊/新版本bin文件的CRC校驗、長度等信息。所以用戶在收到差分包頭時,偏移掉這64個字節的文件頭的地址才是需要給到iap_patch接口的真正的bin文件。
文件頭格式如下代碼,用戶只需要關心中文注釋的部分,其余的都是預留的信息。
/*差分包制作時自帶的文件頭信息,用戶只需要關心中文注釋的部分*/ typedefstructimage_header { uint32_tih_magic;/*ImageHeaderMagicNumber*/ uint32_tih_hcrc;/*ImageHeaderCRCChecksum差分包包頭校驗*/ uint32_tih_time;/*ImageCreationTimestamp*/ uint32_tih_size;/*ImageDataSize差分包的大小*/ uint32_tih_load;/*DataLoadAddress上一版本舊文件的大小*/ uint32_tih_ep;/*EntryPointAddress要升級的新文件的大小*/ uint32_tih_dcrc;/*ImageDataCRCChecksum新文件的CRC*/ uint8_tih_os;/*OperatingSystem*/ uint8_tih_arch;/*CPUarchitecture*/ uint8_tih_type;/*ImageType*/ uint8_tih_comp;/*CompressionType*/ uint8_tih_name[IH_NMLEN];/*ImageName*/ uint32_tih_ocrc;/*OldImageDataCRCChecksum上一版本舊文件的CRC*/ }image_header_t; /*差分包制作時自帶的文件頭信息,用戶只需要關心中文注釋的部分*/
4.2.2. 接口使用例子
我截取一段我工程中的代碼來講解如何使用該接口還原出新版本bin文件:
#ifdefBSDIFF_UPGRADE image_header_trecv_head; uint32_trecv_hcrc;/*接收到的文件頭CRC*/ uint32_tcalculation_crc;/*計算出來的文件頭CRC*/ uint32_tspi_flash_addr=UPGRADE_PROGRAM_ADDR; memcpy(&recv_head,(uint8_t*)APPLICATION_A,sizeof(image_header_t)); recv_hcrc=BigtoLittle32(recv_head.ih_hcrc); recv_head.ih_hcrc=0; calculation_crc=crc32((uint8_t*)&recv_head,sizeof(image_header_t)); if(recv_hcrc==calculation_crc) { recv_head.ih_hcrc=recv_hcrc; recv_head.ih_time=BigtoLittle32(recv_head.ih_time); recv_head.ih_size=BigtoLittle32(recv_head.ih_size); recv_head.ih_dcrc=BigtoLittle32(recv_head.ih_dcrc); recv_head.ih_ocrc=BigtoLittle32(recv_head.ih_ocrc); /*差分升級包*/ recv_head.ih_hcrc=calculation_crc; if(crc32((uint8_t*)APPLICATION_RUN,recv_head.ih_load)!=recv_head.ih_ocrc) { APP_ERR_PRINT("fileoldcrcerr,calcrc:0X%08X,ih_oldbin_crc:0X%08X,", crc32((uint8_t*)APPLICATION_RUN, recv_head.ih_load),recv_head.ih_ocrc); gotobsdiff_out; } RTOS_LOCK(); disable_task_monitoring(ALL_TASK_RUNFLAG_BIT,true); //flash_erase_sector(UPGRADE_PROGRAM_ADDR,UPGRADE_PROGRAM_PAGE); recv_hcrc=iap_patch((uint8_t*)APPLICATION_RUN,recv_head.ih_load, (uint8_t*)(APPLICATION_A+sizeof(image_header_t)), recv_head.ih_size,UPGRADE_PROGRAM_ADDR); if(recv_hcrc!=recv_head.ih_ep) { APP_ERR_PRINT("iap_patchlenerr."); APP_ERR_PRINT("iap_patchlen:%lu,new_len:%lu",recv_hcrc,recv_head.ih_ep); gotobsdiff_out; } if(erase_program(APPLICATION_A)) { APP_ERR_PRINT("Ieraseprogramfailed."); gotobsdiff_out; } current_flash_write_addr=APPLICATION_A; for(uint32_ti=0;i(recv_head.ih_ep?/?1024);?i++) ?????????????{ ?????????????????xmq25qxx_read(spi_flash_addr,?spi_read_cache,?1024); ?????????????????if?(xflash_write(current_flash_write_addr,?spi_read_cache,?1024)) ?????????????????{ ?????????????????????APP_ERR_PRINT("I?write?program?failed."); ?????????????????????goto?bsdiff_out; ?????????????????} ?????????????????spi_flash_addr?+=?1024; ?????????????????current_flash_write_addr?+=?1024; ?????????????????APP_PRINT("current_flash_write_addr:?0X%08X",?current_flash_write_addr); ?????????????} ?????????????if?(recv_head.ih_ep?%?1024?!=?0) ?????????????{ ?????????????????memset(spi_read_cache,?0XFF,?1024); ?????????????????xmq25qxx_read(spi_flash_addr,?spi_read_cache,?recv_head.ih_ep?%?1024); ? ?????????????????if?(xflash_write(current_flash_write_addr,?spi_read_cache,?1024)) ?????????????????{ ?????????????????????APP_ERR_PRINT("I?write?program?failed."); ?????????????????????goto?bsdiff_out; ?????????????????} ?????????????} ?????????????if?(crc32((uint8_t?*)APPLICATION_A,?recv_head.ih_ep)?!=?recv_head.ih_dcrc) ?????????????{ ?????????????????APP_ERR_PRINT("file?newcrc?err,calcrc:0X%08X,?newcrc:0X%08X,?len:?%lu",? ??????????????????????????????????????????crc32((uint8_t?*)APPLICATION_A,?recv_head.ih_ep),? ??????????????????????????????????????????recv_head.ih_load,?recv_head.ih_dcrc); ?????????????????goto?bsdiff_out; ?????????????} ?????????????/*?下載成功,開始升級?*/ ?????????????if?(check_bin_file((bin_info_t?*)(APPLICATION_A?+?BIN_INFO_OFFSET)))?/*?bin文件非法?*/ ?????????????{ ?????????????????APP_ERR_PRINT("check_bin_file?err."); ?????????????????goto?bsdiff_out; ?????????????} ?????????????recv_head.ih_dcrc?=?CRT_CRC16_check(0,?(uint8_t?*)APPLICATION_A,? ?????????????????????????????????????????????????recv_head.ih_ep); ?????????????readwrite_app_run_bin_info(0,?&recv_head.ih_ep,?(uint16_t?*)&recv_head.ih_dcrc); ?????????????/*?整體校驗成功,確認升級?*/ ?????????????if?(switch_program_stage(STAGE_1)) ?????????????{ ?????????????????APP_ERR_PRINT("I?write?switch_program_stage?STAGE_0?failed.");??/*?置位升級標志寫失敗?*/ ?????????????????goto?bsdiff_out; ?????????????} ?????????????APP_PRINT("upgrade?success."); ???bsdiff_out: ?????????????SYSTEM_RESET(); ?????????}審核編輯:湯梓紅
-
單片機
+關注
關注
6039文章
44583瀏覽量
636526 -
mcu
+關注
關注
146文章
17185瀏覽量
351734 -
嵌入式
+關注
關注
5087文章
19148瀏覽量
306186 -
STM32
+關注
關注
2270文章
10910瀏覽量
356615
原文標題:適用于嵌入式的差分升級通用庫!
文章出處:【微信號:電子工程師筆記,微信公眾號:電子工程師筆記】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論