简体   繁体   中英

Rotating 1 bit bitmap for lcd screen

I'm storing some custom characters for a Hitachi HD44780 LCD controller in an array on my microcontroller (Arduino Mega). A character is basically a bitmap with a color depth of one bit and is 5 px wide and 8 px high.

To save as much precious memory as possible, I decided to store the data rotated. Otherwise I would waste three bits per row. So for example, an É would be stored like this:

---#####   0x1F
-#-#-#-#   0x55
#--#-#-#   0x95
---#-#-#   0x15
---#---#   0x11

The output should look like this:

-----#--   0x04
----#---   0x08
--------   0x00
---#####   0x1F
---#----   0x10
---####-   0x1E
---#----   0x10
---#####   0x1F

My question is, what's the most efficient way to turn this back around into an É. We're talking about a 16 MHz processor with only 8 KB of RAM here, so keeping it as fast and tiny as possible is the key. The programming language is C(++).

My personal idea was to create the required array of 8 bytes and scan the lines from left to right, setting the bits with some bit masks. That's why I also mirrored the letters, so that I can easily shift my bitmask right and use it for both arrays.

Basically scan the first input byte, set the 3rd bits of the output array accordingly, scan the second line, set the 4th bits of the output array and so on.

But is there a better way to achieve this?

First off, thank you. This is the most interesting question I have seen in months.

So space is the constraint. As I see it the major problem is that there is a cost to bit extraction and insertion that will eat up code memory pretty fast. A loop extracting a single bit from an array of 5 bytes, inserting into an array of 8 bytes could require a good hunk of executable code to do that.

I suggest a different way to represent the data is to support a kind of run-length encoding. The output character can be thought as a single bit string, consisting of a stream of 0/1s, followed by other streams of 0/1s until 64 bits have been filled.

In effect you are encoding the state changes, rather the actual bit pattern. The data would be 1 bit wide, to indicate 0 or 1, with a length of 3 bits wide to represent length 1 .. 8.

hence with

-----#--   0x04
----#---   0x08
--------   0x00
---#####   0x1F

The encoding would be

  • 0101 - 0 bit, a total of 6 bit positions, encoded as 6 - 1 (------)
  • 1000 - 1 bit, a total of 1 bit positions, encoded as 1 - 1 (#)
  • 0101 - 0 bit, a total of 6 bit positions, encoded as 6 - 1 (------)
  • 1000 - 1 bit, a total of 1 bit positions, encoded as 1 - 1 (#)
  • 0111 - 0 bit, a total of 8 bit positions, encoded as 8 - 1 (--------)
  • 0101 - 0 bit, a total of 6 bit positions, encoded as 6 - 1 (------)
  • 1100 - 1 bit, a total of 5 bit positions, encoded as 5 - 1 (#####)

That's 3.5 bytes instead of 4 bytes to encode.


A variation on the theme, is to not encode the leading 3 bits on each byte. When the executable code comes to that, it automatically puts the three 0s into it. That would drop the encoding cost of the little example above to about 2.5 bytes at the cost of some extra executable code.

I think the benefit here is that you are pulling exactly bits from one nibble of the byte each time, and dropping them into bits of a single byte. IMNSHO that is going to get the biggest size bang for the buck.

is there a better way to achieve this?

Rather than save the character bitmap in a rotated way, save it packed in 5 bytes.

To pack at compile time:

  1. Create 32 constants (or eunm )

     #define L_____ 0 #define L____X 1 #define L___X_ 2 ... #define LXXXX_ 30 #define LXXXXX 31 
  2. Make macros

     #define PACK8_5(a,b,c,d,e,f,g,h) \\ ((((((uint64_t)(a) << 5) + (b)) << 5) + (c)) << 5) + (d) ... #define UNPACK40_5(a) \\ ((a) >> 32) & 31, ((a) >> 24) & 31, ((a) >> 16) & 31, ((a) >> 8) & 31, (a) & 31 #define CHBM(a,b,c,d,e,f,g,h) (UNPACK40_5(PACK8_5((a),(b),(c),(d),(e),(f),(g),(h)))) 
  3. Make the character bit map. What is nice about this is that the source code can look like the character bitmap.

     unsigned char letter[5] = { CHBM( \\ L__X__, \\ L_X___, \\ L_____, \\ LXXXXX, \\ LX____, \\ LXXXX_, \\ LX____, \\ LXXXXX) }; 

To unpack at run time - various methods possible. Below is a simple idea.

void unpack5to8(unsigned char dest[8], const unsigned char src[5]) {
  uint64_t pack = src[0];
  for (unsigned i=1; i<5; i++) {
    pack <<= 8;
    pack += src[i];
  }
  for (unsigned i=8; i>0; ) {
    i--;
    dest[i] = pack & 31;
    pack >>= 5;
  }

Another idea would use more code but 32-bit variables. This is where OP can profile different codes.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM