在项目开发过程中,难免会遇到需要在线升级的情况,而升级包过大会导致升级时间过长,影响产品性能和用户体验,因此我们可以将需要升级的程序压缩,然后在bootloader中解压。差分升级实际上就是对比出两个升级包的差异,然后再对差异进行压缩。这里我们不讲差分,只讲压缩。

压缩算法有很多,QuickLZ是比较适合单片机的一种轻量级无损压缩算法。QuickLZ是一个号称压缩速度最快的压缩库,以下是几种较流行的压缩库的压缩率和速度对比。虽然QuickLZ的压缩率没有Zlib高,但压缩率相差无几,且解压速度上QuickLZ完胜Zlib,且QuickLZ消耗的资源少(仅需0.5KB的ROM,自身几乎不占RAM),可运行在嵌入式设备上,因此选择QuickLZ作为升级程序包的压缩算法。

Library

Level

Compressed size

Compression Mbyte/s

Decompression Mbyte/s

QuickLZ C 1.5.0

1

47.9%

308

358

QuickLZ C 1.4.0

1

47.9%

272

332

QuickLZ C 1.4.0

2

42.3%

131

309

QuickLZ C 1.4.0

3

40.0%

31

516

QuickLZ C# 1.4.0

1

47.9%

133

132

QuickLZ Java 1.4.0

1

47.9%

127

95

LZF 3.1

UF

54.9%

204

396

LZF 3.1

VF

51.9%

193

384

FastLZ 0.1.0

1

53.0%

173

442

FastLZ 0.1.0

2

50.7%

167

406

LZO 1X 2.02

1

48.3%

169

434

zlib 1.22

1

37.6%

55

234

下面给出一个QuickLZ在单片机升级的应用。我们可以在编译完后使用QuickLz对生成的升级文件进行压缩,升级时传输压缩后的升级文件,然后后在Boot中进行解压验证,最后擦写Flash完成升级。

由于单片机的内存十分有限,在RAM中接收升级包并解压不现实。因此我们将升级包进行分段,以4KB为单位进行分段压缩,bootloader中就可以以4KB为单位进行分段解压。需要注意的是,接收的升级数据都是先存放在FLASH的压缩区域里(定义一块分区专门存放压缩的数据),接收完成后,每次从压缩区域里读取4K压缩数据进行解压,解压完成后将解压后的数据写回到FLASH的解压区域里(定义一块分区专门存放解压后数据),全部解压

压缩过程如下:将升级包进行累加求和,得到2BYTE的校验和,并将该校验和追加到升级包数据尾部得到带求和的升级包,将该升级包数据按4KB分割成一个个PACK,最后不足4KB的部分也当成一个PACK处理,然后对每个PACK进行压缩,得到一个4字节的压缩长度和压缩后的数据,将所有PACK压缩后的这两个数据进行“串联”,得到最终的压缩数据包。

升级包压缩流程

明确了压缩的流程后,解压流程就很简单了,解压过程就是压缩过程的逆向反推。 流程如下图所示。首先进行解压初始化,这一步主要是完成SPI FLASH的初始化,检测升级标志位是否存在,若升级标志位存在,则开始解压流程。首先读取4字节数据,该4字节表示后面紧跟着的压缩数据的长度,根据长度读取压缩数据并进行解压,解压成功则重复上述过程直到解压完整个bin文件。若中途有出现解压错误,那么解压就失败了。解压的过程中也会对解压数据进行累计求和,当解压完成后,比较该累计的求和与解压后的最后两字节数据是否一致,若一致则表示解压成功,否则解压失败。最后去除这两个字节的求和校验,因为这两个字节是压缩时额外添加的,并不是真正的升级数据。

升级包解压流程

附上压缩和解压的部分程序,压缩程序在PC上运行,在程序编译后执行压缩程序,解压程序则是在单片机运行。

//压缩程序#include "quicklz.h"
#include "stdio.h"
#include "stdlib.h"#define RT_NULL                           0
#define BLOCK_HEADER_SIZE              4
#define COMPRESS_BUFFER_SIZE           (4096 + 400)
#define DCOMPRESS_BUFFER_SIZE          4096 #define BUFFER_PADDING                 QLZ_BUFFER_PADDINGtypedef unsigned char  uint8_t;
typedef unsigned short  uint16_t; uint8_t filebuff[256 * 1024];int main(int argc, char * argv[])
{qlz_state_compress *state_compress = RT_NULL;uint8_t *cmprs_buffer = RT_NULL, *buffer = RT_NULL;uint8_t buffer_hdr[BLOCK_HEADER_SIZE] = { 0 };size_t cmprs_size = 0, block_size = 0, totle_cmprs_size = 0;size_t file_size = 0, i = 0;int ret = 0;uint16_t sum = 0;uint8_t sumdata[2];FILE *f_in = NULL, *f_out = NULL;printf("[qlz] QuizkLz Start!\n");f_in = fopen(".\\Tools\\old.bin","ab+");f_out= fopen(".\\Tools\\new.bin", "wb");if (f_in == NULL || f_out == NULL){printf("[qlz] File open err\n");goto _exit;}fseek(f_in, 0L, SEEK_END);file_size = ftell(f_in);fseek(f_in, 0L, SEEK_SET);if (file_size == 0){printf("[qlz] File size = 0\n");goto _exit;}fread(filebuff, 1, file_size, f_in);for (size_t j = 0; j < file_size; j++){sum += filebuff[j];}sumdata[0] = sum >> 8;sumdata[1] = sum &0x00FF;fseek(f_in, 0L, SEEK_END);fwrite(sumdata, 1, 2, f_in);file_size += 2;fseek(f_in, 0L, SEEK_SET);cmprs_buffer = (uint8_t *)malloc(COMPRESS_BUFFER_SIZE + BUFFER_PADDING);buffer = (uint8_t *)malloc(COMPRESS_BUFFER_SIZE);if (!cmprs_buffer || !buffer){printf("[qlz] No memory for cmprs_buffer or buffer!\n");ret = -1;goto _exit;}state_compress = (qlz_state_compress *)malloc(sizeof(qlz_state_compress));if (!state_compress){printf("[qlz] No memory for state_compress struct, need %d byte, or you can change QLZ_HASH_VALUES to 1024 !\n",sizeof(qlz_state_compress));ret = -1;goto _exit;}memset(state_compress, 0x00, sizeof(qlz_state_compress));printf("[qlz] Compress start : ");for (i = 0; i < file_size; i += COMPRESS_BUFFER_SIZE){if ((file_size - i) < COMPRESS_BUFFER_SIZE){block_size = file_size - i;}else{block_size = COMPRESS_BUFFER_SIZE;}memset(buffer, 0x00, COMPRESS_BUFFER_SIZE);memset(cmprs_buffer, 0x00, COMPRESS_BUFFER_SIZE + BUFFER_PADDING);fread( buffer,1, block_size, f_in);/* The destination buffer must be at least size + 400 bytes large because incompressible data may increase in size. */cmprs_size = qlz_compress(buffer, (char *)cmprs_buffer, block_size, state_compress);/* Store compress block size to the block header (4 byte). */buffer_hdr[3] = cmprs_size % (1 << 8);buffer_hdr[2] = (cmprs_size % (1 << 16)) / (1 << 8);buffer_hdr[1] = (cmprs_size % (1 << 24)) / (1 << 16);buffer_hdr[0] = cmprs_size / (1 << 24);fwrite(buffer_hdr, 1,BLOCK_HEADER_SIZE, f_out);fwrite(cmprs_buffer,1, cmprs_size, f_out);totle_cmprs_size += cmprs_size + BLOCK_HEADER_SIZE;printf(">");}printf("\n");printf("[qlz] Compressed %d bytes into %d bytes , compression ratio is %d%!\n", file_size, totle_cmprs_size,(totle_cmprs_size * 100) / file_size);_exit:if (f_in)fclose(f_in);if (f_out)fclose(f_out);if (cmprs_buffer){free(cmprs_buffer);}if (buffer){free(buffer);}if (state_compress){free(state_compress);}if (file_size == 0 || totle_cmprs_size == 0){printf("[qlz] Compressed failed!!!\n");remove("old.bin");remove("new.bin");}printf("[qlz] QuickLz End!\n");return ret;
}
//解压程序
u32 app_QuickLz_Decompress(u32 u32Addr,u32 u32FileSize)
{u16 u16Sum = 0;u8 u8ReadBuffer_hdr[BLOCK_HEADER_SIZE] = { 0 };u32 u32DcmprsSize = 0, u32BlockSize = 0, u32TotalDcmprsSize = 0, u32AddrRead = 0,u32AddrWrite, i = 0,j = 0;if(u32FileSize < DCOMPRESS_BUFFER_SIZE){return 0;}if(sys_SpiFlash_Erase(1,UPDATE_BUFF_DECOMPRESS_ADDR,UPDATE_BUFF_DECODE_SIZE) == RET_ERR)       //先清除解压缓冲区{return 0;}memset(&s_stDecompressState,0,sizeof(s_stDecompressState));u32AddrRead = u32Addr;u32AddrWrite = UPDATE_BUFF_DECOMPRESS_ADDR;for(i = 0; i < u32FileSize; i+= BLOCK_HEADER_SIZE + u32BlockSize)                         //分段解压数据{if(sys_SpiFlash_Read(1,u32AddrRead,u8ReadBuffer_hdr,BLOCK_HEADER_SIZE) == RET_ERR)       {return 0;}u32AddrRead += BLOCK_HEADER_SIZE;u32BlockSize = u8ReadBuffer_hdr[0] * (1 << 24) + u8ReadBuffer_hdr[1] * (1 << 16) + u8ReadBuffer_hdr[2] * (1 << 8) + u8ReadBuffer_hdr[3];    if(!u32BlockSize){return 0;}memset(u8ReadBuffer, 0x00, sizeof(u8ReadBuffer));memset(s_u8DcmprsBuffer, 0x00, DCOMPRESS_BUFFER_SIZE);if(sys_SpiFlash_Read(1,u32AddrRead,u8ReadBuffer,u32BlockSize) == RET_ERR){return 0;}u32AddrRead +=   u32BlockSize;u32DcmprsSize = qlz_decompress((const char *)u8ReadBuffer, s_u8DcmprsBuffer, &s_stDecompressState);if(!u32DcmprsSize){return 0;}for(j = 0; j < u32DcmprsSize; j++){u16Sum += s_u8DcmprsBuffer[j];}if(sys_SpiFlash_Write(1,u32AddrWrite,s_u8DcmprsBuffer,u32DcmprsSize) == RET_ERR){return 0;}u32AddrWrite += u32DcmprsSize;u32TotalDcmprsSize += u32DcmprsSize;app_Iwdg_Refresh();}u16Sum -= (s_u8DcmprsBuffer[u32DcmprsSize-2] + s_u8DcmprsBuffer[u32DcmprsSize-1]);     if(u16Sum != ((s_u8DcmprsBuffer[u32DcmprsSize-2] <<8) + s_u8DcmprsBuffer[u32DcmprsSize-1])){return 0;}DEBUG_MSG(1,"Decompress ok ,file size = %d",u32TotalDcmprsSize - 2);return u32TotalDcmprsSize - 2;                                 //原始数据的最后两字节是求和校验
}

压缩算法在单片机升级中的应用相关推荐

  1. 浅谈单片机程序设计中的“分层思想”!

    浅谈单片机程序设计中的"分层思想",并不是什么神秘的东西,事实上很多做项目的工程师本身自己也会在用.看了不少帖子都发现没有提及这个东西,然而分层结构确是很有用的东西,参透后会有一种 ...

  2. c语言使单片机输出低电平,单片机开发中的一些实用技巧

    很多朋友正在学习单片机开发技术,但开发中免不了要碰到这样.那样的问题,有些问题可能无碍大局,但有一些问题却直接影响到产品的成本.体积.性能.这里介绍笔者的几个技巧,希望对大家的工作有帮助. 一.C语言 ...

  3. 服务器升级中暂不可修改怎么回事,抖音服务器升级中,暂不支持本地区开播抖音怎么在法国直播?...

    抖音服务器升级中,暂不支持本地区开播抖音怎么在法国直播? 除了餐饮,腾讯自主研发的各种跨界开发节目也无人问津.至于年初腾讯app项目和资源,还没有整体发布,不过,神似的行业信息也有公布.这其中对爆红的 ...

  4. 聊聊我们在业务链路升级中做的数据洞察

    简介:关于数据相关的词条很多,虽然有不同的定义,但是本质上是相辅相成,通常结合使用才能拿到结果.类比词条诸如 数据分析,数据挖掘, 数据洞察.本文将聊聊我们在业务链路升级中做的数据洞察. 作者 | 金 ...

  5. 单片机系统中的红外通信接口

    摘要:本文结合复费率电能表中红外通信的设计实从事贸易,介绍了单片机系统中红外通信的软硬件设计方法,并给出了具体的电路原理和通信源程序. 关键词:单片机,红外通信,遥控 在许多基于单片机的应用系统中,系 ...

  6. Android Studio升级中的“未找到默认活动”

    本文翻译自:"Default Activity Not Found" on Android Studio upgrade I upgraded IntelliJ Idea from ...

  7. Keil用C语言定义函数,STC单片机Keil中C语言函数定位的方法

    STC单片机Keil中C语言函数定位的方法:STC单片机Keil中C语言函数定位的方法 下面以演示程序进行说明 演示程序中有ReadIAP.ProgramIAP和EraseIAP三个函数 最终目的是将 ...

  8. 打地鼠java代码流程图_51单片机 普中51 打地鼠游戏 仿真 程序 流程图

    51单片机 普中51 打地鼠游戏 仿真 程序 流程图 51单片机 普中51 打地鼠游戏 仿真 程序 流程图 普中51-单核-A3&A4开发板原理图 用到数码管.LED.矩阵按键 描述: (1) ...

  9. 为什么显示服务器升级中,手机老是显示服务器升级中

    手机老是显示服务器升级中 内容精选 换一换 XEN实例变更为KVM实例前,需要确保Windows弹性云服务器已安装了PV driver和UVP VMTools.本节指导您安装PV driver和UVP ...

最新文章

  1. “拟态防御”: 让黑客找不到破门之机
  2. java清除cookie
  3. java e7 e9格式怎么转_java�?e7?a8??e9?a8�ӿ�
  4. Case Study. Technical and Commercial understating. Internal use only.
  5. [云炬创业管理笔记]第五章打磨最有效的商业模式测试2
  6. Count The Repetitions
  7. alexnet训练多久收敛_卷积神经网络之AlexNet
  8. [转载]Bluetooth协议栈学习之SDP
  9. 100行Python代码理解深度学习关键概念:从头构建恶性肿瘤检测网络
  10. 【华为云技术分享】使用keil5打开GD32F450i的MDK项目出现的问题以及J-Link无法烧录程序对应的解决方案
  11. win10运行YOLOv4+OPENCV+VS2017
  12. HANA SQL基础
  13. 搜索引擎漫谈以及 Zinc 简介
  14. 上海车展6大热门车型自动驾驶配置梳理
  15. Latex各种箭头符号总结
  16. Allegro如何更改铜皮的网络操作指导
  17. MySQL必知必会——实践习题
  18. Hudi-Flink SQL实时读取Hudi表数据
  19. 著名ai换脸网站_AI如何从著名的死去艺术家那里删除新音乐
  20. 准确率,召回率,mAP,ROC,AUC,特异性,敏感性,假阴性,假阳性

热门文章

  1. B1019(数字黑洞)
  2. linux 遍历目录查找文件find太慢,Linux下比find快N倍的文件查找命令
  3. JavaScript空判断
  4. React Parameter ‘from‘ implicitly has an ‘any‘ type.
  5. powerbi输入数据_PowerBI数据编辑与管理
  6. Citrix 服务器虚拟化之二十五 桌面虚拟化之XenDesktop高可用性
  7. 你不知道的javascript读书记录
  8. 面向对象三大特性-多态
  9. XSS Challenges
  10. 曙光实习笔记:第一天