Introduction: Connecting Arduino Uno to Crystalfontz 16x2 OLED With Only 4 Wires!

About: We manufacturer LCDs, perfect for integrating into consumer products.

These modules are pretty nice. They are the same size as our CFAH1602C series 16x2 Character LCD modules, and thinner as a bonus. The OLED's contrast is fantastic, and they have a crisp look that is hard to beat. Another nice thing is that they have 4 software-selectable character sets, so you won't be stuck wanting for the omega or mu symbol.

Check them out here!

https://www.crystalfontz.com/product/cfal1602c-16x...

Step 1: What You'll Need

Since I had an Arduino Uno handy, I decided to port the SPI version of the AVR code for this OLED to the Arduino. Here are all the parts you will need:

  1. Crystalfontz CFAL1602C OLED in your preferred color.
  2. Connection wires. I used some standard 0.1" headers I had around, and a pack of WRJMPY40 jumper wires.
  3. An Arduino Uno R3

Step 2: Convert OLED to SPI

You will need to order your CFAL1602C pre-confgured for SPI, or follow the instructions in the CFAL1602C datasheet to make the conversion.

Step 3: Connect SPI Lines to OLED

Once that is done, use some of the jumper wires to connect to the pins used for SPI on the CFAL1602C.

Step 4: Connect 5v Power and Ground

Then connect the 5v power and ground pins to the Arduino.

Step 5: Complete the Connections of the OLED's SPI Pins to the Arduino

  • SS: Slave Select
  • MOSI: Master (Arduino) Out, Slave (OLED) In
  • MISO: Master (Arduino) In, Slave (OLED) Out
  • SCK: Spi ClocK

Step 6: Connect Arduino Uno to PC and Open Sketch.

You then just need to connect the Arduino to your PC and open the sketch CFAL1602C_SPI_Arduino.ino. Upload and you will be rewarded with OLED goodness.


//===========================================================================
// // Code written for Arduino Uno // // CRYSTALFONTZ CFAL1602C-B CHARACTER OLED IN SPI MODE // // ref: https://www.crystalfontz.com/product/cfal1602c-16x2-character-oled // // 2015 - 10 - 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 //=========================================================================== //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 16 #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) //=========================================================================== 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 } //=========================================================================== 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 | (line?0x40:0x00) | (column & 0x3F)); } //=========================================================================== // 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,0x17);

//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(); } //=========================================================================== 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, shows a screen of text, including high order characters //( see page 10 of https://www.crystalfontz.com/controllers/WinstarDisplay/WS0010/378 ) // //Then there is a screen with solid characters on the left, and 50% //checkerboard characters on the right.

// Initialize the display initialize_display();

//Display text screen // 0123456789012345 char line1[17]=">}CRYSTALFONTZ{<"; line1[ 0]=0xF6; // right triange; line1[ 1]=0xC7; // right triange; line1[14]=0xC8; // left triange; line1[15]=0xF7; // left triange;

// 0123456789012345 char line2[17]="OLED:aaaaaaaaaaS"; line2[ 5]=0xE0; //beta line2[ 6]=0xA8; //function line2[ 7]=0xB8; //divide line2[ 8]=0xDA; //sigma line2[ 9]=0xEA; //mu (micro) line2[10]=0xDF; //alpha line2[11]=0xDE; //omega line2[12]=0xCF; //circle C line2[13]=0x1A; //about equal line2[14]=0xED; //pi

// write to the first line position_cursor(0,0); for(int col=0;col<16;col++) { write_to_OLED_SPI(DATA,line1[col]); }

// write to the second line position_cursor(0,1); for(int col=0;col<16;col++) { write_to_OLED_SPI(DATA,line2[col]); }

//Show the text message for a bit _delay_ms(1000);

//Use the custom characters (CGRAM) to make the //left half solid, the right half checkerboard

//Set CGRAM [0], to solid write_to_OLED_SPI(COMMAND,0x40 | (0 <<3)); for(int i=1;i<=8;i++) write_to_OLED_SPI(DATA,0xFF); //Set CGRAM [1], checkerboard write_to_OLED_SPI(COMMAND,0x40 | (1 <<3)); for(int i=1;i<=4;i++) { write_to_OLED_SPI(DATA,0xAA); write_to_OLED_SPI(DATA,0x55); }

// write to the first line position_cursor(0,0); for(int col=0;col<8;col++) { write_to_OLED_SPI(DATA,0); // CGRAM[0] } for(int col=8;col<16;col++) { write_to_OLED_SPI(DATA,1); // CGRAM[1] }

// write to the second line position_cursor(0,1); for(int col=0;col<8;col++) { write_to_OLED_SPI(DATA,0); // CGRAM[0] } for(int col=8;col<16;col++) { write_to_OLED_SPI(DATA,1); // CGRAM[1] }

//Show the pattern for a bit _delay_ms(1000); } //===========================================================================

Step 7: Optimizing the Code.

This code transfers at a clock frequency of about ~57KHz, and it takes about ~180uS, to complete one 10-bit write, you won't die from old age waiting for it.

But the old assembly-coding EE in me wanted to see if it could go a little faster . Starting with some optimized Arduino Software SPI examples, I was able to do a bit better:

//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;

That speeds up things by a factor of 47 times, bringing a transfer from 180uS to 3.8us. The equivalent clock frequency from 247KHz to 2.6MHz. Very nice!

Of course either routine will work . . . the "proper Arduino" should be more portable. Have fun connecting an OLED to your Arduino, feel free post your comments or questions.

Step 8: Have Questions? Need More Info?

Have questions or problems? Post questions to our forum: https://forum.crystalfontz.com/showthread.php/7388-Connect-Arduino-Uno-to-a-CFAL1602C-SPI-16x2-Character-OLED-(only-4-wires-needed)