Close

A Tiny 4x6 Pixel Font That Will Fit On Almost Any Microcontroller (License: MIT)

A project log for VGA Graphics Over SPI and Serial - VGATonic

640x480 (and 848x480!) Color VGA Video Card for Microcontrollers and Single Board Computers

pkPK 07/10/2015 at 05:1211 Comments

I won't ruin the surprise about what feature I'm working on right now for VGATonic (it's a crazy one), but I needed to add a font. Let me rephrase - a tiny font. Let me be more specific - the most space efficient font possible.


My searching first led me to the 4x6 font here: http://robey.lag.net/2010/01/23/tiny-monospace-font.html made with an assist by 'Robey' by Brian Swetland of Palm Pilot fame (I've got a working Palm Pilot with a minor crack in the screen in the office). Further clicking led me here, where "Dr_Acula" had already encoded it into 96x3 bytes: http://forums.parallax.com/discussion/comment/1073601#Comment_1073601

We can do better, though - you see, it's really a "3x5" font, with whitespaces.

3x5 = 15, which is less than 16 (one word, or 2 8 bit bytes). You guessed it - I wrote up some python to make it even smaller, and jammed each character into 2 bytes. It's so small, I'm going to post it in its entirety here (in C, for AVR - easy enough to convert for your favorite language):

// Font Definition
const uint8_t font4x6 [96][2] PROGMEM = {
 {  0x00  ,  0x00  },   /*SPACE*/
 {  0x49  ,  0x08  },   /*'!'*/
 {  0xb4  ,  0x00  },   /*'"'*/
 {  0xbe  ,  0xf6  },   /*'#'*/
 {  0x7b  ,  0x7a  },   /*'$'*/
 {  0xa5  ,  0x94  },   /*'%'*/
 {  0x55  ,  0xb8  },   /*'&'*/
 {  0x48  ,  0x00  },   /*'''*/
 {  0x29  ,  0x44  },   /*'('*/
 {  0x44  ,  0x2a  },   /*')'*/
 {  0x15  ,  0xa0  },   /*'*'*/
 {  0x0b  ,  0x42  },   /*'+'*/
 {  0x00  ,  0x50  },   /*','*/
 {  0x03  ,  0x02  },   /*'-'*/
 {  0x00  ,  0x08  },   /*'.'*/
 {  0x25  ,  0x90  },   /*'/'*/
 {  0x76  ,  0xba  },   /*'0'*/
 {  0x59  ,  0x5c  },   /*'1'*/
 {  0xc5  ,  0x9e  },   /*'2'*/
 {  0xc5  ,  0x38  },   /*'3'*/
 {  0x92  ,  0xe6  },   /*'4'*/
 {  0xf3  ,  0x3a  },   /*'5'*/
 {  0x73  ,  0xba  },   /*'6'*/
 {  0xe5  ,  0x90  },   /*'7'*/
 {  0x77  ,  0xba  },   /*'8'*/
 {  0x77  ,  0x3a  },   /*'9'*/
 {  0x08  ,  0x40  },   /*':'*/
 {  0x08  ,  0x50  },   /*';'*/
 {  0x2a  ,  0x44  },   /*'<'*/
 {  0x1c  ,  0xe0  },   /*'='*/
 {  0x88  ,  0x52  },   /*'>'*/
 {  0xe5  ,  0x08  },   /*'?'*/
 {  0x56  ,  0x8e  },   /*'@'*/
 {  0x77  ,  0xb6  },   /*'A'*/
 {  0x77  ,  0xb8  },   /*'B'*/
 {  0x72  ,  0x8c  },   /*'C'*/
 {  0xd6  ,  0xba  },   /*'D'*/
 {  0x73  ,  0x9e  },   /*'E'*/
 {  0x73  ,  0x92  },   /*'F'*/
 {  0x72  ,  0xae  },   /*'G'*/
 {  0xb7  ,  0xb6  },   /*'H'*/
 {  0xe9  ,  0x5c  },   /*'I'*/
 {  0x64  ,  0xaa  },   /*'J'*/
 {  0xb7  ,  0xb4  },   /*'K'*/
 {  0x92  ,  0x9c  },   /*'L'*/
 {  0xbe  ,  0xb6  },   /*'M'*/
 {  0xd6  ,  0xb6  },   /*'N'*/
 {  0x56  ,  0xaa  },   /*'O'*/
 {  0xd7  ,  0x92  },   /*'P'*/
 {  0x76  ,  0xee  },   /*'Q'*/
 {  0x77  ,  0xb4  },   /*'R'*/
 {  0x71  ,  0x38  },   /*'S'*/
 {  0xe9  ,  0x48  },   /*'T'*/
 {  0xb6  ,  0xae  },   /*'U'*/
 {  0xb6  ,  0xaa  },   /*'V'*/
 {  0xb6  ,  0xf6  },   /*'W'*/
 {  0xb5  ,  0xb4  },   /*'X'*/
 {  0xb5  ,  0x48  },   /*'Y'*/
 {  0xe5  ,  0x9c  },   /*'Z'*/
 {  0x69  ,  0x4c  },   /*'['*/
 {  0x91  ,  0x24  },   /*'\'*/
 {  0x64  ,  0x2e  },   /*']'*/
 {  0x54  ,  0x00  },   /*'^'*/
 {  0x00  ,  0x1c  },   /*'_'*/
 {  0x44  ,  0x00  },   /*'`'*/
 {  0x0e  ,  0xae  },   /*'a'*/
 {  0x9a  ,  0xba  },   /*'b'*/
 {  0x0e  ,  0x8c  },   /*'c'*/
 {  0x2e  ,  0xae  },   /*'d'*/
 {  0x0e  ,  0xce  },   /*'e'*/
 {  0x56  ,  0xd0  },   /*'f'*/
 {  0x55  ,  0x3B  },   /*'g'*/
 {  0x93  ,  0xb4  },   /*'h'*/
 {  0x41  ,  0x44  },   /*'i'*/
 {  0x41  ,  0x51  },   /*'j'*/
 {  0x97  ,  0xb4  },   /*'k'*/
 {  0x49  ,  0x44  },   /*'l'*/
 {  0x17  ,  0xb6  },   /*'m'*/
 {  0x1a  ,  0xb6  },   /*'n'*/
 {  0x0a  ,  0xaa  },   /*'o'*/
 {  0xd6  ,  0xd3  },   /*'p'*/
 {  0x76  ,  0x67  },   /*'q'*/
 {  0x17  ,  0x90  },   /*'r'*/
 {  0x0f  ,  0x38  },   /*'s'*/
 {  0x9a  ,  0x8c  },   /*'t'*/
 {  0x16  ,  0xae  },   /*'u'*/
 {  0x16  ,  0xba  },   /*'v'*/
 {  0x16  ,  0xf6  },   /*'w'*/
 {  0x15  ,  0xb4  },   /*'x'*/
 {  0xb5  ,  0x2b  },   /*'y'*/
 {  0x1c  ,  0x5e  },   /*'z'*/
 {  0x6b  ,  0x4c  },   /*'{'*/
 {  0x49  ,  0x48  },   /*'|'*/
 {  0xc9  ,  0x5a  },   /*'}'*/
 {  0x54  ,  0x00  },   /*'~'*/
 {  0x56  ,  0xe2  }    /*''*/
};

// Font retreival function - ugly, but needed.
unsigned char getFontLine(unsigned char data, int line_num) {
  const uint8_t index = (data-32);
  unsigned char pixel = 0;
  if (pgm_read_byte(&font4x6[index][1]) & 1 == 1) line_num -= 1;
  if (line_num == 0) {
      pixel = (pgm_read_byte(&font4x6[index][0])) >> 4;
  } else if (line_num == 1) {
      pixel = (pgm_read_byte(&font4x6[index][0])) >> 1;
  } else if (line_num == 2) { 
      // Split over 2 bytes
      return (((pgm_read_byte(&font4x6[index][0])) & 0x03) << 2) | (((pgm_read_byte(&font4x6[index][1])) & 0x02));
  } else if (line_num == 3) {
      pixel = (pgm_read_byte(&font4x6[index][1])) >> 4;
  } else if (line_num == 4) {
      pixel = (pgm_read_byte(&font4x6[index][1])) >> 1;
  }
  return pixel & 0xE;
}

To decode, you pass in the character you want (ASCII, so say 'c' or 0x63) and a line number. Line 0 through 4 will be the magic lines, so set up your loop accordingly.

And the Descenders? ('g', 'j', 'p', 'q', 'y')

I manually changed the descenders to have a '1' in the LSB of the second byte. That's the second line of the decoder program - if there is a one in that spot, I shift the whole thing down a line. (Why waste a bit, right? I think the CPLD work in those tight conditions is in my head still).

I manually edited the 'j' and the 'z', but before you ask - it can stay as the MIT license. Enjoy, and please let me know it you use it!

Discussions

Y Schwartz wrote 12/12/2022 at 15:53 point

Numbers?

  Are you sure? yes | no

Leonhard Seidel wrote 05/23/2020 at 09:16 point

i used your font as is to enable serial output to a 32x64 led panel with an arduino nano

https://github.com/CamelCaseName/HUB75nano

its really working great

  Are you sure? yes | no

Rizzo The Small wrote 04/12/2019 at 14:59 point

I made a take on this targeting writing in page mode (such as for an SSD1306 screen) which required me to rotate the bytes used in the font for ease of converting into binary output.

Here's a link to it as it is now for anyone interested: https://pastebin.com/tphFLG8j

It's in C# for .Net Core, and currently works with a byte array, but it should be easy enough to convert to your chosen language and demonstrates how you can write this cool tiny font in paged mode.

Also, thanks to VS's new delimited binary notation, it's super easy to see and edit the contents of the font bytes.

Enjoy!

         R

  Are you sure? yes | no

Dewi wrote 02/15/2019 at 17:30 point

I am confused by the bit-packing. Reading the code, if I number bits from 1 (lowest) to 8 (highest), it feels like line 0 = byte0, bits 5-7; line 1 = byte0, bits 2-4; line 2= byte0, bits 1&2 and byte1, bit 2; line 3=byte1, bits 5-7, line 4=byte 1, bits 2-4; and the flag is byte1, bit 1. So bit 8 of both bytes is unused, and bit 2 of both bytes is used in two different lines. Line 2 looks like it has no low bit, and its middle bit is overwritten. What'm I missing?

  Are you sure? yes | no

mista.inode wrote 05/17/2022 at 11:55 point

I guess you missed the bitwise & 0xE in the return statement, clearing the LSB (bit 1 by your numbering) in each returned bit-pattern; this extends the 3-column glyphs by a 4th blank column, which creates the spacing between characters.

So lines 0 / 3 consist of bits 6-8 of byte 0 / 1, while lines 1 / 4 are encoded in bits 3-5; so bits 1-2 of both bytes remaining, which encode the middle row (byte 0, bits 1-2 and byte 1 bit 2) and the flag in byte 1, bit 1.

  Are you sure? yes | no

Alexander wrote 01/29/2019 at 11:49 point

Man, thank you! It's just what I've been looking for!

  Are you sure? yes | no

adam.klotblixt wrote 02/11/2017 at 11:32 point

Very nice! I'll make sure to use it the next time I need some text.

  Are you sure? yes | no

ogdento wrote 02/08/2017 at 07:18 point

Hey this is great!  I might try and add it to my nokia 1100 serial lcd

  Are you sure? yes | no

PK wrote 02/11/2017 at 17:12 point

Keep me posted on how it looks on a small screen - when blown up on a monitor it's not that hard to read.

  Are you sure? yes | no

Blecky wrote 07/10/2015 at 13:42 point

Ha that is tiny!

  Are you sure? yes | no

PK wrote 07/11/2015 at 19:19 point

Haha - I wish it was even tinier!  Wait until you see how I jammed it into the code, I'm proud/embarrassed about it working.

  Are you sure? yes | no