Arduino - 8 Bit Graphics with SSD1306

After my initial foray into graphics I wanted to see what was possible with the Atmega328p and the SSD1306 screen. Lots of applications which use this little microcontroller involve pretty mundane, uninspiring things like home automation and I like the idea of giving it a wee chance to shine and do something fun. The constraints of a relatively slow processor, extremely low memory, as well as a tiny and monochrome display were pretty enticing to me, especially after reading some of the early stuff on on the hacks Bill Atkinson, Andy Hertzfeld et al employed to get most of out the Macintosh's 68000 processor.

FPS counter

Firstly we need to know the limits of the hardware. I wasn't sure whether the processor, the SPI bus or something inside the display itself would limit the frame rate to something unacceptable and put the kibosh on any sort of fast/responsive visualisations. So I whipped up a quick test which sent a bunch of empty frames to the display and every so often output the average frame rate.

Mercifully it seems that we're able to push as much as 207fps to the SSD1306, which means that we've got a fair bit of room to play with and that if anything the Atmega328p is the bottleneck.

Code: ssd1306_spi_maxfps.ino


My first attempt was a little clone of breakout. I am apparently hopeless at it - but it's actually the first "game" I'd ever written, so that's fun.


3D cube

Since the Breakout clone worked smoothly, wanted to see if it was possible to produce simple 3D graphics - eventually settling on creating a spinning cube bouncing around the screen.

Initially I got a little too ambitious and attempted to roll my own general purpose library for 3D graphics - with a set of functions to manipulate a stack of transformation matrices. However I kept on blowing through my stack space  and clobbering a load of program state. This makes sense, since if we have a matrix implementation of 4x4 floats, we'd need to create one for the camera, a rotation and translation then that's already 3 matrices of 64 bytes each. Then we have to multiply the matrices and apply this to 8 vertices you can easily see how a naive implementation could at some point go a bit haywire and chew through more stack than it should.

Instead of taking the time to trim this down using the limited debug tools available to me (no stepping, watch, gdb through my simple USB cable) I just manually calculated the transformations I needed for my rotating/bouncing cube ahead of time and implemented them so they could be parameterised. So starting with the following (with Tx, Ty & Tz representing the center position of the cube, θ being angle of rotation and x, y and z being the co-ordinates of each cube)

Which means that each point can be represented by the following vector

Since I just wanted a rotating cube with wireframe lines I didn't need to do much other than use this to calculate the expected x/y positions of each point on screen and then use existing drawLine() function to connect each. Which resulted in this slightly clumsy function:

The final video involved two rotating cubes, one of which is flying around the screen.

It's quite satisfying result as it's the first time I'd produced a from-scratch 3D projection since university, and was actually surprisingly fast - the video looks a little shakey but in person it's as smooth as butter.


Seed Cathedral

Known as the Seed Cathedral the UK pavilion at Expo 2010 in Shanghai caused quite a stir and rightly earned itself an award from the organising committee for its outstanding design. It's a simple cube,with thousands of transparent plastic rods sticking out of it - each one containing a different type of seed. When you viewed it from a dozen steps back a subtle Union Flag pattern appears, which I must admit is is very neat even if I'm not a proud/patriotic Brit myself.

When I was trying to think of weird or interesting things that could be modelled quickly and easily in 3D which would have an interesting effect this jumped into my mind. The implementation is pretty simple - use similar code to create the projection, and shuffle the camera around a little to create a shimmering monochrome Union Flag. 

... later that day

And after all that hard work, I saw some stuff that left me pretty deflated - two excellent implementations of graphics libraries for AVR microcontrollers. The first one is u8g - a slightly more heavy duty library than the one Adafruit provided. It also provides some nicer font handling ... including a tiny font similar to the one I did. Here's an example of someone using it to draw a rotating cube at a reasonable clip, ~40 fps no less:

The second one is even more impressive and needs to be seen to be believed. Someone managed to create a library to render a 16 bit colour scene in 3D with texturing and lighting.

Be sure to check out the video at the bottom of this link, it's a little humbling after you've just thrown together a couple of simple monochrome visualisations -

A tiny font ... Eastern European edition

In a previous post I created a little replacement font for the Adafruit Graphics Library, but left the non-ASCII characters (values > 0x7F, 128 in total) completely blank. This is because this area is used to implement the additional symbols necessary for languages other than English, and I had initially just intended to create an English language font for my own purposes.

Obviously you can't represent all of the necessary symbols for all the world's languages in just 128 slots, and this is where ISO 8859 comes in*. There are 16 ISO/IEC 8859 character sets which implement the symbols for loosely related (and sometimes unrelated) languages in the upper 128 bytes.

The most common one is probably ISO 8859-1 which can represent most of the EU's main languages, so it should be a pretty sensible one to implement. However since I live in the Czech Republic and I love Central\Eastern Europe I decided to implement ISO 8859-2 which covers Bosnian, Polish, Croatian, Czech, Slovak, Slovene, Serbian, and Hungarian and looks like the below

Note: the above rendering I picked up someplace on the internet is actually incorrect - when the letters t and d have a caron/haček it actually looks more like an apostrophe - ď and ť.

I only implemented this for my slimmer 3x8 font, but if there's any interest I can quickly put together something for the Adafruit 5x8 one or attempt a different character set so long as it's based based on a latin script. Sadly I'm not sure if my skills are up to the task of creating something tougher like Chinese, Tamil or Thai.

I'm a novice at font design, but looking at the required diacritics it seemed to make sense to reserve the top two rows for things like Haček (e.g. č, ř and ž) and Čarka (e.g. á, ý and é) and the bottom row for anything below the letters - which leaves a 3x5 space in the middle to implement the root of each letter - which I managed to do with a couple of exceptions. Here's a quick visualisation of this:

So for example if we want to create the letter "č" it would look something like this:

With only three columns available there are a number of characters which will look a little weird - the worst of which were Đ, § and ď - and the Albanian characters with the tail (ç, ţ and the like). Here's what the font looks like:

It's a little cramped - with some characters appearing to be joined to those above/below - which is due to the fact that the top rows were previously blank and served as line spacing.

I've pushed this into my fork of the Adafruit GFX Library repo on github - check it out and copy the whole thing into your Arduino libraries folder to install it. Note that the Arduino IDE saves files using UTF-8 encoding, so you can't just throw string literals in and assume they'll work - the simplest way to output a string using this character set is to manually enter the hex values. 

So if we want to have the string representation of a nice little Czech sentence, we'd encode it as follows:

When we pass this to display.write() - not display.print(), which will just output the numeric values - we'll see this:

* = Yes I know that we have unicode to properly address this problem, but it's quite complex and is mercifully not used by the Adafruit Graphics library for simplicity's sake.