Wednesday, 2 March 2016

Adding a flip function to Adafruit's SSD1306 library for oLED displays

Just as we thought we were getting to the end of firmware development for our electronic guitar neck, Steve came up with an idea that we just couldn't shake off - namely, using one of those nice, neat oLED displays, in place of the clunky, power-hungry 16x2 character displays.

These little displays are not only super-small, but they light up without the need for a backlight - eliminating any potential contrast issues when you're up on stage, trying to select the appropriate display pattern!

These displays are a little more expensive than the 16x2 character displays (£2.80 each against about 99p) but the extra resolution, no need for fine contrast balancing and simple two-wire I2C interface made them altogether too irresistible not to use!


There are already a couple of display libraries available for these displays.
At first we tried the ug8 library which worked quite nicely.
But as soon as we tried to incorporate this into the firmware already developed to control the rest of the guitar neck, we hit problems. In fact, it just plain refused to compile. It's just the frustrating nature of using clashing libraries with Arduino. Another reason to hate it.

But we needed to get our display up and running quickly and, the one thing Arduino has going in its favour is, speed of development. So we thought we'd stick a seperate, dedicated chip on the LCD and simply use it as a display driver, to which we'll pump serial data to get it to display text (and maybe even graphics) on the screen, and little else.

With this in mind, the Adafruit display library looked quite interesting. It uses a large buffer to hold the screen image data, then dumps it all to the screen in one hit - creating a fast refreshing effect, rather than a slow screen re-draw rate, as the data is read off external eeprom or similar.

In fact, it didn't take long (after changing the Adafruit libraries to use I2C address 0x3C instead of 0x3D as was hard-coded in places) to get our display up and running with the sample text provided.


Just as we were about to call it a night, someone asked "what about left-handed players"?
Well, I'm left-handed. I'm about as left-handed a person as I can imagine. In fact, I think I could probably lose my right hand completely and - other than holding multiple crisps or biscuits in it after being offered a packet - I don't think it'd particularly miss it.

Yet despite this, I learned to play a right-handed guitar.
Admittedly, this was because I couldn't - at the time I first started learning - afford a left-handed guitar, and so have suffered nearly a lifetime of being crap at playing guitar and continually dropping plectrums to the point where I don't bother with them any more. If a complete and utter, total leftie can learn to play a right-handed guitar, what's the point in left-handed guitars anyway?

Sadly, this argument didn't wash with anyone else and I was simply accused of being bitter and that was no excuse not to make a reversible/flippable LCD display. Still this didn't wash. So it was suggested that I didn't know how to and that it probably wasn't possible.

The code behind our "flip display" function isn't particularly fancy. And, on completing it and showing that it works, it was pointed out that the Adafruit library has a setRotation function which allows you to change the screen co-ordinate system before drawing to the display. But we built our flip function - and it allows you not just to flip the screen before drawing on it, but actually flips the buffer after it's been drawn to.

Because we're manipulating the shadow buffer, we can do all our clever image manipulation, then simply call the .show function to make it all appear on the screen at once.

It's important to understand how the library draws text/images to the screen. It starts in the top-left corner and draws 8 pixels down (a single byte value). Then it moves along to the next "column" of 8 pixels, and draws those.


So our byte values represent a "stack" of pixels from top to bottom.
If we were to take those byte values and reverse them (so, for example, 11100001 becomes 10000111) we'd effectively draw each column "up-side-down". Which is almost what we want. Except it's not quite.


We haven't actually rotated our image/text, merely flipped it. As well as changing the drawing order of each pixel in the image, we also need to change where it appears on the screen:


So pixel at position zero (top-left) needs to be at position pixel-count (bottom-right).
If each column of eight bits is one byte value, we need the order of our bytes to be reversed, from 0,1,2,3,4,5,6,7 to 7,6,5,4,3,2,1.

This is exactly what we did with our new "flip" function. In the Adafruit_SSD1306.h file we added the single line:

void flip();


And in the Adafruit_SSD1306.cpp file, added the function:


void Adafruit_SSD1306::flip(void){
      // loop through every byte in the buffer and flip it vertically.
      // Since the display draws 8 vertical bits (per byte) we can just flip each byte
      // from MSB to LSB first
      uint8_t t;
      uint8_t p;
      int byteCount=((SSD1306_LCDWIDTH*SSD1306_LCDHEIGHT/8)/2)-1;
      int bufferEnd = (SSD1306_LCDWIDTH*SSD1306_LCDHEIGHT/8)-1;
      for (uint16_t i=0; i<=byteCount; i++) {
            p=0;
            t=buffer[i];
            for(int j=0; j<=7; j++){
                  p=p << 1;
                  if((t & 0x01)>0){ p=p|1;}
                  t=t >> 1;
            }
     
            t=buffer[bufferEnd-i];
            buffer[bufferEnd-i]=p;
     
            p=0;
            for(int j=0; j<=7; j++){
                  p=p << 1;
                  if((t & 0x01)>0){ p=p|1;}
                  t=t >> 1;
            }
            buffer[i]=p;
      }
}

And hey presto - call the flip function before draw  and the entire contents of the display - even after you've filled it with junk - appears rotated 180 degrees: