嵌入式设备差分升级原理和实现(附代码)

一起学嵌入式 2023-04-14 07:50

点击下方一起学嵌入式】关注,一起学习,一起成长

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,使用方法上位机界面提示的很清楚,最终效果如下图所示:

下载链接:

链接:

https://pan.baidu.com/s/1nBpftim8OCUI3i32sP3eOA?pwd=diff

提取码:diff

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 用户使用差分升级时唯一需要关心的接口
 * 
 * @param old 设备中执行区代码所在的地址,用户可指定flash执行区的地址,方便算法读出来当前
 *            运行中的代码
 * @param oldsize 设备中执行区代码的长度,用户可在差分包bin头获取
 * @param patch 设备中已经下载的差分包所在的flash地址,或者ram地址,只要能让算法读出来即可
 *              注意,下载的差分包自带image_header_t格式的文件头,真正的差分包需要偏
 *              移sizeof(image_header_t)的长度
 * @param patchsize 设备中已经下载的差分包的长度,用户可在差分包bin头获取
 * @param newfile 新文件的大小,用户需填入新版本bin的长度,用户亦可以差分包bin头获取
 * @return int 然后错误码,0成功,1失败
 */

extern int iap_patch(const uint8_t* old, uint32_t oldsize, const uint8_t* patch,
                     uint32_t patchsize, uint32_t newfile);

另外,使用该接口还原时所需要的一些信息可以在差分包文件头中获取,上位机在制作差分包时,会自动在差分包的bin头加上64字节的文件头,以便于告诉嵌入式设备旧/新版本bin文件的CRC校验、长度等信息。所以用户在收到差分包头时,偏移掉这64个字节的文件头的地址才是需要给到iap_patch接口的真正的bin文件。

文件头格式如下代码,用户只需要关心中文注释的部分,其余的都是预留的信息。

/* 差分包制作时自带的文件头信息,用户只需要关心中文注释的部分 */
 typedef struct image_header
 {
   uint32_t ih_magic; /* Image Header Magic Number */
   uint32_t ih_hcrc;  /* Image Header CRC Checksum 差分包包头校验 */
   uint32_t ih_time;  /* Image Creation Timestamp */
   uint32_t ih_size;  /* Image Data Size 差分包的大小 */
   uint32_t ih_load;  /* Data Load Address 上一版本旧文件的大小 */
   uint32_t ih_ep;   /* Entry Point Address 要升级的新文件的大小 */
   uint32_t ih_dcrc;  /* Image Data CRC Checksum 新文件的CRC */
   uint8_t ih_os;   /* Operating System */
   uint8_t ih_arch;  /* CPU architecture */
   uint8_t ih_type;  /* Image Type */
   uint8_t ih_comp;  /* Compression Type */
   uint8_t ih_name[IH_NMLEN]; /* Image Name */
   uint32_t ih_ocrc;  /* Old Image Data CRC Checksum 上一版本旧文件的CRC */
 } image_header_t
 /* 差分包制作时自带的文件头信息,用户只需要关心中文注释的部分 */

4.2.2. 接口使用例子

我截取一段我工程中的代码来讲解如何使用该接口还原出新版本bin文件:

 #ifdef BSDIFF_UPGRADE
         image_header_t recv_head;
         uint32_t recv_hcrc;  /* 接收到的文件头CRC */
         uint32_t calculation_crc;  /* 计算出来的文件头CRC */
         uint32_t spi_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("file oldcrc err,calcrc:0X%08X, ih_oldbin_crc:0X%08X,"
                                           crc32((uint8_t *)APPLICATION_RUN, 
                                           recv_head.ih_load), recv_head.ih_ocrc);
                 goto bsdiff_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_patch len err.");
                 APP_ERR_PRINT("iap_patch len: %lu, new_len: %lu", recv_hcrc, recv_head.ih_ep);
                 goto bsdiff_out;
             }
             if (erase_program(APPLICATION_A))
             {
                 APP_ERR_PRINT("I erase program failed.");
                 goto bsdiff_out;
             }
 
             current_flash_write_addr = APPLICATION_A;
             for (uint32_t i = 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, 0XFF1024);
                 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();
         }
资源下载
(1)可以通过文中的下载链接自行获取。

(2)公众号后台回复【差分升级】,获取打包好的源码和工具。


原文:https://blog.csdn.net/qq_35333978/article/details/128211763?spm=1001.2014.3001.5501
文章来源于网络,版权归原作者所有,如有侵权,请联系删除。



关注我【一起学嵌入式】,一起学习,一起成长。


觉得文章不错,点击“分享”、“”、“在看” 呗!

一起学嵌入式 公众号【一起学嵌入式】,RTOS、Linux编程、C/C++,以及经验分享、行业资讯、物联网等技术知
评论
  • 11-29学习笔记11-29学习笔记习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-02 23:58 51浏览
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 101浏览
  • 遇到部分串口工具不支持1500000波特率,这时候就需要进行修改,本文以触觉智能RK3562开发板修改系统波特率为115200为例,介绍瑞芯微方案主板Linux修改系统串口波特率教程。温馨提示:瑞芯微方案主板/开发板串口波特率只支持115200或1500000。修改Loader打印波特率查看对应芯片的MINIALL.ini确定要修改的bin文件#查看对应芯片的MINIALL.ini cat rkbin/RKBOOT/RK3562MINIALL.ini修改uart baudrate参数修改以下目
    Industio_触觉智能 2024-12-03 11:28 41浏览
  •         温度传感器的精度受哪些因素影响,要先看所用的温度传感器输出哪种信号,不同信号输出的温度传感器影响精度的因素也不同。        现在常用的温度传感器输出信号有以下几种:电阻信号、电流信号、电压信号、数字信号等。以输出电阻信号的温度传感器为例,还细分为正温度系数温度传感器和负温度系数温度传感器,常用的铂电阻PT100/1000温度传感器就是正温度系数,就是说随着温度的升高,输出的电阻值会增大。对于输出
    锦正茂科技 2024-12-03 11:50 66浏览
  • 戴上XR眼镜去“追龙”是种什么体验?2024年11月30日,由上海自然博物馆(上海科技馆分馆)与三湘印象联合出品、三湘印象旗下观印象艺术发展有限公司(下简称“观印象”)承制的《又见恐龙》XR嘉年华在上海自然博物馆重磅开幕。该体验项目将于12月1日正式对公众开放,持续至2025年3月30日。双向奔赴,恐龙IP撞上元宇宙不久前,上海市经济和信息化委员会等部门联合印发了《上海市超高清视听产业发展行动方案》,特别提到“支持博物馆、主题乐园等场所推动超高清视听技术应用,丰富线下文旅消费体验”。作为上海自然
    电子与消费 2024-11-30 22:03 86浏览
  • 《高速PCB设计经验规则应用实践》+PCB绘制学习与验证读书首先看目录,我感兴趣的是这一节;作者在书中列举了一条经典规则,然后进行详细分析,通过公式推导图表列举说明了传统的这一规则是受到电容加工特点影响的,在使用了MLCC陶瓷电容后这一条规则已经不再实用了。图书还列举了高速PCB设计需要的专业工具和仿真软件,当然由于篇幅所限,只是介绍了一点点设计步骤;我最感兴趣的部分还是元件布局的经验规则,在这里列举如下:在这里,演示一下,我根据书本知识进行电机驱动的布局:这也算知行合一吧。对于布局书中有一句:
    wuyu2009 2024-11-30 20:30 106浏览
  • 作为优秀工程师的你,已身经百战、阅板无数!请先醒醒,新的项目来了,这是一个既要、又要、还要的产品需求,ARM核心板中一个处理器怎么能实现这么丰富的外围接口?踌躇之际,你偶阅此文。于是,“潘多拉”的魔盒打开了!没错,USB资源就是你打开新世界得钥匙,它能做哪些扩展呢?1.1  USB扩网口通用ARM处理器大多带两路网口,如果项目中有多路网路接口的需求,一般会选择在主板外部加交换机/路由器。当然,出于成本考虑,也可以将Switch芯片集成到ARM核心板或底板上,如KSZ9897、
    万象奥科 2024-12-03 10:24 38浏览
  • 概述 说明(三)探讨的是比较器一般带有滞回(Hysteresis)功能,为了解决输入信号转换速率不够的问题。前文还提到,即便使能滞回(Hysteresis)功能,还是无法解决SiPM读出测试系统需要解决的问题。本文在说明(三)的基础上,继续探讨为SiPM读出测试系统寻求合适的模拟脉冲检出方案。前四代SiPM使用的高速比较器指标缺陷 由于前端模拟信号属于典型的指数脉冲,所以下降沿转换速率(Slew Rate)过慢,导致比较器检出出现不必要的问题。尽管比较器可以使能滞回(Hysteresis)模块功
    coyoo 2024-12-03 12:20 70浏览
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 83浏览
  • 当前,智能汽车产业迎来重大变局,随着人工智能、5G、大数据等新一代信息技术的迅猛发展,智能网联汽车正呈现强劲发展势头。11月26日,在2024紫光展锐全球合作伙伴大会汽车电子生态论坛上,紫光展锐与上汽海外出行联合发布搭载紫光展锐A7870的上汽海外MG量产车型,并发布A7710系列UWB数字钥匙解决方案平台,可应用于数字钥匙、活体检测、脚踢雷达、自动泊车等多种智能汽车场景。 联合发布量产车型,推动汽车智能化出海紫光展锐与上汽海外出行达成战略合作,联合发布搭载紫光展锐A7870的量产车型
    紫光展锐 2024-12-03 11:38 65浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 105浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦