Adafruit SSD1306, hardware SPI, and Teensy

Status
Not open for further replies.

Nantonos

Well-known member
This OLED display
http://www.adafruit.com/products/326

There have been several threads in the past about this Adafruit OLED monochrome display, driven by the SSD1306 driver. The conclusion was that I2C worked, but was slow, and SPI worked (with any random pin assignment) because it was bit-banged software SPI so was also slow. Lastly, Adafruit rejected a patch by Patrick Gannon to use hardware SPI on the grounds that no-one needs fast screen updates :(

http://forum.pjrc.com/threads/24562-Teensy3-and-SSD1306-SPI-help!?highlight=ssd1306
http://forum.pjrc.com/threads/24642...2-OLED-and-Pin-Designations?highlight=ssd1306
http://forums.adafruit.com/viewtopic.php?f=25&t=46997&p=238726#p238726

I had also complained in the past about the dumb (their words, in their comment) implementation of supposedly fast primitives like drawFastVLine and drawFastHLine in Adafruit_GFX library, which use the general Bresenham algorithm despite the fact that the slope is always 0 (or 90).

So, onto the good news. Adafruit have continued development on this library and the latest GitHub version has some good improvements

- there is now a true hardware constructor, you just pass the pins for CS and two other pins (Reset and DC) used to control the display. Whatever the Teensy uses for DOUT and SCK are used, without being passed to the constructor

- the SSD1306 library redefines drawFastVLine and drawFastHLine to actually be fast (draw bits directly in the framebuffer, in a loop, rather than using a generalized line drawing algorithm and calling drawPixel at every step. This also speeds up filled rectangles).

Its not all roses, the library still makes you include Wire.h even if you have the SPI version of the board (because the library includes I2C, software SPI, and hardware SPI) and it still checks which of those three options you are using on every function call. Still, it is an improvement.

I tested the new version of the library on Teensy 2.0 with hardware SPI and +5V (worked fine) and again with Teensy 3.0 with hardware SPI and +3V3 (with default SPI pins, also worked fine, and was noticeably faster). I didn't try it with the Teensy 3.x alternate SPI pins.

It would be worthwhile including the new version of the library, from GitHub, in Teensyduino 1.20 and noting on the Teensyduino libraries page that this version works on Teensy 3.x.
https://github.com/adafruit/Adafruit_SSD1306
 
Last edited:
Works fine using alternate SPI pin mappings, too. Moving SCLK away from pin 13 has the benefit of not flashing the LED during SPI activity.

Code:
  /* Now use alternate SPI pin mappings */
  SPI.setMOSI(7);
  SPI.setSCK(14);
 
I made a benchmark by stripping out all the delays from the example program, and saving the value of micros() before each set of drawing tests. I ran this on Teensy 3.0 with software (bit-banged) SPI and hardware SPI. Surprised to note a very uniform 2.5 times speedup irrespective of drawing primitive; the main factor seems to be the SPI transmission speed. (The startup code includes some small delays while bringing up the power, so the speedup is less.)

Testing with one of the fast T3 SPI libraries may be worthwhile.

Raw data
Code:
Software SPI
Startup 17428
Lines 1270543
Rect 1377956
Rects 1458572
Circles 1673432
Round rects: 1771471
Filled round rects 1873425
Trainges 1917736
Filled triangles 1962642
Chars 1982015
Scroll 1989001
Text 1997812
Bitmap 2004264

Hardware SPI
Startup 13397
Lines 502267
Rect 541596
Rects 572232
Circles 657877
Round rects: 696428
Filled round rects 736241
Trainges 753910
Filled triangles 771132
Chars 782700
Scroll 785725
Text 790663
Bitmap 793319

Speedup (compared to SW)
Startup 1.30x
Lines 2.53x
Rect 2.54x
Rects 2.55x
Circles 2.54x
Round rects 2.54x
Filled round rects 2.54x
Trianges 2.54x
Filled triangles 2.55x
Chars 2.53x
Scroll 2.53x
Text 2.53x
Bitmap 2.53x

Code:
Code:
/*********************************************************************
 * This is an example for our Monochrome OLEDs based on SSD1306 drivers
 * 
 * Pick one up today in the adafruit shop!
 * ------> http://www.adafruit.com/category/63_98
 * 
 * This example is for a 128x64 size display using SPI to communicate
 * 4 or 5 pins are required to interface
 * 
 * Adafruit invests time and resources providing this open source code, 
 * please support Adafruit and open-source hardware by purchasing 
 * products from Adafruit!
 * 
 * Written by Limor Fried/Ladyada  for Adafruit Industries.  
 * BSD license, check license.txt for more information
 * All text above, and the splash screen must be included in any redistribution
 *********************************************************************/

#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// If using software SPI (the default case):
/*
#define OLED_MOSI   9
#define OLED_CLK   10 

#define OLED_DC    11
#define OLED_CS    12
#define OLED_RESET 13
 Adafruit_SSD1306 display(OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
 */

/* Uncomment this block to use hardware SPI */
 #define OLED_DC     6
 #define OLED_CS     7
 #define OLED_RESET  8
 Adafruit_SSD1306 display(OLED_DC, OLED_RESET, OLED_CS);
 

uint32_t times[20];

#define NUMFLAKES 10
#define XPOS 0
#define YPOS 1
#define DELTAY 2

#define LOGO16_GLCD_HEIGHT 16 
#define LOGO16_GLCD_WIDTH  16 
static const unsigned char PROGMEM logo16_glcd_bmp[] =
{ 
  B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000 };

#if (SSD1306_LCDHEIGHT != 64)
#error("Height incorrect, please fix Adafruit_SSD1306.h!");
#endif

void setup()   {                
  Serial.begin(9600);
  delay(3000);
  Serial.println("Starting");

  //starting time
  times[0] = micros();

  // by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
  display.begin(SSD1306_SWITCHCAPVCC);
  // init done

  display.display(); // show splashscreen
  display.clearDisplay();   // clears the screen and buffer

  //after initialization
  times[1] = micros();

  // draw a single pixel
  display.drawPixel(10, 10, WHITE);
  display.display();
  display.clearDisplay();

  // draw many lines
  testdrawline();
  display.display();
  display.clearDisplay();

  //after lines
  times[2] = micros();

  // draw rectangles
  testdrawrect();
  display.display();
  display.clearDisplay();
  //after rect
  times[3] = micros();

  // draw multiple rectangles
  testfillrect();
  display.display();
  display.clearDisplay();
  //after rects
  times[4]= micros();

  // draw mulitple circles
  testdrawcircle();
  display.display();
  display.clearDisplay();
  //after circles
  times[5] = micros();

  testdrawroundrect();
  display.clearDisplay();
  //after rrect
  times[6] = micros();

  testfillroundrect();
  display.clearDisplay();
  //after frrect
  times[7] = micros();

  testdrawtriangle();
  display.clearDisplay();
  //after tri
  times[8] = micros();

  testfilltriangle();
  display.clearDisplay();
  //after ftri
  times[9] = micros();

  // draw the first ~12 characters in the font
  testdrawchar();
  display.display();
  display.clearDisplay();
  //after chars
  times[10] = micros();

  // draw scrolling text
  testscrolltext();
  display.clearDisplay();
  //after scroll
  times[11] = micros();

  // text display tests
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);
  display.println("Hello, world!");
  display.setTextColor(BLACK, WHITE); // 'inverted' text
  display.println(3.141592);
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.print("0x"); 
  display.println(0xDEADBEEF, HEX);
  display.display();
  //after text
  times[12] = micros();

  // miniature bitmap display
  display.clearDisplay();
  display.drawBitmap(30, 16,  logo16_glcd_bmp, 16, 16, 1);
  display.display();
  //after bitmap
  times[13] = micros();

  // now output the data
  Serial.print("Startup ");
  Serial.println(times[1] - times[0]);
  Serial.print("Lines ");
  Serial.println(times[2] - times[0]);
  Serial.print("Rect ");
  Serial.println(times[3] - times[0]);
  Serial.print("Rects ");
  Serial.println(times[4] - times[0]);
  Serial.print("Circles ");
  Serial.println(times[5] - times[0]);
  Serial.print("Round rects: ");
  Serial.println(times[6] - times[0]);
  Serial.print("Filled round rects ");
  Serial.println(times[7] - times[0]);
  Serial.print("Trainges ");
  Serial.println(times[8] - times[0]);
  Serial.print("Filled triangles ");
  Serial.println(times[9] - times[0]);
  Serial.print("Chars ");
  Serial.println(times[10] - times[0]);
  Serial.print("Scroll ");
  Serial.println(times[11] - times[0]);
  Serial.print("Text ");
  Serial.println(times[12] - times[0]);
  Serial.print("Bitmap ");
  Serial.println(times[13] - times[0]);


}


void loop() {

}


void testdrawbitmap(const uint8_t *bitmap, uint8_t w, uint8_t h) {
  uint8_t icons[NUMFLAKES][3];

  // initialize
  for (uint8_t f=0; f< NUMFLAKES; f++) {
    icons[f][XPOS] = random(display.width());
    icons[f][YPOS] = 0;
    icons[f][DELTAY] = random(5) + 1;

    Serial.print("x: ");
    Serial.print(icons[f][XPOS], DEC);
    Serial.print(" y: ");
    Serial.print(icons[f][YPOS], DEC);
    Serial.print(" dy: ");
    Serial.println(icons[f][DELTAY], DEC);
  }

  while (1) {
    // draw each icon
    for (uint8_t f=0; f< NUMFLAKES; f++) {
      display.drawBitmap(icons[f][XPOS], icons[f][YPOS], logo16_glcd_bmp, w, h, WHITE);
    }
    display.display();
    delay(200);

    // then erase it + move it
    for (uint8_t f=0; f< NUMFLAKES; f++) {
      display.drawBitmap(icons[f][XPOS], icons[f][YPOS],  logo16_glcd_bmp, w, h, BLACK);
      // move it
      icons[f][YPOS] += icons[f][DELTAY];
      // if its gone, reinit
      if (icons[f][YPOS] > display.height()) {
        icons[f][XPOS] = random(display.width());
        icons[f][YPOS] = 0;
        icons[f][DELTAY] = random(5) + 1;
      }
    }
  }
}


void testdrawchar(void) {
  display.setTextSize(1);
  display.setTextColor(WHITE);
  display.setCursor(0,0);

  for (uint8_t i=0; i < 168; i++) {
    if (i == '\n') continue;
    display.write(i);
    if ((i > 0) && (i % 21 == 0))
      display.println();
  }    
  display.display();
}

void testdrawcircle(void) {
  for (int16_t i=0; i<display.height(); i+=2) {
    display.drawCircle(display.width()/2, display.height()/2, i, WHITE);
    display.display();
  }
}

void testfillrect(void) {
  uint8_t color = 1;
  for (int16_t i=0; i<display.height()/2; i+=3) {
    // alternate colors
    display.fillRect(i, i, display.width()-i*2, display.height()-i*2, color%2);
    display.display();
    color++;
  }
}

void testdrawtriangle(void) {
  for (int16_t i=0; i<min(display.width(),display.height())/2; i+=5) {
    display.drawTriangle(display.width()/2, display.height()/2-i,
    display.width()/2-i, display.height()/2+i,
    display.width()/2+i, display.height()/2+i, WHITE);
    display.display();
  }
}

void testfilltriangle(void) {
  uint8_t color = WHITE;
  for (int16_t i=min(display.width(),display.height())/2; i>0; i-=5) {
    display.fillTriangle(display.width()/2, display.height()/2-i,
    display.width()/2-i, display.height()/2+i,
    display.width()/2+i, display.height()/2+i, WHITE);
    if (color == WHITE) color = BLACK;
    else color = WHITE;
    display.display();
  }
}

void testdrawroundrect(void) {
  for (int16_t i=0; i<display.height()/2-2; i+=2) {
    display.drawRoundRect(i, i, display.width()-2*i, display.height()-2*i, display.height()/4, WHITE);
    display.display();
  }
}

void testfillroundrect(void) {
  uint8_t color = WHITE;
  for (int16_t i=0; i<display.height()/2-2; i+=2) {
    display.fillRoundRect(i, i, display.width()-2*i, display.height()-2*i, display.height()/4, color);
    if (color == WHITE) color = BLACK;
    else color = WHITE;
    display.display();
  }
}

void testdrawrect(void) {
  for (int16_t i=0; i<display.height()/2; i+=2) {
    display.drawRect(i, i, display.width()-2*i, display.height()-2*i, WHITE);
    display.display();
  }
}

void testdrawline() {  
  for (int16_t i=0; i<display.width(); i+=4) {
    display.drawLine(0, 0, i, display.height()-1, WHITE);
    display.display();
  }
  for (int16_t i=0; i<display.height(); i+=4) {
    display.drawLine(0, 0, display.width()-1, i, WHITE);
    display.display();
  }

  display.clearDisplay();
  for (int16_t i=0; i<display.width(); i+=4) {
    display.drawLine(0, display.height()-1, i, 0, WHITE);
    display.display();
  }
  for (int16_t i=display.height()-1; i>=0; i-=4) {
    display.drawLine(0, display.height()-1, display.width()-1, i, WHITE);
    display.display();
  }

  display.clearDisplay();
  for (int16_t i=display.width()-1; i>=0; i-=4) {
    display.drawLine(display.width()-1, display.height()-1, i, 0, WHITE);
    display.display();
  }
  for (int16_t i=display.height()-1; i>=0; i-=4) {
    display.drawLine(display.width()-1, display.height()-1, 0, i, WHITE);
    display.display();
  }

  display.clearDisplay();
  for (int16_t i=0; i<display.height(); i+=4) {
    display.drawLine(display.width()-1, 0, 0, i, WHITE);
    display.display();
  }
  for (int16_t i=0; i<display.width(); i+=4) {
    display.drawLine(display.width()-1, 0, i, display.height()-1, WHITE); 
    display.display();
  }

}

void testscrolltext(void) {
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(10,0);
  display.clearDisplay();
  display.println("scroll");
  display.display();

  display.startscrollright(0x00, 0x0F);
  display.stopscroll();
  display.startscrollleft(0x00, 0x0F);
  display.stopscroll();   
  display.startscrolldiagright(0x00, 0x07);
  display.startscrolldiagleft(0x00, 0x07);
  display.stopscroll();
}
 
I should have mentioned that this benchmarking was done with Teensyduino 1.20-rc1 and the stock Adafruit SSD1306 library supplied with -rc1.
 
Update for anyone following this thread: Paul included a recent Adafruit_SSD1306 and Adafruit_GFX in Teensyduino 1.20. This dealt with a bunch of the issues reported on this forum during 2013 about these displays.

Some comments based on the current GitHub version:

I2C is still at 100kHz, because they test for Due before setting to 400kHz:
Code:
 // I2C Init
Wire.begin();
#ifdef __SAM3X8E__
// Force 400 KHz I2C, rawr! (Uses pins 20, 21 for SDA, SCL)
TWI1->TWI_CWGR = 0;
TWI1->TWI_CWGR = ((VARIANT_MCK / (2 * 400000)) - 4) * 0x101;
#endif

Based on this post it seems the TWBR emulation could be used to set I2C to 400kHz.

Hardware SPI is not using transactions and is using fixed clock multipliers to set the speed:
Code:
if (hwSPI){
SPI.begin ();
#ifdef __SAM3X8E__
SPI.setClockDivider (9); // 9.3 MHz
#else
SPI.setClockDivider (SPI_CLOCK_DIV2); // 8 MHz
#endif
}

It seems that this could check for SPI transactions and then, if supported, use SPISettings. This would also allow the SPI bus to be shared with another SPI device that uses a different mode or clockspeed, and would cope better with higher Teensy clock speeds. Much of the drawing time is spent updating the display buffer in RAM on the teensy; SPI is only used for display initialization, update, setting contrast, and setting hardware scrolling. The SSD1306 datasheet indicates a minimum SPI clock period of 100ns, or 10Mhz, so 8Mhz is about right.

The latest version on GitHub also supports three colors (BLACK, WHITE, INVERSE), the latter being an XOR paint.
 
I only have an SPI display so I can't verify which of I2C or software SPI is faster. Maybe someone who has both could run the benchmark above (editing the constructor and the pin number sor I2C) and report the relative timings?
 
Here is an updated documentation page for the SSD1306 library. Changes:

- more explanation in the introduction
- clarify software vs. hardware SPI
- basic usage section with constructors and SSD1306-accelerated commands
- discuss screen orientation
- mention custom splash screens

Changes that could be made, but have not (yet)
- describe hardware scrolling
- correct the navigation sidebar and breadcrumbs, both of which claim that this is the ST7565 library :)
- saying whether I2C is faster or slower than bit-banged SPI

The first one because I have not done it yet, the second because I suspect all that navigation is generated server-side and the third one because I don't have the data.

Page is a zip because the forum does not allow html attachments.
View attachment td_libs_SSD1306.zip
 
Status
Not open for further replies.
Back
Top