Little flash, big trouble

A project log for DIY JBC Nano Controller

DIY controller for JBCs Nano (NT115) Soldering irons

DanielDaniel 10/26/2020 at 14:400 Comments

16KB flash should be enough for everybody...

... at least that's what I though when selecting the controller for this project. After all, controlling a soldering iron is not a very complex task, so I just got the smallest STM32F0 available. Then I started to write the code and figured out, I was gravely mistaken. The whole project with the features I wanted would have never fit into that controller, so I hade to find ways to save flash.

HAL is huge

Initially, I wanted to build this with the STM32Cube HAL. I've never used it before, but since it's kind of standard I wanted to give it a try. But once I set up the project, I realized, this is a bad idea! Even a very simple test program already takes 10k, so that's not gonna work.

libopencm3 to the rescue

In all my other STM32 projects, I always used the libopencm3. That's a free HAL for Cortex-M ARMs. It does not come with the nice graphical pin and clock assignment thingy, so it requires a bit more data-sheet diving. And I think it makes a slightly worse job at abstracting the hardware, so the code is a bit more target specific. But its WAY smaller, so that's why I used libopencm3 for this project as well.

Fonts aren't small either

Since I'm using a graphic display, I needed fonts! And since I don't want all text in the same font size, I need more than one of those. I used bitmap fonts, which makes fonts with large letters quite big. And so when I ran out of flash again and decided to do something about the font size.

Strip unused characters

I realized, that the biggest font (16x26) is exclusively used for the temperature display in the main screen. But there only the numbers and the 'C' is needed. So the first reduction was by creating a copy of that font that only contains 11 symbols instead of a full char set.

Compress the font

Even with the reduced char set in the large font, the flash was still to small, so I decided with experimenting with ways to compress font. For large fonts, this is usually done by using vector fonts. But for smaller fonts, this gets ugly results, so that's not really a good option. Instead, I was looking for ways to compress the bitmap font. Since after compression, the sizes of the single bitmaps won't be the same, an offset table is needed so the symbols can be found again. This further increases the size of the compressed data so an efficient compression is needed to reach the break-even.

Run-length encoding

At first I experimented with different forms of run-length encoding, but the resulting compression ratio was disappointing. The following example shows a very simple run length encoding, where only blocks of '0' are encoded. Blocks of '1' or mixed blocks are stored raw. This shows that even with data that is very compression friendly and an optimal setting for the block length, the compression ration isn't very good. For small data sizes, the size of the headers just eats the space savings, so the data needs to be very compressible to even reach the break-even.

Code: 0xxxx -> xxxx bits of 0
      1xxx  -> xxx bits of uncompressed data

Original: 00000000110000000000  20 bit
Encoded:  0100010101101010      16 bit
          ^8x0 ^2x* **^ 10x0

Geometric compression / Bounding boxes

I decided to try an encoding scheme, where the break-even is very early. When looking at the font, I figured that most chars do not fill the whole bitmap. So I only stored the used area of the char and have a small header containing the position and size of that area. These headers are tiny and there is only one header for each char. For a 11x18 font the header is just 18 bit (x/w each 4 bit + y/h each 5 bit). So the break-even is reached as soon as we can avoid storing a single column.

Header: xxxxwwwwyyyyyhhhhh 18 bit
x: Offset (x)
w: Width of the stored region
y: Offset (y)
h: Height of the stored region 

 Below is the summary of the compression efficiency for the used 11x18 font

11x18 Font 95 Chars
Raw size:   11x18 = 198 bits -> 25 bytes (rounded) per char
             => Total: 2375 bytes

Compressed: Offset table: 11 bits per entry, 95 entries  -> 131 bytes
            Data size: 1364
             => Total: 1495 bytes

===> 37% size saved by compression