//=========================================================================== // // 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 //=========================================================================== //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); } //===========================================================================