1K LCD Tinyfont

A tiny pixel font rendered to an LCD display, in under 1K program space.

Similar projects worth following
In under 1kB, this project uses a tiny, custom-made pixel font to write a message to a display. It receives messages from the UART and writes them to the display.

The font was created using my homemade small font designer (see external links). It includes all letters (uppercase/lowercase), numbers, and most punctuation. The font weights in at 383 bytes.

The application has the ability to unpack this font, and use it to write a message on an LCD display.

It also performs word-wrapping.

Final program size: 930 bytes


The project was built using a Atmega328pu, and Nokia 5110 LCd display on a Sparkfun dev board.

The board is custom-made (see attached Eagle CAD and images), but the project will function on a Arduino with the same microcontroller. The microcontroller is programmed with a USBTiny.

The software was written in assembly with Atmel Studio 7- initial versions written in C were far too large.

Character Development and Compression

The font was designed using our own Small Font Designer, producing variable-width characters 8 pixels tall.

Since each character is 8 pixels tall, each will fit evenly into bytes. The characters are packed to indicate their width, and each active pixel.

A => 5 wide, bytes: 0114a74620
width | data | data | data | data |  data
0x05,  0x01, 0x14,  0xa7,  0x46, 0x20
These bytes expand to:
. . . . .
. . X . .
. X . X .
. X . X .
. X X X .
X . . . X
X . . . X
. . . . .

Which kinda looks like an 'A'.

Messages are written to the display buffer letter-by-letter. The current character is loaded found in the program (slowly looping to save space), decompressed to individual pixels in 64-byte buffer, and written to the LCD display buffer.

After loading the message, the display buffer is written to the LCD via SPI.

Receiving Messages

Messages are received with the UART at 9600bps. A newline character 0x0A prints the message and resets the buffer. Additional newlines will be inserted by the program during word wrapping.

This example receives messages over Bluetooth, but the source could be anything.

Program Size

The final program size was determined by pulling the program from flash and checking the bytes.

This is included in "program_map.txt".

Full Application930 bytes
Compressed Font383 bytes
Code (decompression, LCD driver, UART, etc)547 bytes


The full program (bytes + memory addresses) from flash used to determine the program size.

plain - 4.76 kB - 01/02/2017 at 18:26



Schematic of board used in this project.

Portable Network Graphics (PNG) - 70.02 kB - 12/29/2016 at 15:46



PCB layout of board used in this project.

Portable Network Graphics (PNG) - 65.12 kB - 12/29/2016 at 15:46


Eagle CAD files for the board used in this project.

Zip Archive - 1.06 MB - 12/29/2016 at 15:44


  • 1 × Atmega328pu Microprocessors, Microcontrollers, DSPs / ARM, RISC-Based Microcontrollers
  • 1 × Nokia 5110 LCD Display
  • 1 × USBTiny Atmel ISP

  • Messages from UART

    Zach01/02/2017 at 18:08 0 comments

    I modified the message buffer to fill with data received from the UART. Since the code to set up and process serial input is pretty short, it actually saves space vs. the preset message and prep.

    Plus, now the project is actually useful. My test device has a Bluetooth serial receiver, so it could be used to display arbitrary messages from my computer or phone.

  • Reduced Font Size

    Zach12/30/2016 at 03:07 1 comment

    A quick tip from a community member (hat tip Yann Guidon) saved some space. Since each character of the font begins with the character width, and each character is one-byte tall, the null terminator is redundant. We can infer this information already from the width.

    This reduced the font size about 90 bytes, and saved the program about 70 (additional code to process this change). Will do some code cleanup to try to realize some more savings.

  • Word Wrapping

    Zach12/29/2016 at 05:34 0 comments

    The program now uses word wrapping when displaying the message to the display. This is done naturally based on the width of the characters. Manually newlines are also supported

    The project is feature complete, clocking in at 1014 bytes- we just made it!

  • Newline Support

    Zach12/29/2016 at 04:36 0 comments

    Added support for the newline character, allowing text to extend to multiple lines on the display.

  • Full font with 867 bytes!

    Zach12/28/2016 at 04:59 0 comments

    867 bytes! We did it!

    The application now contains a fully usable font. This includes all uppercase/lowercase characters, and most punctuation. I also found a quick workaround to the font storage to avoid the strange marks on some characters, without adding any more space.

  • Hello World!

    Zach12/27/2016 at 03:12 0 comments

    Well, technically 'hello world'. Haven't loaded in upper case or symbols yet.

    The program is able to draw any character to a specific place on the screen, and increment through a message buffer.

    Will need to find a new method for storing the font, as the current method introduces some artifacts in order to identify complete letters in the buffer- that's why some of the letters appear to have weird accent marks.

    Current Program Size: 541 bytes

  • Using less memory than I thought

    Zach12/21/2016 at 16:51 0 comments

    I loaded my compressed font into the application, and was pretty bummed about program size- 1.1kB for just a lowercase font, and I'm still missing a ton of functionality.

    Then, I realized that I'm overcalculating my memory usage by a long shot.

    I was basing my calculation off the .hex file produced by the linker. This isn't right- I should be basing it off the actual program size saved to application ROM.

    After copying the contents from the memory map in the Atmel simulator, I'm at 402 bytes. Way way way under!

  • Setting Individual Pixels

    Zach12/20/2016 at 05:26 0 comments

    Added the ability to set an individual pixel on the screen by coordinate. Doesn't look like much, but from here I can bring in font processing.

    At 656 bytes- so getting pretty close the the line already. Going to have to find something to trim...

    Had to pull out some fun, space-saving assembly math tricks:

    y % 8 == y AND 0x07
    y / 8 
    ... is the same as...
    right shift y 3 times

    Not much to see, but three pixels are set... and they're the ones I asked for!

View all 8 project logs

Enjoy this project?



Yann Guidon / YGDES wrote 12/29/2016 at 16:48 point

More saving and better entropy :

The size prefix can at best encode numbers up to, say, 7. Size is never 0 BTW.

Many lowercase letters (a,c,e,m,n,o,s,r,u,w,v,x,z,...) have fewer bits for the higher pixels.
Use the MSB (bit 4 to 7) of the prefix to encode the pixel height, which can further reduce the nuber of bits used by the bitmap :-)

  Are you sure? yes | no

K.C. Lee wrote 12/29/2016 at 16:46 point

Null terminated means that the font would not be able to handle graphics characters with a gap.  If you are going to waste a byte, use it to specify the width of the font and kerning.  Put that byte first so that you code knows how many data byte and thus can have anything including 0x00 as data.

464 bytes = 92.8 character for a fixed 8x5 font.

  Are you sure? yes | no

Yann Guidon / YGDES wrote 12/29/2016 at 16:33 point

I don't get it... Why do you have a Null char when you have the width as a run-length information ?

You could save maybe 50 bytes by either removing the null or the runlength prefix, and adding the "null" (inter-char space ?) during the display routine. You could even have a better handling of variable-spacing words so they can exactly fit one line :)

  Are you sure? yes | no

Zach wrote 12/29/2016 at 16:42 point

Yup- that will definitely save some space. Didn't connect that I know 'exactly' how many byte the character is already. It will also make my algorithm for skipping to the next letter way easier as well. Thanks for the tip!

  Are you sure? yes | no

Yann Guidon / YGDES wrote 12/29/2016 at 23:36 point

To put this into perspective, there are 2 ways to represent a character string :
* C style is NULL terminated (a PITA)
* Pascal style is size-prefixed.

Mixing them is possible, I did it once :-)

However it is redundant...

Knowing this, it was obvious to me that you mixed the representations. I believe that the prefix style is the best because you can put more informations in the prefix than in a NULL byte.

The size information can also be compiled to infer each line's height :-D

Happy new year from Paris !

  Are you sure? yes | no

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates