Demonstration: Arduino Driving the CFAL5016A-Y SPI Graphic OLED

CF Tech

Administrator
Here is a quick tutorial showing how to connect a Crystalfontz CFAL5016A-Y 50x16 Graphic OLED Module in SPI mode to an Arduino Uno R3.

Connect some WRJMPY40 or similar wires to the Arduino as shown here:


The other ends push onto the OLD module's header like so:


Load up the Arduino Sketch (Arduino_SPI_OLED_Example_Code.ino):
Code:
//===========================================================================
//
//  Code written for Arduino Uno
//
//  CRYSTALFONTZ CFAL5016A-Y 50x15 Graphic OLED IN SPI MODE
//
//  ref: https://www.crystalfontz.com/product/cfal5016ay
​//
//  video: https://www.youtube.com/watch?v=By0t1mrXsO8
//
//  tutorial: https://forum.crystalfontz.com/showthread.php/7395
//
//  2016 - 01 - 22 Brent A. Crosby
//===========================================================================
//This is free and unencumbered software released into the public domain.
//
//Anyone is free to copy, modify, publish, use, compile, sell, or
//distribute this software, either in source code form or as a compiled
//binary, for any purpose, commercial or non-commercial, and by any
//means.
//
//In jurisdictions that recognize copyright laws, the author or authors
//of this software dedicate any and all copyright interest in the
//software to the public domain. We make this dedication for the benefit
//of the public at large and to the detriment of our heirs and
//successors. We intend this dedication to be an overt act of
//relinquishment in perpetuity of all present and future rights to this
//software under copyright law.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
//MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
//IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
//OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
//ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//OTHER DEALINGS IN THE SOFTWARE.
//
//For more information, please refer to <http://unlicense.org/>
//===========================================================================
//Software SPI (10-bit transfers, difficult to do using the hardware SPI)
#define SPIPORT (PORTB)
#define SPITOGGLE (PINB)
// PB5 (0x20) is SCK  (output) green  OLED pin 12
#define SPI_SCK_PIN (13)
#define SCK_MASK (0x20)
#define CLR_SCK (PORTB &= ~(SCK_MASK))
#define SET_SCK (PORTB |=  (SCK_MASK))
// PB4 (0x10) is MISO (input)  blue   OLED pin 13
//(reference only, it is an input)
#define SPI_MISO_PIN (12)
#define MISO_MASK (0x10)
#define CLR_MISO (PORTB &= ~(MISO_MASK))
#define SET_MISO (PORTB |=  (MISO_MASK))
// PB3 (0x08) is MOSI (output) violet OLED pin 14
#define SPI_MOSI_PIN (11)
#define MOSI_MASK (0x08)
#define CLR_MOSI (PORTB &= ~(MOSI_MASK))
#define SET_MOSI (PORTB |=  (MOSI_MASK))
// DB2 (0x04) is SS   (output) gray   OLED pin 15
#define SPI_SS_PIN (10)
#define SS_MASK (0x04)
#define CLR_SS  (PORTB &= ~(SS_MASK))
#define SET_SS  (PORTB |=  (SS_MASK))

#define DATA (1)
#define COMMAND (0)
//============================================================================
// Graphic data to display on LCD.
// Image2Code may be helpful to create your own screen image:
//    https://forum.crystalfontz.com/showthread.php/5854
//    http://sourceforge.net/p/image2code/code/HEAD/tree/
const char LOGO_Screen[2][50] PROGMEM =
  {
    {0xF0,0xF0,0xF4,0xF6,0xF3,0xF1,0xF3,0x02,0xF8,0xFC,0xFE,0x03,0x51,0x53,
     0x56,0x54,0x50,0x50,0x50,0x00,0x00,0xF0,0xFC,0x1E,0x0F,0x07,0x07,0x07,
     0x0F,0x1E,0x08,0x00,0xFF,0xFF,0xC7,0xC7,0xC7,0xC7,0xC7,0x07,0x00,0x80,
     0xF8,0x7F,0x07,0x7F,0xF8,0x80,0x00,0x00},
    {0x07,0x07,0x17,0x37,0x67,0x47,0x67,0x20,0x0F,0x1F,0x3F,0x60,0x45,0x65,
     0x35,0x15,0x05,0x05,0x05,0x00,0x00,0x0F,0x3F,0x78,0xF0,0xE0,0xE0,0xE0,
     0xF0,0x78,0x10,0x00,0xFF,0xFF,0x01,0x01,0x01,0x01,0x01,0xC0,0xFC,0x3F,
     0x0F,0x0E,0x0E,0x0E,0x0F,0x3F,0xFC,0xC0}
  };
const char OLED_Screen[2][50] PROGMEM =
  {
    {0xE0,0xF8,0xFC,0x0E,0x03,0x01,0x01,0x01,0x03,0x0E,0xFC,0xF8,0xE0,0x01,
     0x01,0xFF,0xFF,0xFF,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0xFF,
     0xFF,0xFF,0x81,0xC1,0xF9,0x03,0x07,0x1F,0x00,0x01,0x01,0xFF,0xFF,0xFF,
     0x01,0x01,0x01,0x03,0x0E,0xFC,0xF8,0xE0},
    {0x07,0x1F,0x3F,0x70,0xC0,0x80,0x80,0x80,0xC0,0x70,0x3F,0x1F,0x07,0x80,
     0x80,0xFF,0xFF,0xFF,0x80,0x80,0x80,0x80,0xC0,0xF0,0x1C,0x80,0x80,0xFF,
     0xFF,0xFF,0x80,0x81,0x8F,0x80,0xC0,0xC0,0x78,0x80,0x80,0xFF,0xFF,0xFF,
     0x80,0x80,0x80,0xC0,0x70,0x3F,0x1F,0x07}
  };
const char TEXT_Screen[2][50] PROGMEM =
  {
    {0x00,0x0E,0x11,0x11,0x00,0x9F,0x85,0x9A,0x80,0x83,0x9C,0x03,0x00,0x16,
     0x95,0x8D,0x80,0x81,0x9F,0x01,0x00,0x1E,0x05,0x1E,0x00,0x1F,0x10,0x10,
     0x00,0x1F,0x05,0x01,0x80,0x0E,0x11,0x0E,0x00,0x1F,0x02,0x9F,0x80,0x81,
     0x1F,0x01,0x00,0x19,0x15,0x13,0x00,0x00},
    {0x00,0x00,0x00,0x00,0x00,0x4F,0xCF,0x88,0x88,0xF8,0x70,0x00,0x00,0x7F,
     0xFF,0xB0,0x9C,0x86,0xFF,0x7F,0x00,0x84,0xCC,0x78,0x30,0x78,0xCC,0x84,
     0x00,0x82,0x82,0xFF,0xFF,0x80,0x80,0x00,0x00,0x7E,0xFF,0x89,0x88,0xF8,
     0x70,0x00,0x00,0x00,0x00,0x00,0x00,0x00}
  };
const char CHECK_Screen[2][50] PROGMEM =
  {
    {0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
     0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
     0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
     0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55},
    {0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
     0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
     0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55,
     0xAA,0x55,0xAA,0x55,0xAA,0x55,0xAA,0x55}
  };
const char VLINES_Screen[2][50] PROGMEM =
  {
    {0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,
     0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,
     0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,
     0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00},
    {0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,
     0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,
     0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,
     0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00}
  };
const char HLINES_Screen[2][50] PROGMEM =
  {
    {0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
     0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
     0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
     0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA},
    {0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
     0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
     0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,
     0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA,0xAA}
  };
//============================================================================
void write_to_OLED_SPI(uint8_t destination,uint8_t data)
  {
  //Bits sent in this order:
  //  data(1)/command(0) flag
  //  read(1)/write(0) flag
  //  data.7 (msb)
  //  data.6
  //  data.5
  //  data.4
  //  data.3
  //  data.2
  //  data.1
  //  data.0 (lsb)

#if(1)
  //Pretty fast software SPI 10-bit transfer code
  //Several ideas taken from the fastest non-assembly implementation at:
  //http://nerdralph.blogspot.com/2015/03/fastest-avr-software-spi-in-west.html
  //
  // The SS pin is low for ~3.8uS, the clock frequency is ~ 2.6MHz
  // 47x faster than above

  //Pre-calculate the value that will drive clk and data low in one cycle.
  //(Note that this is not interrupt safe if an ISR is writing to the same port)
  register uint8_t
    force_clk_and_data_low;

  //Select the chip
  CLR_SS;

  //Pre-calculate the value that will drive clk and data low in one
  //operation.
  force_clk_and_data_low = (SPIPORT & ~(MOSI_MASK | SCK_MASK) );

  //Set clock and data low
  SPIPORT = force_clk_and_data_low;
  //Set the data(1)/command(0) flag
  if(DATA==destination)
    {
    SPITOGGLE = MOSI_MASK;
    }
  //Use a toggle to bring the clock up
  SPITOGGLE = SCK_MASK;

  //Set clock and data low
  SPIPORT = force_clk_and_data_low;
  //MOSI is already 0, for read(1)/write(0) flag as write
  //Use a toggle to bring the clock up
  SPITOGGLE = SCK_MASK;

  //Now clock the 8 bits of data out
  SPIPORT = force_clk_and_data_low;
  if(data & 0x80)
    SPITOGGLE = MOSI_MASK;
  SPITOGGLE = SCK_MASK;
  SPIPORT = force_clk_and_data_low;
  if(data & 0x40)
    SPITOGGLE = MOSI_MASK;
  SPITOGGLE = SCK_MASK;
  SPIPORT = force_clk_and_data_low;
  if(data & 0x20)
    SPITOGGLE = MOSI_MASK;
  SPITOGGLE = SCK_MASK;
  SPIPORT = force_clk_and_data_low;
  if(data & 0x10)
    SPITOGGLE = MOSI_MASK;
  SPITOGGLE = SCK_MASK;
  SPIPORT = force_clk_and_data_low;
  if(data & 0x08)
    SPITOGGLE = MOSI_MASK;
  SPITOGGLE = SCK_MASK;
  SPIPORT = force_clk_and_data_low;
  if(data & 0x04)
    SPITOGGLE = MOSI_MASK;
  SPITOGGLE = SCK_MASK;
  SPIPORT = force_clk_and_data_low;
  if(data & 0x02)
    SPITOGGLE = MOSI_MASK;
  SPITOGGLE = SCK_MASK;
  SPIPORT = force_clk_and_data_low;
  if(data & 0x01)
    SPITOGGLE = MOSI_MASK;
  SPITOGGLE = SCK_MASK;

  //Release the chip
  SET_SS;
#else
  //Straight-forward software SPI 10-bit transfer code, perhaps
  //easier to understand, possibly more portable. Certainly slower.
  //
  // The SS pin is low for ~180uS, the clock frequency is ~57KHz
  // 47x slower than above

  //Select the chip, starting a 10-bit SPI transfer
  digitalWrite(SPI_SS_PIN, LOW);

  //(bit 0) Set the data(1)/command(0) flag
  if(DATA==destination)
    digitalWrite(SPI_MOSI_PIN, HIGH);
  else
    digitalWrite(SPI_MOSI_PIN, LOW);
  //Clock it in.
  digitalWrite(SPI_SCK_PIN, LOW);
  digitalWrite(SPI_SCK_PIN, HIGH);

  //(bit 1) Clear the read(1)/write(0) flag to write, clock it in.
  digitalWrite(SPI_MOSI_PIN, LOW);
  //Clock it in.
  digitalWrite(SPI_SCK_PIN, LOW);
  digitalWrite(SPI_SCK_PIN, HIGH);

  //(bits 2-9) Push each of the 8 data bits out.
  for(uint8_t mask=0x80;mask;mask>>=1)
    {
    //Set the MOSI pin high or low depending on if our mask
    //corresponds to a 1 or 0 in the data.
    if(mask&data)
      {
      digitalWrite(SPI_MOSI_PIN, HIGH);
      }
    else
      {
      digitalWrite(SPI_MOSI_PIN, LOW);
      }
    //Clock it in.
    digitalWrite(SPI_SCK_PIN, LOW);
    digitalWrite(SPI_SCK_PIN, HIGH);
    }

  //Release the chip, ending the 10-bit SPI transfer
  digitalWrite(SPI_SS_PIN, HIGH);
#endif
  }
//===========================================================================
//#define WriteCommand(command)  write_to_OLED_SPI(COMMAND, command)
//#define WriteData(data)    write_to_OLED_SPI(DATA,    data)
//===========================================================================
void position_cursor(uint8_t column, uint8_t line)
  {
  //Set CGRAM Address, RS=0,R/W=0
  // 7654 3210
  // 1AAA AAAA
  //  0x00 to 0x27 => Line 1
  //  0x40 to 0x67 => Line 2
  write_to_OLED_SPI(COMMAND,0x80 | (column&0x7F));
  //Line / Y/8
  write_to_OLED_SPI(COMMAND,0x40 | (line&0x01));
  }
//===========================================================================
// According to the WS0010 data sheet, only the clear display time has
// an appreciable execution time of 6mS. All others are listed at 0.
void clear_display(void)
  {
  //Display Clear RS=0,R/W=0
  // 7654 3210
  // 0000 0001
  write_to_OLED_SPI(COMMAND,0x01);
  _delay_ms(6);
  }
//===========================================================================
void initialize_display()
  {
  //Refer to WS0010 data sheet, page 21
  // https://www.crystalfontz.com/products/document/3176/WS0010.pdf

  //The display controller requests:
  //  "Wait for power stabilization 500ms:
  _delay_ms(500);

  //Function set, RS=0,R/W=0
  // 7654 3210
  // 0011 NFFT
  //  N = lines: N=1 is 2 lines
  //  F = Font: 0 = 5x8, 1 = 5x10
  //  FT = Font Table:
  //     FT=00 is English/Japanese ~"standard" for character LCDs
  //     FT=01 is Western European I fractions, circle-c some arrows
  //     FT=10 is English/Russian
  //     FT=11 is Western European II my favorite, arrows, Greek letters
  write_to_OLED_SPI(COMMAND,0x3B);

  //Graphic vs character mode setting, RS=0,R/W=0
  // 7654 3210
  // 0001 GP11
  //  G = Mode: 1=graphic, 0=character
  //  C = Power: 1=0n, 0=off
  write_to_OLED_SPI(COMMAND,0x1F);

  //Display On/Off Control RS=0,R/W=0
  // 7654 3210
  // 0000 1DCB
  //  D = Display On
  //  C = Cursor On
  //  B = Cursor Blink
  write_to_OLED_SPI(COMMAND,0x0E);

  //Display Clear RS=0,R/W=0
  // 7654 3210
  // 0000 0001
  clear_display();

  //Display home
  write_to_OLED_SPI(COMMAND,0x02);

  //Entry Mode Set RS=0,R/W=0
  // 7654 3210
  // 0000 01IS
  //  I = Increment/or decrement
  //  S = Shift(scroll) data on line
  write_to_OLED_SPI(COMMAND,0x06);

  //Display Clear RS=0,R/W=0
  // 7654 3210
  // 0000 0001
  clear_display();
  }
//===========================================================================
// ref: http://stackoverflow.com/questions/28787177/c-arduino-passing-pointer-to-2d-array-stored-in-progmem-to-a-function
void Send_Logo(const char (&screen)[2][50],uint8_t invert)
  {
  uint8_t
    i;
  uint8_t
    j;
  position_cursor(0,0);
  //Logo
  //Send the image from the flash to the LCD.
  //This "normal" version takes about 3.1mS to fill the screen from the image in
  //flash, using 8MHz SPI transfers.
  for(j=0;j<=1;j++)
    {
    // Point the controller to the correct address. This is the Y coordinate,
    // addressed by 8 pixel / 1 byte horizontal bands.
    position_cursor(0,j);
    //Dump the data out for this line.
    if(invert)
      for(i=0;i<=49;i++)
        {
        write_to_OLED_SPI(DATA,~pgm_read_byte(&screen[j][i]));
        }
      else
        for(i=0;i<=49;i++)
          {
          write_to_OLED_SPI(DATA,pgm_read_byte(&screen[j][i]));
          }
    }
  }
//===========================================================================
void setup()
  {
  //General setup, port directions.
  // PB5 (0x20) is SCK  (output) green  OLED pin 12
  pinMode(SPI_SCK_PIN, OUTPUT);
  // PB4 (0x10) is MISO (input)  blue   OLED pin 13
  //(reference only, it is an input)
  pinMode(SPI_MISO_PIN, INPUT);
  // PB3 (0x08) is MOSI (output) violet OLED pin 14
  pinMode(SPI_MOSI_PIN, OUTPUT);
  // DB2 (0x04) is SS   (output) gray   OLED pin 16
  pinMode(SPI_SS_PIN, OUTPUT);
  }
//===========================================================================
void loop()
  {
  //Simple demo loop

  // Initialize the display
  initialize_display();
  _delay_ms(200);

  Send_Logo(TEXT_Screen,0);
  _delay_ms(1000);
  Send_Logo(LOGO_Screen,0);
  _delay_ms(1000);
  Send_Logo(OLED_Screen,0);
  _delay_ms(1000);
  Send_Logo(CHECK_Screen,0);
  _delay_ms(1000);
  Send_Logo(CHECK_Screen,1);
  _delay_ms(1000);
  Send_Logo(VLINES_Screen,0);
  _delay_ms(1000);
  Send_Logo(VLINES_Screen,1);
  _delay_ms(1000);
  Send_Logo(HLINES_Screen,0);
  _delay_ms(1000);
  Send_Logo(HLINES_Screen,1);
  _delay_ms(1000);
  }
//===========================================================================
The OLED should power up and you should have the it cycling through a couple of screens:


Here is a video of the demonstration in action:

Happy hacking! Be sure to contact us if you have any questions or troubles getting your display going.
Looking for additional LCD resources? Check out our LCD blog for the latest developments in LCD technology.
 
Top