Changes to Adafruit SSD1306 library to speed it up

Adafruit have released a new version of the SSD1306 OLED library here:

They have chosen not to implement the SPI speed improvements I posted on their forum here:

http://forums.adafruit.com/viewtopic.php?f=19&t=19079&start=15#p103938

To save arguing with them, I'll just post the relevant changes here.

The diffs for Adafruit_SSD1306.cpp are as follows:

*** Desktop/AdafruitSSD1306/Adafruit_SSD1306.cpp	2012-04-18 14:31:38.000000000 +1000
--- Documents/Arduino/libraries/AdafruitSSD1306/Adafruit_SSD1306.cpp	2012-06-04 13:09:57.000000000 +1000
***************
*** 14,19 ****
--- 14,23 ----
  Written by Limor Fried/Ladyada  for Adafruit Industries.  
  BSD license, check license.txt for more information
  All text above, and the splash screen below must be included in any redistribution
+  
+ SPI speed-ups by Nick Gammon
+  4th June 2012
+  
  *********************************************************************/
  
  #include <avr/pgmspace.h>
***************
*** 21,26 ****
--- 25,31 ----
  #include <stdlib.h>
  
  #include <Wire.h>
+ #include <SPI.h>
  
  #include "Adafruit_GFX.h"
  #include "Adafruit_SSD1306.h"
***************
*** 155,160 ****
--- 160,167 ----
      cspinmask   = digitalPinToBitMask(cs);
      dcport      = portOutputRegister(digitalPinToPort(dc));
      dcpinmask   = digitalPinToBitMask(dc);
+     SPI.begin ();
+     SPI.setClockDivider (SPI_CLOCK_DIV2);
    }
    else
    {
***************
*** 380,392 ****
  
  inline void Adafruit_SSD1306::fastSPIwrite(uint8_t d) {
    
!   for(uint8_t bit = 0x80; bit; bit >>= 1) {
!     *clkport &= ~clkpinmask;
!     if(d & bit) *mosiport |=  mosipinmask;
!     else        *mosiport &= ~mosipinmask;
!     *clkport |=  clkpinmask;
!   }
!   //*csport |= cspinmask;
  }
  
  inline void Adafruit_SSD1306::slowSPIwrite(uint8_t d) {
--- 387,393 ----
  
  inline void Adafruit_SSD1306::fastSPIwrite(uint8_t d) {
    
!   SPI.transfer (d);
  }
  
  inline void Adafruit_SSD1306::slowSPIwrite(uint8_t d) {

In plain English ...

Edit the library file Adafruit_SSD1306.cpp. Make the following changes:

  • Add another include:
    #include <SPI.h>

  • In setup(), add:
    SPI.begin ();
    SPI.setClockDivider (SPI_CLOCK_DIV2);

  • Change the contents of the function fastSPIwrite to read just:
    SPI.transfer (d);

Save your changes. That's it!

You also need to add this to the start of your sketch:

#include <SPI.h>

You also need to use different pin-outs to use the hardware SPI. Replace the defines at the top of the sketch with these ones:

#define OLED_DC 8     // pin 9 on CD4050B  (output: pin 10)
#define OLED_CS 10    // pin 5 on CD4050B  (output: pin 4)
#define OLED_CLK 13   // pin 11 on CD4050B (output: pin 12)
#define OLED_MOSI 11  // pin 14 on CD4050B (output: pin 15)
#define OLED_RESET 7  // pin 3 on CD4050B  (output: pin 2)

The wiring now needs to be:

Signal   Chip out --> Arduino pin

Reset      3    -->      7  (was 13)
CS         5    -->     10  (was 12)
D/C        9    -->      8  (was 11)
CLK       11    -->     13  (was 10)
DAT/MOSI  14    -->     11  (was 9)

That should compile OK, and run somewhat faster.

The changes use the hardware SPI rather than the the software (bit-banged) SPI that they were using. This restricts you to using the hardware SPI pins, whereas their implementation let you use any pins of your choice. If you want speed, make these changes. If you want flexibility, don't. :slight_smile:

Amended library file attached (keep the other ones as they are). Their license.txt file included in order to comply with the licensing requirements. It hasn't changed.

Adafruit_SSD1306.cpp (15.6 KB)

license.txt (1.46 KB)

Fantastic! Thank you Nick!

If you want speed, make these changes. If you want flexibility, don't.

Well, my opinion the changes are also improvements in flexibility. I want to use the display and also a SD card. The SD card already needs hardware SPI. So why not share these pins. Also the SD card needs 3.3V, so the ssd1306 display can share the same level-shifter pins from the level shifter. Otherwise I needed more than 1 level shifter.

Made a quick test. Seems to be that the old library is a bit faster and better. I made an endless loop that draws a bitmap image, and clears it again. With the first library with your SPI adjustment code it gets 34 FPS. The new library without SPI gets 16fps and the new library with the above SPI adjustment gets 24 fps. Besides that, the image does not flash. It’s like a black bar runs very quick over it (there is only a part of the image visible).

So, if you like the fastest framerate, I advise to use the old library. Go to Github, history, download the January version and modify it with these modifications:
http://forums.adafruit.com/viewtopic.php?f=19&t=19079&start=15#p103938

I am assuming the pins listed are for an UNO or Deumilanove? Would they be different if I'm using a MEGA 2560?

The Mega pins are different, yes.

The RESET and DC pins could stay the same as far as I know.

if(d & bit) *mosiport |=  mosipinmask;

Given that you are doing bit test, it makes a lot of sense to do that in a cpu register, or an unused IO register below 0x1f.

Better write to Adafruit about that. That was one of the lines I omitted.

All the best too, considering Adafruit responded to my suggested improvements thus:

... few people need fast updates ...

dhenry:

if(d & bit) *mosiport |=  mosipinmask;

Given that you are doing bit test, it makes a lot of sense to do that in a cpu register, or an unused IO register below 0x1f.

Personally I like to keep things simple. Descending into assembler to use an "unused register" is needless complexity in many cases.

Descending into assembler to use an "unused register" is needless complexity in many cases.

That indeed would be true.

Fortunately, the assumption of "descending into assembler" is so wrong.