? ? ? ?在项目开发过程中,难免会遇到需要在线升级的情况,而升级包过大会导致升级时间过长,影响产品性能和用户体验,因此我们可以将需要升级的程序压缩,然后在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
#define DCOMPRESS_BUFFER_SIZE 4096
#define BUFFER_PADDING QLZ_BUFFER_PADDING
typedef 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; //原始数据的最后两字节是求和校验
}
|