保存图形到EEPROM,通过过滤器重复0x00和0xFF压缩图形保存space
Saving graphic to EEPROM, compressing graphic by filter repeating 0x00 and 0xFF to save space
作为固件的一部分,我想将一个或多个图形保存到MCU的EEPROM中。 space不多,1K,不过可以存点程序space。是的,你可以分离图形的字形来保存 space 但是它不容易管理,你需要更多代码才能正确显示它。
大多数单色 GUI 图形不会完全填满屏幕,并且包含分配的空白 space 或重复像素。图像已经被压缩,一个字节中的每一位代表 8 个像素。
我在 128x32 像素的微型显示器上显示图形。显示它并删除不相关的部分,工作得非常好和高效。
所以我想出了过滤这些重复的想法,将其压缩一点。成功后,像这样的位图(见下文)为 496 字节,'compressed' 使用我的方法少了 401 字节。
这听起来并不多,但是总大小减少了 20%,这在只有 1K 存储空间可用时非常好。
字节数组示例:
PROGMEM const uint8_t TEP_DISPLAY [] = { /* 496 bytes */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00,
0x06, 0x00, 0x80, 0x90, 0x00, 0x3E, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x80, 0x00, 0x47, 0x0F, 0xFE,
0x17, 0x01, 0xC0, 0x90, 0x00, 0x30, 0x00, 0x00, 0x03, 0x60, 0x01, 0x80, 0x01, 0x87, 0x10, 0x02,
0x30, 0x83, 0xE3, 0xFC, 0x00, 0x61, 0xE7, 0x39, 0xB6, 0x6F, 0x0F, 0x00, 0x03, 0x07, 0x36, 0xDA,
0x7F, 0xF0, 0x83, 0xFC, 0x7C, 0x7D, 0xB3, 0x6D, 0xB6, 0x61, 0x9B, 0x1F, 0x03, 0x87, 0x36, 0xDA,
0x30, 0x43, 0xE1, 0xF8, 0x00, 0x61, 0xB3, 0x6D, 0xA7, 0xCF, 0xB3, 0x00, 0x01, 0x80, 0x36, 0xDA,
0x13, 0x81, 0xC0, 0x60, 0x00, 0xC3, 0x66, 0x6D, 0xCC, 0x1B, 0x36, 0x00, 0x01, 0x07, 0x10, 0x02,
0x03, 0x00, 0x80, 0x60, 0x00, 0xFB, 0x66, 0x39, 0x8C, 0x0F, 0x1E, 0x00, 0x02, 0x07, 0x0F, 0xFE,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA2, 0xD5, 0x54,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x02,
0x00, 0xC0, 0x22, 0x00, 0x08, 0x00, 0x02, 0x20, 0x00, 0x82, 0x48, 0x20, 0x00, 0x08, 0x00, 0x00,
0x40, 0xC0, 0x01, 0xE0, 0x00, 0x01, 0xC0, 0x1E, 0x00, 0x01, 0x50, 0x00, 0xFE, 0x00, 0x0C, 0x02,
0x00, 0xC0, 0x20, 0x10, 0x08, 0x07, 0xC2, 0x01, 0x00, 0x80, 0x00, 0x21, 0x01, 0x08, 0x0E, 0x00,
0x4F, 0xFC, 0x00, 0xFE, 0x00, 0x0F, 0x40, 0x3F, 0xF8, 0x03, 0xF8, 0x03, 0x01, 0x80, 0x0B, 0x02,
0x1C, 0xC2, 0x21, 0x11, 0x08, 0x1C, 0x42, 0x40, 0x04, 0x84, 0x04, 0x21, 0x11, 0x08, 0x69, 0x80,
0x59, 0xE2, 0x01, 0x11, 0x00, 0x18, 0x40, 0x55, 0x54, 0x05, 0x54, 0x03, 0x39, 0x80, 0x3B, 0x02,
0x12, 0xD2, 0x21, 0x11, 0x08, 0x10, 0x42, 0x40, 0x04, 0x84, 0x04, 0x21, 0x7D, 0x08, 0x1E, 0x00,
0x54, 0xCA, 0x01, 0x83, 0x00, 0x10, 0x40, 0x55, 0x54, 0x05, 0x54, 0x03, 0x11, 0x80, 0x3E, 0x02,
0x12, 0x12, 0x21, 0x01, 0x08, 0x11, 0xC2, 0x40, 0x04, 0x84, 0x04, 0x21, 0x11, 0x08, 0x6B, 0x00,
0x51, 0xE2, 0x01, 0x01, 0x00, 0x13, 0xC0, 0x47, 0xC4, 0x04, 0x44, 0x01, 0x11, 0x00, 0x09, 0x82,
0x10, 0x02, 0x21, 0x01, 0x08, 0x71, 0x82, 0x40, 0x04, 0x84, 0x04, 0x23, 0x01, 0x88, 0x0B, 0x00,
0x4F, 0xFC, 0x01, 0xFF, 0x00, 0xF0, 0x00, 0x3F, 0xF8, 0x05, 0x54, 0x01, 0x01, 0x00, 0x0E, 0x02,
0x0F, 0xFC, 0x20, 0xFE, 0x08, 0x60, 0x02, 0x1F, 0xF0, 0x84, 0x04, 0x20, 0xFE, 0x08, 0x0C, 0x00,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02
};
还有一个问题,我认为这是代码中的一个小错误,我无法检测到它(因为花了几天时间考虑如何让它更小)。也许有人可以指出我解决问题的正确方向。
问题
当有很多相似之处,相同的重复超过 255 次,例如许多 0xFF 重复行或 0x00 重复空 space 时,就会出现问题。在我的代码中,我采取了一些预防措施来避免字节溢出,但它失败了(现在无法弄清楚原因)。我尝试做的是当出现溢出时,记录下来并重新开始计数。我不知道这是读取函数的问题还是写入函数的问题。
这是存储布局
At start address:
-----------------
<byte width>
<byte heigth>
<uint16 dataSize>
<data>
<if data=0xFF>
<0xFF>
<repeat count>
</if>
<if data=0x00>
<0x00>
<repeat count>
</if>
<else data>
</data>
这是我的代码:
uint16_t TOLEDdisplay::writeToEeprom( uint16_t iAddress )
{
if( width == 0 || height == 0 || cacheSize == 0 )
{ return 0; }
uint8_t iZeros = 0;
uint8_t iFFs = 0;
bool bIsZero = false;
bool bIsFF = false;
bool bZeroOverflow = false;
bool bFFOverflow = false;
uint16_t iBits = 0;
uint8_t* pByteSize = (uint8_t*)&iBits;
uint8_t iZeroCount = 0; // empty stripes , same pixels in a row
uint8_t iFFCount = 0; // filled stripes, same pixels in a row
// Write screen bounds, when read it back with readFromEeprom,
// this bounds must match with the current screen bounds.
EEPROM.write( iAddress++, width );
EEPROM.write( iAddress++, height );
// Reserve two bytes for stream size
uint16_t iSizeAddress = iAddress++;
++iAddress;
// Write the cache content to the EEPROM
uint16_t i = 0;
while( i < cacheSize )
{
iBits = getCacheRawBits( i );
//iBits = displayCache[ i ];
bIsFF = ( iBits == 0xFF );
bIsZero = ( iBits == 0x00 );
if( bIsFF && !bFFOverflow )
{ ++iFFs; }
bFFOverflow = (iFFs == 0xFF);
if( bIsZero && !bZeroOverflow )
{ ++iZeros; }
bZeroOverflow = (iZeros == 0xFF);
if( (!bIsFF && !bIsZero) || bFFOverflow || bZeroOverflow )
{
if( (!bIsFF && iFFs > 0) || bFFOverflow )
{
// Read function knows if there is a 0xFF, amount of 0xFF
// will be follow.
EEPROM.write( iAddress++, 0xFF );
// Write the amount of FF's
EEPROM.write( iAddress++, iFFs );
iFFCount+=iFFs;
// If there is no byte 'overflow' iFFs = 0, otherwise it is 1
iFFs = (uint8_t)bIsFF;
}
if( (!bIsZero && iZeros > 0) || bZeroOverflow )
{
// Read function knows if there is a zero, amount of zeros
// will be follow.
EEPROM.write( iAddress++, 0 );
// Write the amount of zero's
EEPROM.write( iAddress++, iZeros );
iZeroCount+=iZeros;
// If there is no byte 'overflow' iZeros = 0, otherwise it is 1
iZeros = (uint8_t)bIsZero;
}
// Avoid confusion writing a FF or zero
if( !bIsFF && !bIsZero )
{ EEPROM.write( iAddress++, iBits ); }
}
++i;
}
// Calculate stream size
iBits=iAddress-iSizeAddress-1;
// Write size of stream
EEPROM.write( iSizeAddress , *pByteSize++ );
EEPROM.write( iSizeAddress+1, *pByteSize );
Serial.print( "Zeros found: " );
Serial.println( iZeroCount );
Serial.print( "FF found: " );
Serial.println( iFFCount );
Serial.print( "SIZE: " );
Serial.println( iBits );
// return bytes written
return iBits+2;
}
bool TOLEDdisplay::readFromEeprom( uint16_t iAddress )
{
uint8_t bits = 0;
uint16_t i = 0;
uint8_t* pI = (uint8_t*)&i;
uint8_t iZeros = 0;
uint8_t iFFs = 0;
uint8_t iWidth = EEPROM.read( iAddress++ );
uint8_t iHeight = EEPROM.read( iAddress++ );
// Read stream size, read two bytes
*pI = EEPROM.read( iAddress++ );
*pI++;
*pI = EEPROM.read( iAddress++ );
// Clear the screen
clear();
Serial.print( "Size: " );
Serial.println( i );
Serial.print( "Width: " );
Serial.println( iWidth );
Serial.print( "Height: " );
Serial.println( iHeight );
// If an error (no image on EEPROM address) or screen bounds
// doesn't match, skip to continue
if( i == 0 || iWidth != width || iHeight != height )
{
update( true );
return false;
}
uint16_t iCacheAddress = 0;
while( i-- )
{
do {
if( iFFs == 0 && iZeros == 0 )
{
bits = EEPROM.read( iAddress++ );
if( bits == 0xFF )
{
// read amount of FF's minus this one
iFFs = EEPROM.read( iAddress++ )-1;
Serial.print( "iFFs: ");
Serial.println( iFFs );
}
else if( bits == 0x00 )
{
// read amount of zeros minus this one
iZeros = EEPROM.read( iAddress++ )-1;
Serial.print( "iZeros: ");
Serial.println( iZeros );
}
}
else {
if( iFFs > 0 )
{
--iFFs;
bits = 0xFF;
}
else if( iZeros > 0 )
{
--iZeros;
bits = 0x00;
}
}
setCacheRawBits( iCacheAddress, bits );
++iCacheAddress;
}
while( iFFs == 0 && iZeros == 0 );
}
update( true );
return true;
}
有什么想法吗?
注意:
我不想使用任何昂贵的压缩方法,96% 的程序 space 已经在使用中,我的方法似乎工作正常但有一些错误,我需要知道错误,没有替代压缩方法。它已经进行了一些压缩,一个字节中的位代表 8 个像素,只是想将其缩小一点(但已证明在字节溢出时出错)。
第一次通过循环时,bFFOverflow
和 bZeroOverflow
未经初始化就被访问。
但主要问题是,在记录 255 个 0 或 0xFF 字节后,如果还有更多字节,则将计数设置为 1。但是,这应该为零,因为您在计算该字节的第 255 个副本后检测到溢出。
所以总是将 bFFOverflow
和 bZeroOverflow
设置为 0 来写出计数。
睡了一觉后,我重做了,结果更好,代码更少,我把这样的事情复杂化了太多。
我用它得到了令人印象深刻的结果,并考虑用一种检查方法对其进行改进,以通过选择重复次数最多的字节并将其记录到 EEPROM 'file' 中找到最佳 'compression'。
无论如何,这是我的代码,与第一个相比好多了,也许它可以帮助别人。这是节省一些字节的非常轻量级的解决方案。
例如,分辨率为 128x32 像素的空白屏幕或全屏仅产生 9 个字节,对半仅产生 17 个字节。我在之前的问题中显示的屏幕'compiles'只有405字节,节省了大约100字节。
这是我更新的代码:
uint8_t TOLEDdisplay::getCacheRawBits( uint16_t iAddress )
{
if( iAddress < cacheSize )
{ return displayCache[ iAddress ]; }
return 0x00;
}
bool TOLEDdisplay::setCacheRawBits( uint16_t iAddress, uint8_t iBitByte )
{
if( iAddress < cacheSize )
{
displayCache[ iAddress ]=iBitByte;
return true;
}
return false;
}
uint16_t TOLEDdisplay::writeToEeprom( uint16_t iAddress )
{
if( cacheSize == 0 || width == 0 || height == 0 )
{ return 0; }
uint8_t iBits; // Pixel * 8 = byte
uint8_t iFoundBits; // 'Type' of detected
uint16_t iByteSize = 0; // Total byte size of stream
uint8_t iCount = 0; // Count of repeats found
bool bSame; // Boolean to evaluate repeats
// Write screen bounds, when read it back with readFromEeprom,
// these bounds need to match with current screen bounds.
EEPROM.write( iAddress++, width );
EEPROM.write( iAddress++, height );
// Reserve two bytes for stream size
uint16_t iSizeAddress = iAddress;
iAddress+=2;
// Write the cache content to the EEPROM
uint16_t i = 0;
while( i < cacheSize )
{
// Get a byte with bits
iBits = getCacheRawBits( i );
++i;
// Find repeating lines or empty lines
if( iBits == 0xFF || iBits == 0x00 )
{
iFoundBits = iBits; // Set found bits to detect changes
bSame = true; // Set to true to able to start loop
iCount=1; // Count this found one
// Loop to find duplicates
while( bSame && ( iCount < 0xFF ) && ( i < cacheSize ))
{
iBits = getCacheRawBits( i ); // Get next byte with bits
bSame = (iBits == iFoundBits); // Determine is repeat, the same
iCount+=bSame; // Increment count when same is found
i+=bSame;
}
// Finally write result to EEPROM
EEPROM.write( iAddress++, iFoundBits ); // type
// Write the amount
EEPROM.write( iAddress++, iCount ); // count
// Goto main loop and find next if any
}
else {
// Write found value normally to EEPROM
EEPROM.write( iAddress++, iBits );
}
}
// Final EOF address is one pos back
--iAddress;
// Calculate stream size
iByteSize=iAddress-iSizeAddress;
uint8_t* pByteSize = (uint8_t*)&iByteSize;
// Write size of stream
EEPROM.write( iSizeAddress , *pByteSize++ );
EEPROM.write( iSizeAddress+1, *pByteSize );
// return bytes written including width and height bytes (+2 bytes)
return iByteSize+2;
}
bool TOLEDdisplay::readFromEeprom( uint16_t iAddress )
{
uint8_t iBits;
uint8_t iRepeats;
uint16_t i = 0;
uint8_t* pI = (uint8_t*)&i;
uint8_t iWidth = EEPROM.read( iAddress++ );
uint8_t iHeight = EEPROM.read( iAddress++ );
// Read stream size, read two bytes
*pI = EEPROM.read( iAddress++ );
*pI++;
*pI = EEPROM.read( iAddress++ );
// Clear the screen
clear();
// If an error (no image on EEPROM address) or screen bounds
// doesn't match, skip to continue
if( i == 0 || iWidth != width || iHeight != height )
{
update( true ); // Set screen to blank
return false;
}
uint16_t iCacheAddress = 0;
while( i-- )
{
// Get a byte with bits
iBits = EEPROM.read( iAddress++ );
// Explode repeats if detected
if( iBits == 0xFF || iBits == 0x00 )
{
// read amount of repeats
iRepeats = EEPROM.read( iAddress++ );
// Explode it into cache
while( iRepeats-- )
{ setCacheRawBits( iCacheAddress++, iBits ); }
}
else {
// Put value normally into cache
setCacheRawBits( iCacheAddress++, iBits );
}
}
// Done, update the screen
update( true );
// Return success
return true;
}
也许我必须添加一些 EEPROM 边界检查,但现在它工作正常。
作为固件的一部分,我想将一个或多个图形保存到MCU的EEPROM中。 space不多,1K,不过可以存点程序space。是的,你可以分离图形的字形来保存 space 但是它不容易管理,你需要更多代码才能正确显示它。
大多数单色 GUI 图形不会完全填满屏幕,并且包含分配的空白 space 或重复像素。图像已经被压缩,一个字节中的每一位代表 8 个像素。
我在 128x32 像素的微型显示器上显示图形。显示它并删除不相关的部分,工作得非常好和高效。
所以我想出了过滤这些重复的想法,将其压缩一点。成功后,像这样的位图(见下文)为 496 字节,'compressed' 使用我的方法少了 401 字节。
这听起来并不多,但是总大小减少了 20%,这在只有 1K 存储空间可用时非常好。
字节数组示例:
PROGMEM const uint8_t TEP_DISPLAY [] = { /* 496 bytes */
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00,
0x06, 0x00, 0x80, 0x90, 0x00, 0x3E, 0x01, 0x80, 0x03, 0xC0, 0x01, 0x80, 0x00, 0x47, 0x0F, 0xFE,
0x17, 0x01, 0xC0, 0x90, 0x00, 0x30, 0x00, 0x00, 0x03, 0x60, 0x01, 0x80, 0x01, 0x87, 0x10, 0x02,
0x30, 0x83, 0xE3, 0xFC, 0x00, 0x61, 0xE7, 0x39, 0xB6, 0x6F, 0x0F, 0x00, 0x03, 0x07, 0x36, 0xDA,
0x7F, 0xF0, 0x83, 0xFC, 0x7C, 0x7D, 0xB3, 0x6D, 0xB6, 0x61, 0x9B, 0x1F, 0x03, 0x87, 0x36, 0xDA,
0x30, 0x43, 0xE1, 0xF8, 0x00, 0x61, 0xB3, 0x6D, 0xA7, 0xCF, 0xB3, 0x00, 0x01, 0x80, 0x36, 0xDA,
0x13, 0x81, 0xC0, 0x60, 0x00, 0xC3, 0x66, 0x6D, 0xCC, 0x1B, 0x36, 0x00, 0x01, 0x07, 0x10, 0x02,
0x03, 0x00, 0x80, 0x60, 0x00, 0xFB, 0x66, 0x39, 0x8C, 0x0F, 0x1E, 0x00, 0x02, 0x07, 0x0F, 0xFE,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x2A, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xA2, 0xD5, 0x54,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x02,
0x00, 0xC0, 0x22, 0x00, 0x08, 0x00, 0x02, 0x20, 0x00, 0x82, 0x48, 0x20, 0x00, 0x08, 0x00, 0x00,
0x40, 0xC0, 0x01, 0xE0, 0x00, 0x01, 0xC0, 0x1E, 0x00, 0x01, 0x50, 0x00, 0xFE, 0x00, 0x0C, 0x02,
0x00, 0xC0, 0x20, 0x10, 0x08, 0x07, 0xC2, 0x01, 0x00, 0x80, 0x00, 0x21, 0x01, 0x08, 0x0E, 0x00,
0x4F, 0xFC, 0x00, 0xFE, 0x00, 0x0F, 0x40, 0x3F, 0xF8, 0x03, 0xF8, 0x03, 0x01, 0x80, 0x0B, 0x02,
0x1C, 0xC2, 0x21, 0x11, 0x08, 0x1C, 0x42, 0x40, 0x04, 0x84, 0x04, 0x21, 0x11, 0x08, 0x69, 0x80,
0x59, 0xE2, 0x01, 0x11, 0x00, 0x18, 0x40, 0x55, 0x54, 0x05, 0x54, 0x03, 0x39, 0x80, 0x3B, 0x02,
0x12, 0xD2, 0x21, 0x11, 0x08, 0x10, 0x42, 0x40, 0x04, 0x84, 0x04, 0x21, 0x7D, 0x08, 0x1E, 0x00,
0x54, 0xCA, 0x01, 0x83, 0x00, 0x10, 0x40, 0x55, 0x54, 0x05, 0x54, 0x03, 0x11, 0x80, 0x3E, 0x02,
0x12, 0x12, 0x21, 0x01, 0x08, 0x11, 0xC2, 0x40, 0x04, 0x84, 0x04, 0x21, 0x11, 0x08, 0x6B, 0x00,
0x51, 0xE2, 0x01, 0x01, 0x00, 0x13, 0xC0, 0x47, 0xC4, 0x04, 0x44, 0x01, 0x11, 0x00, 0x09, 0x82,
0x10, 0x02, 0x21, 0x01, 0x08, 0x71, 0x82, 0x40, 0x04, 0x84, 0x04, 0x23, 0x01, 0x88, 0x0B, 0x00,
0x4F, 0xFC, 0x01, 0xFF, 0x00, 0xF0, 0x00, 0x3F, 0xF8, 0x05, 0x54, 0x01, 0x01, 0x00, 0x0E, 0x02,
0x0F, 0xFC, 0x20, 0xFE, 0x08, 0x60, 0x02, 0x1F, 0xF0, 0x84, 0x04, 0x20, 0xFE, 0x08, 0x0C, 0x00,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02
};
还有一个问题,我认为这是代码中的一个小错误,我无法检测到它(因为花了几天时间考虑如何让它更小)。也许有人可以指出我解决问题的正确方向。
问题
当有很多相似之处,相同的重复超过 255 次,例如许多 0xFF 重复行或 0x00 重复空 space 时,就会出现问题。在我的代码中,我采取了一些预防措施来避免字节溢出,但它失败了(现在无法弄清楚原因)。我尝试做的是当出现溢出时,记录下来并重新开始计数。我不知道这是读取函数的问题还是写入函数的问题。
这是存储布局
At start address:
-----------------
<byte width>
<byte heigth>
<uint16 dataSize>
<data>
<if data=0xFF>
<0xFF>
<repeat count>
</if>
<if data=0x00>
<0x00>
<repeat count>
</if>
<else data>
</data>
这是我的代码:
uint16_t TOLEDdisplay::writeToEeprom( uint16_t iAddress )
{
if( width == 0 || height == 0 || cacheSize == 0 )
{ return 0; }
uint8_t iZeros = 0;
uint8_t iFFs = 0;
bool bIsZero = false;
bool bIsFF = false;
bool bZeroOverflow = false;
bool bFFOverflow = false;
uint16_t iBits = 0;
uint8_t* pByteSize = (uint8_t*)&iBits;
uint8_t iZeroCount = 0; // empty stripes , same pixels in a row
uint8_t iFFCount = 0; // filled stripes, same pixels in a row
// Write screen bounds, when read it back with readFromEeprom,
// this bounds must match with the current screen bounds.
EEPROM.write( iAddress++, width );
EEPROM.write( iAddress++, height );
// Reserve two bytes for stream size
uint16_t iSizeAddress = iAddress++;
++iAddress;
// Write the cache content to the EEPROM
uint16_t i = 0;
while( i < cacheSize )
{
iBits = getCacheRawBits( i );
//iBits = displayCache[ i ];
bIsFF = ( iBits == 0xFF );
bIsZero = ( iBits == 0x00 );
if( bIsFF && !bFFOverflow )
{ ++iFFs; }
bFFOverflow = (iFFs == 0xFF);
if( bIsZero && !bZeroOverflow )
{ ++iZeros; }
bZeroOverflow = (iZeros == 0xFF);
if( (!bIsFF && !bIsZero) || bFFOverflow || bZeroOverflow )
{
if( (!bIsFF && iFFs > 0) || bFFOverflow )
{
// Read function knows if there is a 0xFF, amount of 0xFF
// will be follow.
EEPROM.write( iAddress++, 0xFF );
// Write the amount of FF's
EEPROM.write( iAddress++, iFFs );
iFFCount+=iFFs;
// If there is no byte 'overflow' iFFs = 0, otherwise it is 1
iFFs = (uint8_t)bIsFF;
}
if( (!bIsZero && iZeros > 0) || bZeroOverflow )
{
// Read function knows if there is a zero, amount of zeros
// will be follow.
EEPROM.write( iAddress++, 0 );
// Write the amount of zero's
EEPROM.write( iAddress++, iZeros );
iZeroCount+=iZeros;
// If there is no byte 'overflow' iZeros = 0, otherwise it is 1
iZeros = (uint8_t)bIsZero;
}
// Avoid confusion writing a FF or zero
if( !bIsFF && !bIsZero )
{ EEPROM.write( iAddress++, iBits ); }
}
++i;
}
// Calculate stream size
iBits=iAddress-iSizeAddress-1;
// Write size of stream
EEPROM.write( iSizeAddress , *pByteSize++ );
EEPROM.write( iSizeAddress+1, *pByteSize );
Serial.print( "Zeros found: " );
Serial.println( iZeroCount );
Serial.print( "FF found: " );
Serial.println( iFFCount );
Serial.print( "SIZE: " );
Serial.println( iBits );
// return bytes written
return iBits+2;
}
bool TOLEDdisplay::readFromEeprom( uint16_t iAddress )
{
uint8_t bits = 0;
uint16_t i = 0;
uint8_t* pI = (uint8_t*)&i;
uint8_t iZeros = 0;
uint8_t iFFs = 0;
uint8_t iWidth = EEPROM.read( iAddress++ );
uint8_t iHeight = EEPROM.read( iAddress++ );
// Read stream size, read two bytes
*pI = EEPROM.read( iAddress++ );
*pI++;
*pI = EEPROM.read( iAddress++ );
// Clear the screen
clear();
Serial.print( "Size: " );
Serial.println( i );
Serial.print( "Width: " );
Serial.println( iWidth );
Serial.print( "Height: " );
Serial.println( iHeight );
// If an error (no image on EEPROM address) or screen bounds
// doesn't match, skip to continue
if( i == 0 || iWidth != width || iHeight != height )
{
update( true );
return false;
}
uint16_t iCacheAddress = 0;
while( i-- )
{
do {
if( iFFs == 0 && iZeros == 0 )
{
bits = EEPROM.read( iAddress++ );
if( bits == 0xFF )
{
// read amount of FF's minus this one
iFFs = EEPROM.read( iAddress++ )-1;
Serial.print( "iFFs: ");
Serial.println( iFFs );
}
else if( bits == 0x00 )
{
// read amount of zeros minus this one
iZeros = EEPROM.read( iAddress++ )-1;
Serial.print( "iZeros: ");
Serial.println( iZeros );
}
}
else {
if( iFFs > 0 )
{
--iFFs;
bits = 0xFF;
}
else if( iZeros > 0 )
{
--iZeros;
bits = 0x00;
}
}
setCacheRawBits( iCacheAddress, bits );
++iCacheAddress;
}
while( iFFs == 0 && iZeros == 0 );
}
update( true );
return true;
}
有什么想法吗?
注意:
我不想使用任何昂贵的压缩方法,96% 的程序 space 已经在使用中,我的方法似乎工作正常但有一些错误,我需要知道错误,没有替代压缩方法。它已经进行了一些压缩,一个字节中的位代表 8 个像素,只是想将其缩小一点(但已证明在字节溢出时出错)。
第一次通过循环时,bFFOverflow
和 bZeroOverflow
未经初始化就被访问。
但主要问题是,在记录 255 个 0 或 0xFF 字节后,如果还有更多字节,则将计数设置为 1。但是,这应该为零,因为您在计算该字节的第 255 个副本后检测到溢出。
所以总是将 bFFOverflow
和 bZeroOverflow
设置为 0 来写出计数。
睡了一觉后,我重做了,结果更好,代码更少,我把这样的事情复杂化了太多。
我用它得到了令人印象深刻的结果,并考虑用一种检查方法对其进行改进,以通过选择重复次数最多的字节并将其记录到 EEPROM 'file' 中找到最佳 'compression'。
无论如何,这是我的代码,与第一个相比好多了,也许它可以帮助别人。这是节省一些字节的非常轻量级的解决方案。
例如,分辨率为 128x32 像素的空白屏幕或全屏仅产生 9 个字节,对半仅产生 17 个字节。我在之前的问题中显示的屏幕'compiles'只有405字节,节省了大约100字节。
这是我更新的代码:
uint8_t TOLEDdisplay::getCacheRawBits( uint16_t iAddress )
{
if( iAddress < cacheSize )
{ return displayCache[ iAddress ]; }
return 0x00;
}
bool TOLEDdisplay::setCacheRawBits( uint16_t iAddress, uint8_t iBitByte )
{
if( iAddress < cacheSize )
{
displayCache[ iAddress ]=iBitByte;
return true;
}
return false;
}
uint16_t TOLEDdisplay::writeToEeprom( uint16_t iAddress )
{
if( cacheSize == 0 || width == 0 || height == 0 )
{ return 0; }
uint8_t iBits; // Pixel * 8 = byte
uint8_t iFoundBits; // 'Type' of detected
uint16_t iByteSize = 0; // Total byte size of stream
uint8_t iCount = 0; // Count of repeats found
bool bSame; // Boolean to evaluate repeats
// Write screen bounds, when read it back with readFromEeprom,
// these bounds need to match with current screen bounds.
EEPROM.write( iAddress++, width );
EEPROM.write( iAddress++, height );
// Reserve two bytes for stream size
uint16_t iSizeAddress = iAddress;
iAddress+=2;
// Write the cache content to the EEPROM
uint16_t i = 0;
while( i < cacheSize )
{
// Get a byte with bits
iBits = getCacheRawBits( i );
++i;
// Find repeating lines or empty lines
if( iBits == 0xFF || iBits == 0x00 )
{
iFoundBits = iBits; // Set found bits to detect changes
bSame = true; // Set to true to able to start loop
iCount=1; // Count this found one
// Loop to find duplicates
while( bSame && ( iCount < 0xFF ) && ( i < cacheSize ))
{
iBits = getCacheRawBits( i ); // Get next byte with bits
bSame = (iBits == iFoundBits); // Determine is repeat, the same
iCount+=bSame; // Increment count when same is found
i+=bSame;
}
// Finally write result to EEPROM
EEPROM.write( iAddress++, iFoundBits ); // type
// Write the amount
EEPROM.write( iAddress++, iCount ); // count
// Goto main loop and find next if any
}
else {
// Write found value normally to EEPROM
EEPROM.write( iAddress++, iBits );
}
}
// Final EOF address is one pos back
--iAddress;
// Calculate stream size
iByteSize=iAddress-iSizeAddress;
uint8_t* pByteSize = (uint8_t*)&iByteSize;
// Write size of stream
EEPROM.write( iSizeAddress , *pByteSize++ );
EEPROM.write( iSizeAddress+1, *pByteSize );
// return bytes written including width and height bytes (+2 bytes)
return iByteSize+2;
}
bool TOLEDdisplay::readFromEeprom( uint16_t iAddress )
{
uint8_t iBits;
uint8_t iRepeats;
uint16_t i = 0;
uint8_t* pI = (uint8_t*)&i;
uint8_t iWidth = EEPROM.read( iAddress++ );
uint8_t iHeight = EEPROM.read( iAddress++ );
// Read stream size, read two bytes
*pI = EEPROM.read( iAddress++ );
*pI++;
*pI = EEPROM.read( iAddress++ );
// Clear the screen
clear();
// If an error (no image on EEPROM address) or screen bounds
// doesn't match, skip to continue
if( i == 0 || iWidth != width || iHeight != height )
{
update( true ); // Set screen to blank
return false;
}
uint16_t iCacheAddress = 0;
while( i-- )
{
// Get a byte with bits
iBits = EEPROM.read( iAddress++ );
// Explode repeats if detected
if( iBits == 0xFF || iBits == 0x00 )
{
// read amount of repeats
iRepeats = EEPROM.read( iAddress++ );
// Explode it into cache
while( iRepeats-- )
{ setCacheRawBits( iCacheAddress++, iBits ); }
}
else {
// Put value normally into cache
setCacheRawBits( iCacheAddress++, iBits );
}
}
// Done, update the screen
update( true );
// Return success
return true;
}
也许我必须添加一些 EEPROM 边界检查,但现在它工作正常。