压缩算法在单片机升级中的应用
在项目开发过程中,难免会遇到需要在线升级的情况,而升级包过大会导致升级时间过长,影响产品性能和用户体验,因此我们可以将需要升级的程序压缩,然后在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; //原始数据的最后两字节是求和校验
}
压缩算法在单片机升级中的应用相关推荐
- 浅谈单片机程序设计中的“分层思想”!
浅谈单片机程序设计中的"分层思想",并不是什么神秘的东西,事实上很多做项目的工程师本身自己也会在用.看了不少帖子都发现没有提及这个东西,然而分层结构确是很有用的东西,参透后会有一种 ...
- c语言使单片机输出低电平,单片机开发中的一些实用技巧
很多朋友正在学习单片机开发技术,但开发中免不了要碰到这样.那样的问题,有些问题可能无碍大局,但有一些问题却直接影响到产品的成本.体积.性能.这里介绍笔者的几个技巧,希望对大家的工作有帮助. 一.C语言 ...
- 服务器升级中暂不可修改怎么回事,抖音服务器升级中,暂不支持本地区开播抖音怎么在法国直播?...
抖音服务器升级中,暂不支持本地区开播抖音怎么在法国直播? 除了餐饮,腾讯自主研发的各种跨界开发节目也无人问津.至于年初腾讯app项目和资源,还没有整体发布,不过,神似的行业信息也有公布.这其中对爆红的 ...
- 聊聊我们在业务链路升级中做的数据洞察
简介:关于数据相关的词条很多,虽然有不同的定义,但是本质上是相辅相成,通常结合使用才能拿到结果.类比词条诸如 数据分析,数据挖掘, 数据洞察.本文将聊聊我们在业务链路升级中做的数据洞察. 作者 | 金 ...
- 单片机系统中的红外通信接口
摘要:本文结合复费率电能表中红外通信的设计实从事贸易,介绍了单片机系统中红外通信的软硬件设计方法,并给出了具体的电路原理和通信源程序. 关键词:单片机,红外通信,遥控 在许多基于单片机的应用系统中,系 ...
- Android Studio升级中的“未找到默认活动”
本文翻译自:"Default Activity Not Found" on Android Studio upgrade I upgraded IntelliJ Idea from ...
- Keil用C语言定义函数,STC单片机Keil中C语言函数定位的方法
STC单片机Keil中C语言函数定位的方法:STC单片机Keil中C语言函数定位的方法 下面以演示程序进行说明 演示程序中有ReadIAP.ProgramIAP和EraseIAP三个函数 最终目的是将 ...
- 打地鼠java代码流程图_51单片机 普中51 打地鼠游戏 仿真 程序 流程图
51单片机 普中51 打地鼠游戏 仿真 程序 流程图 51单片机 普中51 打地鼠游戏 仿真 程序 流程图 普中51-单核-A3&A4开发板原理图 用到数码管.LED.矩阵按键 描述: (1) ...
- 为什么显示服务器升级中,手机老是显示服务器升级中
手机老是显示服务器升级中 内容精选 换一换 XEN实例变更为KVM实例前,需要确保Windows弹性云服务器已安装了PV driver和UVP VMTools.本节指导您安装PV driver和UVP ...
最新文章
- “拟态防御”: 让黑客找不到破门之机
- java清除cookie
- java e7 e9格式怎么转_java�?e7?a8??e9?a8�ӿ�
- Case Study. Technical and Commercial understating. Internal use only.
- [云炬创业管理笔记]第五章打磨最有效的商业模式测试2
- Count The Repetitions
- alexnet训练多久收敛_卷积神经网络之AlexNet
- [转载]Bluetooth协议栈学习之SDP
- 100行Python代码理解深度学习关键概念:从头构建恶性肿瘤检测网络
- 【华为云技术分享】使用keil5打开GD32F450i的MDK项目出现的问题以及J-Link无法烧录程序对应的解决方案
- win10运行YOLOv4+OPENCV+VS2017
- HANA SQL基础
- 搜索引擎漫谈以及 Zinc 简介
- 上海车展6大热门车型自动驾驶配置梳理
- Latex各种箭头符号总结
- Allegro如何更改铜皮的网络操作指导
- MySQL必知必会——实践习题
- Hudi-Flink SQL实时读取Hudi表数据
- 著名ai换脸网站_AI如何从著名的死去艺术家那里删除新音乐
- 准确率,召回率,mAP,ROC,AUC,特异性,敏感性,假阴性,假阳性
热门文章
- B1019(数字黑洞)
- linux 遍历目录查找文件find太慢,Linux下比find快N倍的文件查找命令
- JavaScript空判断
- React Parameter ‘from‘ implicitly has an ‘any‘ type.
- powerbi输入数据_PowerBI数据编辑与管理
- Citrix 服务器虚拟化之二十五 桌面虚拟化之XenDesktop高可用性
- 你不知道的javascript读书记录
- 面向对象三大特性-多态
- XSS Challenges
- 曙光实习笔记:第一天