Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 7 of 7

Thread: Adafruit SSD1306, hardware SPI, and Teensy

  1. #1
    Senior Member
    Join Date
    Nov 2012
    Location
    Boston, MA, USA
    Posts
    1,108

    Adafruit SSD1306, hardware SPI, and Teensy

    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-...hlight=ssd1306
    http://forum.pjrc.com/threads/24642-...hlight=ssd1306
    http://forums.adafruit.com/viewtopic...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 by Nantonos; 07-02-2014 at 01:40 AM.

  2. #2
    Senior Member
    Join Date
    Nov 2012
    Location
    Boston, MA, USA
    Posts
    1,108
    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);

  3. #3
    Senior Member
    Join Date
    Nov 2012
    Location
    Boston, MA, USA
    Posts
    1,108
    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();
    }

  4. #4
    Senior Member
    Join Date
    Nov 2012
    Location
    Boston, MA, USA
    Posts
    1,108
    I should have mentioned that this benchmarking was done with Teensyduino 1.20-rc1 and the stock Adafruit SSD1306 library supplied with -rc1.

  5. #5
    Senior Member
    Join Date
    Nov 2012
    Location
    Boston, MA, USA
    Posts
    1,108
    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.

  6. #6
    Senior Member
    Join Date
    Nov 2012
    Location
    Boston, MA, USA
    Posts
    1,108
    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?

  7. #7
    Senior Member
    Join Date
    Nov 2012
    Location
    Boston, MA, USA
    Posts
    1,108
    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.
    td_libs_SSD1306.zip

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •