OLED SSD1306 display library for whoever wants

I have been looking for some easy to use librabries to use the above display and in the end I wrote my own. One big problem is the speed of the Wire library. I have seen a lot of code out there that does not understand how to manipulate the speed register and results in ridiculous rates that cannot be supported by anything other than shortest wires and low value, external pull up resistors. The best speed is 100K or 400K. Anything higher and you are asking for trouble with random crashes (Wire library hanging) and so on. I wrote my own small library that is the quickest I have seen as well as simple and small. It is given here, for whoever wants. Some portions copied from other libraries from I cannot even remember. The fonts were taken from the ardafruit I remember.

Header:

#include <Arduino.h>
#include <Print.h>


#define SSD1306_LCDWIDTH  128
#define SSD1306_LCDHEIGHT  64

#define SSD1306_SETCONTRAST 0x81
#define SSD1306_DISPLAYALLON_RESUME 0xA4
#define SSD1306_DISPLAYALLON 0xA5
#define SSD1306_NORMALDISPLAY 0xA6
#define SSD1306_INVERTDISPLAY 0xA7
#define SSD1306_DISPLAYOFF 0xAE
#define SSD1306_DISPLAYON 0xAF

#define SSD1306_SETDISPLAYOFFSET 0xD3
#define SSD1306_SETCOMPINS 0xDA

#define SSD1306_SETVCOMDETECT 0xDB

#define SSD1306_SETDISPLAYCLOCKDIV 0xD5
#define SSD1306_SETPRECHARGE 0xD9

#define SSD1306_SETMULTIPLEX 0xA8

#define SSD1306_SETLOWCOLUMN 0x00
#define SSD1306_SETHIGHCOLUMN 0x10

#define SSD1306_COLUMNADDR 0x21
#define SSD1306_PAGEADDR   0x22

#define SSD1306_SETSTARTLINE 0x40
#define SSD1306_SETSTARTPAGE 0XB0
#define SSD1306_MEMORYMODE 0x20

#define SSD1306_COMSCANINC 0xC0
#define SSD1306_COMSCANDEC 0xC8

#define SSD1306_SEGREMAP 0xA0

#define SSD1306_CHARGEPUMP 0x8D

#define SSD1306_EXTERNALVCC 0x1
#define SSD1306_SWITCHCAPVCC 0x2


class SSD1306_Text_Basic
: 
public Print
{
public:
  SSD1306_Text_Basic(uint8_t I2C_Address, uint8_t pin_RST, uint8_t vcc_state, uint32_t frequency);

  void begin();
  void clearDisplay();
  boolean setWrap(boolean wrap);
  void setCursor(uint8_t row, uint8_t col);
  void dimDisplay(boolean dim);
  void invertDisplay(boolean invert);

  void erase(uint8_t row, uint8_t col);
  void erase(uint8_t row);
  void erase(uint8_t row,uint8_t col,uint8_t len);
  
  // temp
	uint16_t getFontSize() const;
  // override from Print
  virtual size_t write(uint8_t);
  virtual size_t write(const uint8_t *buffer, size_t size);

private:
  void ssd1306_init();
  void ssd1306_command(uint8_t c);
  void ssd1306_data(uint8_t c);
  void ssd1306_data(const uint8_t *pdata, size_t len);
  void advanceColumn();

private:
  uint8_t _I2C_address;
  uint8_t _pin_RST;
  uint8_t _vcc_state;
  uint32_t _frequency;

  uint8_t _col, _row;
  boolean _wrap;
};

Code

#include <Wire.h>

#include "SSD1306_Text_Basic.h"


extern const uint8_t ucFont5X7[];
extern const uint16_t uiFont5X7Len;

uint16_t SSD1306_Text_Basic::getFontSize() const
{
	return uiFont5X7Len;
}

uint8_t display_buf[SSD1306_LCDHEIGHT/8 * SSD1306_LCDWIDTH/6];

SSD1306_Text_Basic::SSD1306_Text_Basic(uint8_t I2C_address, uint8_t pin_RST, uint8_t vcc_state, uint32_t frequency)
{
  _I2C_address = I2C_address;
  _pin_RST = pin_RST;
  _vcc_state = vcc_state;
  _frequency = frequency;

  _col = 0;
  _row = 0;
  _wrap = false;
}

void SSD1306_Text_Basic::ssd1306_init() 
{
  // Init sequence for 128x64 OLED module
  ssd1306_command(SSD1306_DISPLAYOFF);                    // 0xAE
  ssd1306_command(SSD1306_SETDISPLAYCLOCKDIV);            // 0xD5
  ssd1306_command(0x80);                                  // the suggested ratio 0x80
  ssd1306_command(SSD1306_SETMULTIPLEX);                  // 0xA8
  ssd1306_command(0x3F);
  ssd1306_command(SSD1306_SETDISPLAYOFFSET);              // 0xD3
  ssd1306_command(0x0);                                   // no offset
  ssd1306_command(SSD1306_SETSTARTLINE | 0x0);            // line #0
  ssd1306_command(SSD1306_CHARGEPUMP);                    // 0x8D
  if (_vcc_state == SSD1306_EXTERNALVCC) 
  { 
    ssd1306_command(0x10); 
  }
  else 
  { 
    ssd1306_command(0x14); 
  }
  ssd1306_command(SSD1306_MEMORYMODE);                    // 0x20
  ssd1306_command(0x00);                                  // 0x0 act like ks0108
  ssd1306_command(SSD1306_SEGREMAP | 0x1);
  ssd1306_command(SSD1306_COMSCANDEC);
  ssd1306_command(SSD1306_SETCOMPINS);                    // 0xDA
  ssd1306_command(0x12);
  ssd1306_command(SSD1306_SETCONTRAST);                   // 0x81
  if (_vcc_state == SSD1306_EXTERNALVCC) 
  { 
    ssd1306_command(0x9F); 
  }
  else 
  { 
    ssd1306_command(0xCF); 
  }
  ssd1306_command(SSD1306_SETPRECHARGE);                  // 0xd9
  if (_vcc_state == SSD1306_EXTERNALVCC) 
  { 
    ssd1306_command(0x22); 
  }
  else 
  { 
    ssd1306_command(0xF1); 
  }
  ssd1306_command(SSD1306_SETVCOMDETECT);                 // 0xDB
  ssd1306_command(0x40);
  ssd1306_command(SSD1306_DISPLAYALLON_RESUME);           // 0xA4
  ssd1306_command(SSD1306_NORMALDISPLAY);                 // 0xA6

  ssd1306_command(SSD1306_DISPLAYON);//--turn on oled panel
  
  clearDisplay();
}

void SSD1306_Text_Basic::begin() 
{
  _col = 0;
  _row = 0;
  _wrap = false;

  if (_pin_RST!=0xff) pinMode(_pin_RST, OUTPUT);

  Wire.begin();
  Wire.setClock(_frequency);
  
  // Reset
  if (_pin_RST!=0xff)
  {
    digitalWrite(_pin_RST, HIGH);
    delay(1);
    digitalWrite(_pin_RST, LOW);
    delay(10);
    digitalWrite(_pin_RST, HIGH);
  }
  ssd1306_init();
}

void SSD1306_Text_Basic::ssd1306_command(uint8_t c) 
{
  uint8_t control = 0x00;   // Co = 0, D/C = 0
  Wire.beginTransmission(_I2C_address);
  Wire.write(control);
  Wire.write(c);
  Wire.endTransmission();
}

void SSD1306_Text_Basic::ssd1306_data(uint8_t c) 
{
  uint8_t control = 0x40;   // Co = 0, D/C = 1
  Wire.beginTransmission(_I2C_address);
  Wire.write(control);
  Wire.write(c);
  Wire.endTransmission();
}

void SSD1306_Text_Basic::ssd1306_data(const uint8_t *pdata, size_t len)
{
  uint8_t control = 0x40;   // Co = 0, D/C = 1
  Wire.beginTransmission(_I2C_address);
  Wire.write(control);
  Wire.write(pdata,len);
  Wire.endTransmission();
}

void SSD1306_Text_Basic::clearDisplay() 
{
  ssd1306_command(SSD1306_COLUMNADDR);
  ssd1306_command(0);   // Column start address (0 = reset)
  ssd1306_command(SSD1306_LCDWIDTH-1); // Column end address (127 = reset)

  ssd1306_command(SSD1306_PAGEADDR);
  ssd1306_command(0); // Page start address (0 = reset)
  ssd1306_command(7); // Page end address

  for (uint16_t i=0; i<(SSD1306_LCDWIDTH*SSD1306_LCDHEIGHT/8); i++) 
  {
    Wire.beginTransmission(_I2C_address);
    Wire.write(0x40);
    for (uint8_t x=0; x<16; x++) 
    {
      Wire.write(0x00);
      i++;
    }
    i--;
    Wire.endTransmission();
  }
  setCursor(0,0);
}

boolean SSD1306_Text_Basic::setWrap(boolean wrap)
{
  boolean oldwrap=_wrap;
  _wrap=wrap;
  return oldwrap;
}

void SSD1306_Text_Basic::dimDisplay(boolean dim)
{
  uint8_t contrast;

  if (dim) {
    contrast = 0; // Dimmed display
  } else {
    if (_vcc_state == SSD1306_EXTERNALVCC) {
      contrast = 0x9F;
    } else {
      contrast = 0xCF;
    }
  }
  // the range of contrast to too small to be really useful
  // it is useful to dim the display
  ssd1306_command(SSD1306_SETCONTRAST);
  ssd1306_command(contrast);
}

void SSD1306_Text_Basic::invertDisplay(boolean invert) 
{
  if (invert) 
  {
    ssd1306_command(SSD1306_INVERTDISPLAY);
  } else 
  {
    ssd1306_command(SSD1306_NORMALDISPLAY);
  }
}

void SSD1306_Text_Basic::erase(uint8_t row, uint8_t col)
{
	setCursor(row,col);
	write(' ');
}

void SSD1306_Text_Basic::erase(uint8_t row)
{
	setCursor(row,0);
	memset(display_buf,' ',SSD1306_LCDWIDTH);
	write(display_buf,SSD1306_LCDWIDTH);
	setCursor(row,0);
}

void SSD1306_Text_Basic::erase(uint8_t row,uint8_t col,uint8_t len)
{
	setCursor(row,col);
	if (len > SSD1306_LCDHEIGHT * SSD1306_LCDWIDTH) len=SSD1306_LCDHEIGHT * SSD1306_LCDWIDTH;
	memset(display_buf,' ',len);
	write(display_buf,len);
	setCursor(row,col);
}

void SSD1306_Text_Basic::setCursor(uint8_t row, uint8_t col) 
{
  // font glyph is 6X8 (Font5X7.c)
  row = row  % (SSD1306_LCDHEIGHT/8);
  col = col % (SSD1306_LCDWIDTH/6);
  if (row != _row)
  {
    _row=row;
    ssd1306_command(SSD1306_SETSTARTPAGE | _row);
  }
  if (col!=_col)
  {
    _col=col;
    ssd1306_command(SSD1306_SETLOWCOLUMN | ((_col*6) & 0XF));
    ssd1306_command(SSD1306_SETHIGHCOLUMN | ((_col*6) >> 4));
  }
}

void SSD1306_Text_Basic::advanceColumn()
{
  _col++;
  if (_col >= SSD1306_LCDWIDTH/6)
	if (_wrap)
		setCursor(_row+1,0);
		else _col--;
}

size_t SSD1306_Text_Basic::write(uint8_t c) 
{
  if (c == '\n')
  {
    setCursor(_row+1,_col);
  } 
  else if (c == '\r') 
  {
    setCursor(_row,0);  
  } 
  else 
  {
    if (_col==SSD1306_LCDWIDTH/6) return 0;
    const uint8_t *base = ucFont5X7 + 5 * c; // 5 bytes each char, means 5 vertical pixels, 5 columns
    uint8_t buf[6];

    for (uint8_t i = 0; i < 5 ; i++ )
    {
      buf[i] = pgm_read_byte(base + i);
    }
    buf[5]=0;
    ssd1306_data(buf,6);
    advanceColumn();
  }
  return 1;
}

size_t SSD1306_Text_Basic::write(const uint8_t *buffer, size_t size)
{
#if 0
	for (size_t i=0;i<size;i++) write(buffer[i]);
	return size;
#else
  size_t cn = 0;
  int i,j;
  while (size > 0)
  {
    int num_chars = min(5, size); // hardcoding 5 chars max before the SSD1306 coughs up
    if (_col + num_chars >= SSD1306_LCDWIDTH/6) 
    {
      num_chars = SSD1306_LCDWIDTH/6 - _col;
    }
    if (num_chars == 0)
    {
      if (_wrap) { advanceColumn(); continue; }
      else break;
    }
    // if we have specials in the chunk buffer
    for (i=0;i<num_chars;i++)
      if (buffer[cn+i]==13 || buffer[cn+i]==10) break;
    if (i<num_chars) // have specials
    {
      for (i=0;i<num_chars;i++)
        write(buffer[cn++]);
      size-=num_chars;
      continue;
    }
    byte byte_buf[30]; // max 5 chars
    for (i=0;i<num_chars;i++)
    {
      const uint8_t *base = ucFont5X7 + 5 * buffer[cn++];
      for (j=0;j<5;j++)
        byte_buf[i*6+j] = pgm_read_byte(base + j);
      byte_buf[i*6+5]=0;
    }
    ssd1306_data(byte_buf, num_chars*6);
    size-=num_chars;
    _col+=num_chars;
    // if (_col==SSD1306_LCDWIDTH/6 && _wrap) advanceColumn();
  }
  return cn;
#endif  
}

Example wrapper

#include "debug.h"

#include "boards.h"
#include "pinAllocations.h"

#include "Wire.h"
#include <SSD1306_Text_Basic.h>

SSD1306_Text_Basic *pdisplay;

void SSD1306_Init()
{
  pdisplay = new SSD1306_Text_Basic(0x3C, 0xff, SSD1306_SWITCHCAPVCC, 400000L);
  SSD1306_Text_Basic &display = *pdisplay;
  display.begin();
#ifdef DEBUG222
  snprintf( __debug_buf, sizeof( __debug_buf), "display has %u bytes\r\n", display.getFontSize());
  Serial.print( __debug_buf);
#endif


}

void SSD1306_ClearScreen()
{
  SSD1306_Text_Basic &display = *pdisplay;
  display.clearDisplay();
}

void SSD1306_DimScreen(boolean bDim)
{
  SSD1306_Text_Basic &display = *pdisplay;
  display.dimDisplay(bDim);
}

void SSD1306_Erase(int row)
{
  SSD1306_Text_Basic &display = *pdisplay;
  display.erase(row);
}

void SSD1306_Print(int row, int col, const char *buf, ...)
{
  va_list args;
  va_start (args, buf);
  char buffer[128];
  vsprintf(buffer, buf, args);
  SSD1306_Text_Basic &display = *pdisplay;
  display.setCursor(row, col);
  display.print(buffer);
}

void SSD1306_Print(int row, int col, const __FlashStringHelper *buf, ...)
{
  char RAMbuffer[64];
  char *ptr = ( char * ) buf;
  size_t stLen = 0;
  while ( (RAMbuffer[stLen++] = pgm_read_byte( ptr++ ) ) != '\0' && stLen < 63);
  RAMbuffer[63] = '\0';
  va_list args;
  va_start (args, buf);
  // int iValue=va_arg(args,int);
  char buffer[64];
  vsnprintf(buffer, 64, RAMbuffer, args);
  //sprintf(buffer,RAMbuffer,iValue);
  SSD1306_Text_Basic &display = *pdisplay;
  display.setCursor(row, col);
  display.print(buffer);
  va_end(args);
}

Example lines of random code on how to use it:

        SSD1306_Print(7, 0, F("%0.2u/%0.2u/%0.2u %0.2u:%0.2u:%0.2u"),
                      day(), month(), year(),
                      hour(), minute(), second());



        SSD1306_Print(7, 0, F("Frequency :%0.6ld"), (long)dFrequency);



    SSD1306_Erase(5);
    SSD1306_Erase(6);
    SSD1306_Erase(7);
    SSD1306_Print(5, 0, F("* SETUP *"));
    SSD1306_Print(6, 0, F("min,max,mnl,frq,RTC.."));
    SSD1306_Print(7, 0, F(">min<"));

And so on.

The header file isn't posted; you posted the code twice.

One could modify the Wire library, or write custom code, to improve the speed. I think the main speed limitation is the 32 byte buffer size and increasing that in the library is straightforward. That buffer is why you have a 5 character hard coded buffering for writes.

corrected!