压缩算法在单片机升级中的应用

       在项目开发过程中,难免会遇到需要在线升级的情况,而升级包过大会导致升级时间过长,影响产品性能和用户体验,因此我们可以将需要升级的程序压缩,然后在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压缩后的这两个数据进行“串联”,得到最终的压缩数据包。

压缩算法在单片机升级中的应用_第1张图片 升级包压缩流程

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

压缩算法在单片机升级中的应用_第2张图片 升级包解压流程

 附上压缩和解压的部分程序,压缩程序在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_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;									//原始数据的最后两字节是求和校验
}

你可能感兴趣的:(STM32,算法,单片机压缩,bootloader解压,STM32压缩,单片机升级)