The first release of the SSD1306 support library py-gaugette used the 5x7 pixel fonts from the Adafruit GFX library. That’s a fine and compact font, but wouldn’t it be nice to have some pretty high-res fonts to take advantage of the memory and resolution we have to work with?
Generating Font Bitmaps
I started with The Dot Factory by Eran Duchan. Its a handy C# (Windows) tool for generating C and C++ bitmap files quickly, and source code is available. I modified it to generate Python code, and to add a kerning table to store the minimum number of pixels the cursor must move between any two characters to avoid collison.
The kerning isn’t completely right yet - I noticed that the underscore character can slip beheath other characters. I’ll need to look at that some more in due time - and I’d also like to replace the C# app for a command-line tool to generate the rasterized image files.
Each font is stored in a module like the following. The size suffix (16 in this case) indicates the character height in pixels. The descriptor table specifies the width of each character and the character’s offset in the bitmap.
# Module gaugette.fonts.arial_16 # generated from Arial 12pt name = "Arial 16" start_char = '#' end_char = '~' char_height = 16 space_width = 8 gap_width = 2 bitmaps = ( # @0 '!' (1 pixels wide) 0x00, # 0x80, # O 0x80, # O 0x80, # O 0x80, # O 0x80, # O 0x80, # O 0x80, # O 0x80, # O 0x80, # O 0x80, # O 0x00, # 0x80, # O 0x00, # 0x00, # 0x00, # # @16 '"' (4 pixels wide) 0x00, # 0x90, # O O 0x90, # O O 0x90, # O O 0x90, # O O 0x00, # 0x00, # 0x00, # 0x00, # 0x00, # 0x00, # 0x00, # 0x00, # 0x00, # 0x00, # 0x00, # # @32 '#' (9 pixels wide) 0x00, 0x00, # 0x11, 0x00, # O O 0x11, 0x00, # O O 0x11, 0x00, # O O 0x22, 0x00, # O O 0xFF, 0x80, # OOOOOOOOO 0x22, 0x00, # O O 0x22, 0x00, # O O 0x22, 0x00, # O O 0xFF, 0x80, # OOOOOOOOO 0x44, 0x00, # O O 0x44, 0x00, # O O 0x44, 0x00, # O O 0x00, 0x00, # 0x00, 0x00, # 0x00, 0x00, # ... ) # (width, byte offset) descriptors = ( (1,0),# ! (4,16),# " (9,32),# # ... ) # kerning[c1][c2] yeilds minimum number of pixels to advance # after drawing #c1 before drawing #c2 so that the characters # do not collide. kerning = ( (1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,), ... )
Note the font is a module, not a class, because it allows a very concise syntax:
from gaugette.fonts import arial_16 textSize = led.draw_text3(0,0,"Hello World",arial_16)
Supporting Horizontal Scrolling
In the process of testing these fonts, I realized I would like to be able to scroll horizontally,
and the SSD1306 doesn’t have hardware support for that. Vertical scrolling is accomplished using
SET_START_LINE command, but the horizontal scrolling commands do not support scrolling
through an image that is wider than the display. We need to do it in software.
It turns out that blitting memory from the Pi to the SSD1306 over SPI is pretty fast; fast enough
to get a reasonable horizontal scroll effect by blitting complete frames from the Pi’s
memory to the SSD1306. There’s just one thing - the default memory mode of the
SSD1306 is row-major, and
for horizontal scrolling we really want to send a vertical slice of the memory buffer
over SPI. To avoid buffer manipulation I switched the Pi-side memory buffer to use
MEMORY_MODE_VERT on the SSD1306 when blitting.
To illustrate: the memory buffer is stored as a python list of bytes. Consider a virtual
buffer that is 256 columns wide and 64 rows high. Using column-major layout
we can address the 128 columns starting at column 100 using the python
buffer =  * 256 * 64 / 8 # buffer for 256 columns, 64 rows, 8 pixels stored per byte start = 100 * 64 / 8 # byte offset to 100th column length = 128 * 64 / 8 # byte count of 128 columns x 64 rows led.command(led.SET_MEMORY_MODE, led.MEMORY_MODE_VERT) # use vertical addressing mode led.data(buffer[start:start+length]) # send a vertical slice of the virtual buffer
Note that using column-major layout we cannot easily blit a horizontal
slice of the virtual memory buffer into display ram, so we can’t use the same method for
vertical scrolling. Stick with
SET_START_LINE for vertical scrolling. The combination
of these methods gives us fast horizontal and vertical scrolling.