1. 什么是差分/增量升級?
借用網(wǎng)上的介紹:適合嵌入式的差分升級又叫增量升級,顧名思義就是通過差分算法將源版本與目標(biāo)版本之間差異的部分提取出來制作成差分包,然后在設(shè)備通過還原算法將差異部分在源版本上進行還原從而升級成目標(biāo)版本的過程。
差分升級方案不僅可以節(jié)省MCU內(nèi)部的資源空間、還可以節(jié)省下載流程及下載和升級過程中的功耗。
也就是說,差分升級是拿以前舊設(shè)備內(nèi)的bin,和當(dāng)前新版本的bin用某種算法算出他們的差異部分,然后在借助壓縮算法,生產(chǎn)一個極其小的差分包,然后將這個差分包下載到設(shè)備中,設(shè)備在根據(jù)解壓算法、差分還原算法,生產(chǎn)一個完整的新版本bin,然后將這個新版本bin刷到執(zhí)行區(qū)執(zhí)行代碼。
差分升級一般來說,可以極大的減少下載量,特別是對于嵌入式STM32等單片機來說,可以極大的減少維護成本,因為嵌入式設(shè)備的升級維護一般都是空中ota升級,比如藍牙、紅外等,下載速度受到波特率、包長等限制,更新固件包非常的慢,而差分升級可以讓下載的過程極大的縮小。
正常的維護版本,即使改的再多,生成的差分包bin理論上在原bin的5%左右,比如一個300k的bin,改的很多的情況下差分包也不過15k左右,而我實際測試,版本維護平均都在5~10k左右。
2. 差分升級實現(xiàn)原理
差分升級過程:
1、使用舊版本bin文件和新版本bin文件制作差分包
2、將差分包下載到設(shè)備內(nèi)
3、設(shè)備使用差分算法還原出新版本bin
4、設(shè)備將新版本bin進行crc驗證后刷到代碼執(zhí)行區(qū)
5、設(shè)備重啟并以新版本運行
在過程中有2個關(guān)鍵點:
第一:如何使用舊版本bin文件和新版本bin文件制作差分包?
該過程我們使用穩(wěn)定的開源差分算法bsdiff+lzma生成差分包,該算法被大量使用,穩(wěn)定安全,并且我們已在項目中批量使用,經(jīng)過長時間的驗證無任何問題。一般來說,該過程都是使用上位機來完成,嵌入式設(shè)備無需關(guān)心,我們已經(jīng)做好了上位機軟件,可以供大家隨意使用,稍后會進行介紹。
第二:設(shè)備收到差分包后如何還原出新版本的bin文件?
該過程就是我們要講解的重點過程,相對應(yīng)的,嵌入式設(shè)備中,我們依然使用開源差分算法bsdiff+lzma來還原新版本文件,代碼全開源,并且我已做成了庫、抽象出了極簡的接口,移植起來費不了多少功夫。
基本是市場上所有的單片機如stm32、瑞薩、華大、復(fù)旦微等都可以使用,但是有內(nèi)存限制,要求ram至少要10k以上,然后是該庫本身的消耗大概是5k的rom。
3. 關(guān)鍵點一:差分包制作過程
對于差分包的制作,我已經(jīng)開發(fā)好了上位機軟件,界面如下圖所示:
上位機這邊主要實現(xiàn)使用開源算法bsdiff制作舊版本bin和新版本bin的差分包,然后在使用lzma壓縮算法來壓縮差分包,最終生成一個差分bin,使用方法上位機界面提示的很清楚,最終效果如下圖所示:
4. 關(guān)鍵點二:嵌入式設(shè)備中差分算法庫的移植(還原差分包)
4.1. 移植開關(guān)算法庫代碼
代碼已開源,地址:
https://gitee.com/qq791314247/mcu_bsdiff_upgrade
整體代碼如下圖所示:
如上圖所示,99%的代碼用戶都不用去關(guān)心,用戶只需要提供一個flash寫入接口即可,也就是該庫給定用戶flash地址、數(shù)據(jù)內(nèi)容指針、數(shù)據(jù)內(nèi)容長度,用戶將該段數(shù)據(jù)寫入到flash即可,移植起來特別簡單,花不了幾分鐘的功夫,這也是我花大力氣抽象接口的原因。
4.2. 使用該庫的流程
4.2.1. 使用庫的接口
對于整個庫的代碼,我們只需要關(guān)心一個接口iap_patch,iap_patch在文件”user_interface.h”中。
該接口介紹也比較清晰,差分包的還原,只需要調(diào)用這一個接口即可。
/** *@brief用戶使用差分升級時唯一需要關(guān)心的接口 * *@paramold設(shè)備中執(zhí)行區(qū)代碼所在的地址,用戶可指定flash執(zhí)行區(qū)的地址,方便算法讀出來當(dāng)前 *運行中的代碼 *@paramoldsize設(shè)備中執(zhí)行區(qū)代碼的長度,用戶可在差分包bin頭獲取 *@parampatch設(shè)備中已經(jīng)下載的差分包所在的flash地址,或者ram地址,只要能讓算法讀出來即可 *注意,下載的差分包自帶image_header_t格式的文件頭,真正的差分包需要偏 *移sizeof(image_header_t)的長度 *@parampatchsize設(shè)備中已經(jīng)下載的差分包的長度,用戶可在差分包bin頭獲取 *@paramnewfile新文件的大小,用戶需填入新版本bin的長度,用戶亦可以差分包bin頭獲取 *@returnint然后錯誤碼,0成功,1失敗 */ externintiap_patch(constuint8_t*old,uint32_toldsize,constuint8_t*patch, uint32_tpatchsize,uint32_tnewfile);
另外,使用該接口還原時所需要的一些信息可以在差分包文件頭中獲取,上位機在制作差分包時,會自動在差分包的bin頭加上64字節(jié)的文件頭,以便于告訴嵌入式設(shè)備舊/新版本bin文件的CRC校驗、長度等信息。所以用戶在收到差分包頭時,偏移掉這64個字節(jié)的文件頭的地址才是需要給到iap_patch接口的真正的bin文件。
文件頭格式如下代碼,用戶只需要關(guān)心中文注釋的部分,其余的都是預(yù)留的信息。
/*差分包制作時自帶的文件頭信息,用戶只需要關(guān)心中文注釋的部分*/ 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; /*差分包制作時自帶的文件頭信息,用戶只需要關(guān)心中文注釋的部分*/
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.");??/*?置位升級標(biāo)志寫失敗?*/ ?????????????????goto?bsdiff_out; ?????????????} ?????????????APP_PRINT("upgrade?success."); ???bsdiff_out: ?????????????SYSTEM_RESET(); ?????????}審核編輯:湯梓紅
-
單片機
+關(guān)注
關(guān)注
6030文章
44491瀏覽量
632029 -
mcu
+關(guān)注
關(guān)注
146文章
16900瀏覽量
349950 -
嵌入式
+關(guān)注
關(guān)注
5060文章
18975瀏覽量
302094 -
STM32
+關(guān)注
關(guān)注
2264文章
10854瀏覽量
354319
原文標(biāo)題:適用于嵌入式的差分升級通用庫!
文章出處:【微信號:電子工程師筆記,微信公眾號:電子工程師筆記】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論