适用于嵌入式单片机的差分升级通用库+详细教程

news2024/12/23 5:15:04

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文件:

  1#ifdef BSDIFF_UPGRADE
  2        image_header_t recv_head;
  3        uint32_t recv_hcrc;  /* 接收到的文件头CRC */
  4        uint32_t calculation_crc;  /* 计算出来的文件头CRC */
  5        uint32_t spi_flash_addr = UPGRADE_PROGRAM_ADDR;
  6
  7        memcpy(&recv_head, (uint8_t *)APPLICATION_A, sizeof(image_header_t));
  8        recv_hcrc = BigtoLittle32(recv_head.ih_hcrc);
  9        recv_head.ih_hcrc = 0;
 10        calculation_crc = crc32((uint8_t *)&recv_head, sizeof(image_header_t));
 11
 12        if (recv_hcrc == calculation_crc)
 13        {
 14            recv_head.ih_hcrc = recv_hcrc;
 15            recv_head.ih_time = BigtoLittle32(recv_head.ih_time);
 16            recv_head.ih_size = BigtoLittle32(recv_head.ih_size);
 17            recv_head.ih_dcrc = BigtoLittle32(recv_head.ih_dcrc);
 18            recv_head.ih_ocrc = BigtoLittle32(recv_head.ih_ocrc);
 19            /* 差分升级包 */
 20            recv_head.ih_hcrc = calculation_crc;
 21            if (crc32((uint8_t *)APPLICATION_RUN, recv_head.ih_load) != recv_head.ih_ocrc)
 22            {
 23                APP_ERR_PRINT("file oldcrc err,calcrc:0X%08X, ih_oldbin_crc:0X%08X,", 
 24                                          crc32((uint8_t *)APPLICATION_RUN, 
 25                                          recv_head.ih_load), recv_head.ih_ocrc);
 26                goto bsdiff_out;
 27            }
 28            RTOS_LOCK();
 29            disable_task_monitoring(ALL_TASK_RUNFLAG_BIT, true);
 30            // flash_erase_sector(UPGRADE_PROGRAM_ADDR, UPGRADE_PROGRAM_PAGE);
 31            recv_hcrc = iap_patch((uint8_t *)APPLICATION_RUN, recv_head.ih_load, 
 32                                 (uint8_t *)(APPLICATION_A + sizeof(image_header_t)),  
 33                                   recv_head.ih_size, UPGRADE_PROGRAM_ADDR);
 34            if (recv_hcrc != recv_head.ih_ep)
 35            {
 36                APP_ERR_PRINT("iap_patch len err.");
 37                APP_ERR_PRINT("iap_patch len: %lu, new_len: %lu", recv_hcrc, recv_head.ih_ep);
 38                goto bsdiff_out;
 39            }
 40            if (erase_program(APPLICATION_A))
 41            {
 42                APP_ERR_PRINT("I erase program failed.");
 43                goto bsdiff_out;
 44            }
 45
 46            current_flash_write_addr = APPLICATION_A;
 47            for (uint32_t i = 0; i < (recv_head.ih_ep / 1024); i++)
 48            {
 49                xmq25qxx_read(spi_flash_addr, spi_read_cache, 1024);
 50                if (xflash_write(current_flash_write_addr, spi_read_cache, 1024))
 51                {
 52                    APP_ERR_PRINT("I write program failed.");
 53                    goto bsdiff_out;
 54                }
 55                spi_flash_addr += 1024;
 56                current_flash_write_addr += 1024;
 57                APP_PRINT("current_flash_write_addr: 0X%08X", current_flash_write_addr);
 58            }
 59            if (recv_head.ih_ep % 1024 != 0)
 60            {
 61                memset(spi_read_cache, 0XFF, 1024);
 62                xmq25qxx_read(spi_flash_addr, spi_read_cache, recv_head.ih_ep % 1024);
 63
 64                if (xflash_write(current_flash_write_addr, spi_read_cache, 1024))
 65                {
 66                    APP_ERR_PRINT("I write program failed.");
 67                    goto bsdiff_out;
 68                }
 69            }
 70            if (crc32((uint8_t *)APPLICATION_A, recv_head.ih_ep) != recv_head.ih_dcrc)
 71            {
 72                APP_ERR_PRINT("file newcrc err,calcrc:0X%08X, newcrc:0X%08X, len: %lu", 
 73                                         crc32((uint8_t *)APPLICATION_A, recv_head.ih_ep), 
 74                                         recv_head.ih_load, recv_head.ih_dcrc);
 75                goto bsdiff_out;
 76            }
 77            /* 下载成功,开始升级 */
 78            if (check_bin_file((bin_info_t *)(APPLICATION_A + BIN_INFO_OFFSET))) /* bin文件非法 */
 79            {
 80                APP_ERR_PRINT("check_bin_file err.");
 81                goto bsdiff_out;
 82            }
 83            recv_head.ih_dcrc = CRT_CRC16_check(0, (uint8_t *)APPLICATION_A, 
 84                                                recv_head.ih_ep);
 85            readwrite_app_run_bin_info(0, &recv_head.ih_ep, (uint16_t *)&recv_head.ih_dcrc);
 86            /* 整体校验成功,确认升级 */
 87            if (switch_program_stage(STAGE_1))
 88            {
 89                APP_ERR_PRINT("I write switch_program_stage STAGE_0 failed.");  /* 置位升级标志写失败 */
 90                goto bsdiff_out;
 91            }
 92            APP_PRINT("upgrade success.");
 93  bsdiff_out:
 94            SYSTEM_RESET();
 95        }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/66851.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Jekyll如何自定义摘要

最近搭建博客网站的时候遇到一个问题&#xff1a;博客的摘要包含了内容的格式&#xff0c;比如下面这张图。 标题的样式显示在摘要中&#xff0c;这可太奇怪了。我在查找文档之后没有想到合适的解决方案&#xff0c;于是乎就去 Jekyll 的项目下面提了个 Issue 问了一下。 在…

js原生实现步骤条

实现思路: 1.定義一個流程數組和一个步骤状态 2.遍历这个流程数组&#xff0c;如果步骤状态大于流程&#xff0c;checked&#xff1d;true&#xff0c; 3.页面输出遍历的流程数组&#xff0c;checked的div点亮 最终效果 <!DOCTYPE html> <html> <title>js原…

音视频开发:直播推流技术指南

一、推流架构 推流SDK客户端的模块主要有三个&#xff0c;推流采集端、队列控制模块、推流端。其中每个模块的主要流程如下&#xff0c;本文的主要目的就是拆分推流流程。 1.1 采集端 视频采集&#xff1a;通过Camera采集视频。 音频采集&#xff1a;通过麦克风采集音频。 …

HTML做一个节日页面【六一儿童节】纯HTML代码

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

「动态规划学习心得」正则表达式匹配

正则表达式匹配 给你一个字符串 s 和一个字符规律 p&#xff0c;请你来实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。 ‘.’ 匹配任意单个字符‘*’ 匹配零个或多个前面的那一个元素 所谓匹配&#xff0c;是要涵盖 整个 字符串 s的&#xff0c;而不是部分字符串。 输入&…

客户管理系统中的常用术语都有哪些 (下)

CRM客户管理系统概念问世的二十多年来&#xff0c;曾帮助过无数企业打造优质的客户关系&#xff0c;带来显著的业绩增长。为了让您有更好的理解&#xff0c;小编把CRM常用术语进行了汇总&#xff0c;希望能够帮助到正在了解CRM的您。 销售方法及营销理念 LTC (Leads To Cash)…

超细节的javaWeb知识点总结

文章目录Servlet系统架构C/S架构B/S架构C/S和B/S结构的系统&#xff0c;哪个好&#xff0c;哪个不好&#xff1f;JavaEE是什么&#xff1f;B/S结构的系统通信原理&#xff08;没有涉及到Java小程序&#xff09;WEB系统的访问过程关于域名&#xff1a;IP地址是啥&#xff1f;端口…

搜索与图论-树与图的广度优先遍历

文章目录一、树与图的广度优先遍历1. 构建2. 遍历3. 具体实现详见例题——图中点的层次二、树与图的广度优先遍历例题——图中点的层次具体实现&#xff08;一&#xff09;1. 样例演示2. 实现思路3. 代码注解4. 实现代码具体实现&#xff08;二&#xff09;1. 代码注解2. 实现代…

JupyterLab | 这几款插件推荐给天天使用JupyterLab的你!~

1写在前面 最近用了用JupyterLab&#xff0c;总体来说体验还是不错的&#xff0c;代码写完就是一篇完整的Paper了&#xff0c;非常给力。&#x1f973; 不过单纯使用JupyterLab可能还是有一些不尽人意的地方&#xff0c;这些问题基本都可以通过添加插件来弥补&#xff0c;今天就…

内存可见性问题

目录 1.什么是内存可见性问题 2.内存可见性问题是怎么发生的 3.解决方法&#xff1a;volatile 4.volatile使用的注意事项 5.内存可见性问题的延伸 缓存&#xff08;cache&#xff09; 1.什么是内存可见性问题 首先来看一段代码 class Counter{public int flag 0; } pu…

docker部署redis集群 删除节点(缩容)

上篇博文完成了redis集群的搭建&#xff1a;点这里 以及redis集群的添加节点 即扩容&#xff1a;点这里 本篇博文写一下怎样在redis集群中删除节点&#xff08;还是在之前博文的基础上&#xff09;&#xff0c;博文中的111.111.111.111均换成实际IP使用 删除从节点 我这里想…

大数据学习:进程管理

文章目录一、进程ID&#xff08;PID&#xff09;二、查看进程1、进程查看命令-ps&#xff08;1&#xff09;命令作用&#xff08;2&#xff09;参数说明&#xff08;3&#xff09;操作案例2、Linux进程状态3、观察进程变化命令 -top&#xff08;1&#xff09;参数选项&#xff…

预训练模型之ELMO -《Deep contextualized word representations》论文笔记 + 高频面试题

&#x1f604; 无聊学学罢了&#xff0c;非常简单的一个模型吧&#xff0c;算是一个比较经典的模型。ELMO更多的像是一个承上启下的角色&#xff0c;对于我们去了解那些词向量模型的思想也是很有帮助的。但由于同期的BERT等模型过于耀眼&#xff0c;使得大家并不太了解ELMO。 &…

微服务Sentinel流控难题:QPS模式与线程数模式区别

问题引入 不少新学Sentinel的小伙伴在配置Sentinel流控规则时犯迷糊&#xff0c;如下图&#xff1a; 其中迷糊点是阈值类型这里&#xff1a; QPS&#xff1a;当调用该接口的QPS达到阈值的时候&#xff0c;进行限流 线程数&#xff1a;当调用该接口的线程数达到阈值的时候&am…

Java 面试题 (二) -------- Java 集合相关

1、Java Bean 的命名规范 JavaBean 类必须是一个公共类&#xff0c;并将其访问属性设置为 public JavaBean 类必须有一个空的构造函数&#xff1a;类中必须有一个不带参数的公用构造器&#xff0c;此构造器也应该通过调用各个特性的设置方法来设置特性的缺省值。 一个 JavaB…

【云原生·k8s】k8s集群安装部署

带着理论&#xff0c;再去部署&#xff0c;验证你的理论 文章目录1、环境准备2、环境初始化3、防火墙初始化3、关闭swap4、yum源配置5、ntp配置6、修改linux内核参数&#xff0c;开启数据包转发功能7、安装docker基础环境&#xff08;&#xff09;8、安装k8s的初始化工具kubead…

互联网舆情监控分析

近年来&#xff0c;互联网的快速发展&#xff0c;不论是新闻中、报纸上&#xff0c;还是电视里&#xff0c;都能屡屡看到一些企业被负面缠身&#xff0c;进而损害企业效益&#xff0c;在人人都是自媒体的时代&#xff0c;并非只有重大事件才会引发舆情&#xff0c;小事情也会&a…

kubernetes介绍和安装(1.25版本)

kubernetes介绍和安装&#xff08;1.25版本&#xff09; K8S 是什么&#xff1f; K8S官网文档&#xff1a;https://kubernetes.io/zh/docs/home/ K8S 是Kubernetes的全称&#xff0c;源于希腊语&#xff0c;意为“舵手”或“飞行员”&#xff0c;基于go语言开发&#xff0c;官…

liteos启动流程

一,启动流程 从这里开始我们开始讲解liteos的启动过程,通过前面连接器脚本的分析,我们已经对程序启动阶段期望的内存布局有了一个宏观的认识,然后系统上电从0x08000000地址boot起来之后要做的就是生成这个布局,然后初始化时钟,内存,任务,锁信号量等等基础的系统管理单…

leetcode 332. 重新安排行程

题目描述&#xff1a; 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票都属于一个从 JFK&#xff08;肯尼迪国际机场&#xff09;出发的先生&#xff0c;所以该行程必须从 …