What is an OLED?

An OLED is a type of diode that consists of an organic compound that emits light when current flows through it. 

How does an OLED communicate with Arduino?

The way the OLED communicates with Arduino is via Inter-Integrated Circuit (I2C). However, it is also possible to communicate via Serial Peripheral Interface (SPI). For the purpose of this tutorial, we are going to focus on I2C communication. This type of communication allows multiple slaves to communicate with a master. In this case, we will only be working with one slave: chip SSD1306. Our master will be Arduino UNO.

SSDI1306 is a single chip CMOS OLED/PLED driver with controller for organic/polymer light emitting diode dot-matrix graphic display system. In other words, it is the device in charge of controlling the screen by telling it what to do.  In order to establish I2C communication between master and slave, only two channels are required: SDA and CLK. However, we also need to take into account VCC and Ground for voltage supply and, in this case, RST for initialization purposes. The picture below shows the wiring setup for our OLED:

SDA is in charge of transmitting data and it corresponds to pin A4 in the Arduino. CLK is in charge of setting the clock pulse for synchronization, and it corresponds to pin A5 in the Arduino. These pins, however, will change if you are not using Arduino UNO or Ethernet. Refer to the link below to see I2C pins for different versions of Arduino:

RST is used to reset the device to its default setup and it can be wired to any digital input in the Arduino. In this case, we used pin 10. Once the channels for communication have been established, it is now possible to send information to our device. Figure 1 shows how this is done. 

The first thing that we need is the slave address. This seven bit number identifies a particular device so that the master knows what slave it needs to communicate with. From the SSD1306 datasheet, the slave address for this driver can be either “0111100” or “0111101” depending on SA0 (whether it is high or low). Its default value is high, but, if you desire to change the slave address, you can wire SA0 to any digital pin in the Arduino and set it to zero. In this tutorial, we decided not to change SA0, thus, the address of our slave is 0x3D. Since the OLED will always be in write mode, the R/W# bit (read/write bit) is set to “0.”

The next thing that we need is the control byte. This is defined by Co (continuation bit) and D/C# (data/command selection bit), followed by six “0s.” Co determines whether the following byte is going to be a single byte, or a stream of bytes, while D/C# determines if the byte is going to be treated as data or a command. Thus, we end up having four possible control bytes:

  • 0x40: Data stream
  • 0xC0: Single Data Byte
  • 0x80: Single Command byte
  • 0x00: Command Stream

You can find a list of commands in Table 9-1 on the SSD1306 datasheet.

How is data displayed?

The OLED consists of 128 columns (SEG) and 8 pages each containing 8 rows (COM) as shown in Figure 2. 

While commands are used to configure the screen and personalize it through the use of registers, data is used to display whatever information is stored in the GDDRAM (Graphic Display Data RAM).  If we write byte 00000001,” the first pixel located on the top left corner, or on page 0 column 0, will be turned on. The reason why the byte is written in reverse order is because the OLED uses the following configuration to display data: 

With the use of a pointer, the data starts filling the column from the least significant bit to the most significant bit. Once the pointer reaches the end of the column, it automatically moves to the next column in horizontal addressing mode, or to the next page in vertical addressing mode (refer to SSD1306 datasheet for addressing mode). 

How to draw a pixel?

Now that we know how the OLED works, we can create a program that lets the user turn on a specific pixel on the screen.

#include<Wire.h> //Include Wire library for I2C communication
#define HEIGHT 64
#define WIDTH 128
const int RST = 10; //Assign pin 10 for Reset  
int i; //Set variable i as integer
static unsigned char array[1024]; //buffer array/>
 
void setup() {
  pin_init(); //Initialize pins
  initialize_OLED(); //Initialize screen
  memset(array, 0, sizeof(array)); //Clear array
  draw_pixel(63,31); //Store pixel at (x,y) location
  Flush(); //Send data
}
 
void loop() {
  //Nothing happens here
}
 
void pin_init(){
  Serial.begin(9600); //Set baud for serial transmission
  pinMode(RST, OUTPUT); //Set RST as output
}
 
void initialize_OLED(){
  Wire.begin(); //Initialize I2C interface
  digitalWrite(RST, LOW); //Set reset pin low (active)
  delay(10); //Wait 100 ms
  digitalWrite(RST, HIGH); //Set reset pin high (inactive)  
  Wire.beginTransmission(0x3D); // Start communication with slave
  Wire.write(0x00); //Command stream
  Wire.write(0xAE); //Set display Off
  Wire.write(0xD5); //Set display clock divide ratio/oscillator frequency
  Wire.write(0x80);
  Wire.write(0xA8); //Set multiplex ratio
  Wire.write(0x3F);
  Wire.write(0xD3); //Set display offset
  Wire.write(0x00);
  Wire.write(0x40); //Set display start line
  Wire.write(0x8D); //Set charge pump
  Wire.write(0x14); //VCC generated by internal DC/DC circuit
  Wire.write(0xA1); //Set segment re-map 
  Wire.write(0xC0); //Set COM output scan direction
  Wire.write(0xDA); //Set COM pins hardware configuration
  Wire.write(0x12);
  Wire.write(0x81); //Set contrast control
  Wire.write(0xCF);
  Wire.write(0xD9); //Set pre-changed period
  Wire.write(0xF1);
  Wire.write(0xDB); //Set VCOMH Deselected level
  Wire.write(0x40);
  Wire.write(0xA4); //Set entire display on/off
  Wire.write(0xA6); //Set normal display 
  Wire.write(0x20); //Set memory address mode
  Wire.write(0x00); //Horizontal
  Wire.write(0xAF); //Set display on
  Wire.endTransmission(); //End communicaiton with slave
}
 
void draw_pixel(int x, int y)
  if((x<0) || (x>=WIDTH) || (y<0) || (y>=HEIGHT)){ //Check for boundaries
    return;
  }
  else{
    array[x+(y/8)*WIDTH] |= _BV((y%8)); //Store pixel in array
  }
}
 
void Flush(){
 Wire.beginTransmission(0x3D); //Start communication with slave
 Wire.write(0x00); //Command stream
 Wire.write(0x00); //Set lower column start address for page addressing mode
 Wire.write(0x10); //Set higher column start address for page addressing mode
 Wire.write(0x40); //Set display start line
 Wire.endTransmission(); //End communication with slave
 
 unsigned char twbrbackup = TWBR; //Two wire bit rate register
 TWBR = 12; //Set to 400 kHz
 
 for(unsigned short q=0; q<(WIDTH*HEIGHT/8); q++){
 Wire.beginTransmission(0x3D); //Start communication with slave
 Wire.write(0x40); //Data stream
 for(unsigned char w=0; w<16; w++){
    Wire.write(array[q]); //Transmit data to be displayed
    q++;
 }
 q--;
  Wire.endTransmission(); //End communication with slave
}
TWBR = twbrbackup;
}

The Wire library enables Arduino to communicate with I2C devices. In the first part of the code we assign the already mentioned digital pin to RST, and create an array to buffer the data that we want to display. Then pin RST is initialized by making it an output. Next, the OLED is initialized by following the example shown on the display datasheet on page 15. It is recommended that you reset the device once before you start using it. This is done by setting RST low for some time and then high. Since this pin is active low, it needs to be high during normal operation.

The function “pixel” is then created to store the pixel in a specific location in the array buffer. This function takes two arguments, x and y, to give the program an exact coordinate of where we want the pixel. X can adopt any value between 0-127, while y can have any value between 0-63. Coordinate (0,0) would be located in the bottom left side of our screen. If the coordinate is out of range, then the program won’t display anything. Finally, the function “Flush” is used to display the elements of the array buffer by sending the information in 16 byte burst transfers as the program travels through the whole buffer. Inside this function, TWBR (Two wire bit rate register) is set to 12in order to upgrade the frequency of the Arduino clock (SCL) from 100kHz to 400kHz. This way data transmission is faster. Below is shown how 12 was calculated: 
Where:

  • CPUFrequency = 16MHz
  • TWIFrequency = 400kHz
  • TWI = Two Wire Interface

For this tutorial, we chose coordinate (63,31) to turn on the pixel located at the middle of the screen as shown in the picture below:


How to draw a line?

Once we have a pixel function, we can use it to draw a line defined by a start and end point on the x and y axis on the screen.

#include<Wire.h> //Include Wire library for I2C communication
#define HEIGHT 64
#define WIDTH 128
const int RST = 10; //Assign pin 10 for Reset  
int i; //Set variable i as integer
static unsigned char array[1024]; //Buffer array
 
void setup() {
  pin_init(); //Initialize pins
  initialize_OLED(); //Initialize screen
  memset(array, 0, sizeof(array)); //Initialize array with 0s
  line(0,63,0,31); //Draw line
  Flush(); //Send data
}
 
void loop() { 
}
 
void pin_init(){
  Serial.begin(9600); //Set baud for serial transmission
  pinMode(RST, OUTPUT); //Set RST as output
}
 
void initialize_OLED(){
  Wire.begin(); //Initialize I2C interface
  digitalWrite(RST, LOW); //Set RST pin low
  delay(100); //Wait 100 ms
  digitalWrite(RST, HIGH); //Set RST pin high
  Wire.beginTransmission(0x3D); // Start communication with slave
  Wire.write(0x00); //Command stream
  Wire.write(0xAE); //Set display Off
  Wire.write(0xD5); //Set display clock divide ratio/oscillator frequency
  Wire.write(0x80);
  Wire.write(0xA8); //Set multiplex ratio
  Wire.write(0x3F);
  Wire.write(0xD3); //Set display offset
  Wire.write(0x00);
  Wire.write(0x40); //Set display start line
  Wire.write(0x8D); //Set charge pump
  Wire.write(0x14); //VCC generated by internal DC/DC circuit
  Wire.write(0xA1); //Set segment re-map 
  Wire.write(0xC0); //Set COM output scan direction
  Wire.write(0xDA); //Set COM pins hardware configuration
  Wire.write(0x12);
  Wire.write(0x81); //Set contrast control
  Wire.write(0xCF);
  Wire.write(0xD9); //Set pre-changed period
  Wire.write(0xF1);
  Wire.write(0xDB); //Set VCOMH Deselected level
  Wire.write(0x40);
  Wire.write(0xA4); //Set entire display on/off
  Wire.write(0xA6); //Set normal/inverse display 
  Wire.write(0x20); //Set memory address mode
  Wire.write(0x00); //Horizontal
  Wire.write(0xAF); //Set display on
  Wire.endTransmission(); //End communicaiton with slave
}
 
/*Bresenham's line drawing algorithm*/
 
void line(int x1, int x2, int y1, int y2){
  int cx = x1;
  int cy = y1;
 
  int dx = x2-cx;
  int dy = y2-cy;
 
  if(dx<0){
   dx = 0-dx;
  }
  if(dy<0){
    dy = 0-dy;
  }
 
  int sx = 0;
  int sy = 0; 
  if(cx<x2){
   sx = 1; 
  }
  else{
   sx = -1;
  }
  if(cy<y2){
   sy = 1;
  }
  else{
   sy = -1;
  }
  int err = dx-dy;
 
  for(int n=0; n<1000; n++){
   draw_pixel(cx,cy);
  if((cx==x2) && (cy==y2)){ 
   return;
  }
  int e2 =2*err;
  if(e2>(0-dy)){
   err = err-dy;
   cx = cx+sx;
  }
  if(e2<dx){ 
   err = err+dx;
   cy = cy+sy;
   }
  }
 }
 
void draw_pixel(int x, int y){
  if((x<0) || (x>=WIDTH) || (y<0) || (y>=HEIGHT)){ //Check for boundaries
    return;
  }
  else{
    array[x+(y/8)*WIDTH] |= _BV((y%8)); //Store pixel in array
  }
}
 
void Flush(){
 Wire.beginTransmission(0x3D); //Start communication with slave
 Wire.write(0x00); //Command stream
 Wire.write(0x00);  //Set lower column start address for page addressing mode
 Wire.write(0x10); //Set higher column start address for page addressing mode
 Wire.write(0x40); //Set display start line
 Wire.endTransmission(); //End communication with slave
 
 unsigned char twbrbackup = TWBR; //Two wire bit rate register
 TWBR = 12; //Set to 400 kHz
 
 for(unsigned short q=0; q<(WIDTH*HEIGHT/8); q++){
 Wire.beginTransmission(0x3D); //Start communication with slave
 Wire.write(0x40); //Data stream
 for(unsigned char w=0; w<16; w++){
    Wire.write(array[q]); //Transmit data to be displayed
    q++;
 }
 q--;
  Wire.endTransmission(); //End communication with slave
}
TWBR = twbrbackup;
}

The line function is based on the Bresenham's Line Drawing Algorithm used to draw lines with pixels. The following pseudo code was used to implement the algorithm in our program:

This algorithm basically fills in the pixels between (x0,y0) and (x1,y1) by deciding what pixels should be turned on next based on an error value. Refer to the following links for more information on the algorithm:

The pixel function is used to store a pixel of the line in the array buffer at a specific location each time it is called in the program. This way the array buffer is filled with all the pixels required to draw our line. Once the buffer is loaded with the necessary elements, the Flush function is used to display the data in the screen. By giving our line function four arguments (start and end points on the x and y axis), we can now draw a line like the one below:

In this example, the line goes from (0,0) to (63,31). In other words, it goes from the bottom left corner to the center of our screen. 

How to draw a square?

We can use the following code to draw a square on our screen:

#include<Wire.h> //Include Wire library for I2C communication
#define HEIGHT 64
#define WIDTH 128
const int RST = 10; //Assign pin 10 for Reset  
int i; //Set variable i as integer
static unsigned char array[1024]; //Buffer array
 
void setup() { 
  pin_init(); //Initialize pins
  initialize_OLED(); //Initialize screen
  memset(array, 0, sizeof(array)); //Initialize array with 0s
  square(117,127,53,63); //Draw square 
  Flush(); //Send data
}
 
void loop() { 
}
 
void pin_init(){
  Serial.begin(9600); //Set baud for serial transmission
  pinMode(RST, OUTPUT); //Set RST as output
}
 
void initialize_OLED(){
  Wire.begin(); //Initialize I2C interface
  digitalWrite(RST, LOW); //Set RST pin low
  delay(100); //Wait 100 ms
  digitalWrite(RST, HIGH); //Set RST pin high
  Wire.beginTransmission(0x3D); // Start communication with slave
  Wire.write(0x00); //Command stream
  Wire.write(0xAE); //Set display Off
  Wire.write(0xD5); //Set display clock divide ratio/oscillator frequency
  Wire.write(0x80);
  Wire.write(0xA8); //Set multiplex ratio
  Wire.write(0x3F);
  Wire.write(0xD3); //Set display offset
  Wire.write(0x00);
  Wire.write(0x40); //Set display start line
  Wire.write(0x8D); //Set charge pump
  Wire.write(0x14); //VCC generated by internal DC/DC circuit
  Wire.write(0xA1); //Set segment re-map 
  Wire.write(0xC0); //Set COM output scan direction
  Wire.write(0xDA); //Set COM pins hardware configuration
  Wire.write(0x12);
  Wire.write(0x81); //Set contrast control
  Wire.write(0xCF);
  Wire.write(0xD9); //Set pre-changed period
  Wire.write(0xF1);
  Wire.write(0xDB); //Set VCOMH Deselected level
  Wire.write(0x40);
  Wire.write(0xA4); //Set entire display on/off
  Wire.write(0xA6); //Set normal/inverse display 
  Wire.write(0x20); //Set memory address mode
  Wire.write(0x00); //Horizontal
  Wire.write(0xAF); //Set display on
  Wire.endTransmission(); //End communication with slave
}
 
/*Function to draw square*/
 
void square(int x1, int x2, int y1, int y2){
 
  int x, y; //Define x and y as integer variables
 
  Wire.beginTransmission(0x3D); //Start communication with slave
  for(x=x1; x<x2; x++){ //Iterate over x range, draw line on y1
    draw_pixel(x,y1); //Draw pixel
  }
  Wire.endTransmission(); //End communication with slave
 
  Wire.beginTransmission(0x3D); //Start communication with slave
  for(x=x1; x<x2; x++){ //Iterate over x range, draw line on y2
    draw_pixel(x,y2); //Draw pixel
  }
  Wire.endTransmission(); //End communication with slave
 
  Wire.beginTransmission(0x3D); //Start communication
  for(y=y1; y<y2; y++){ //Iterate over y range, draw line on x1
    draw_pixel(x1,y); //Draw pixel
  }
  Wire.endTransmission(); //End communication with slave
 
  Wire.beginTransmission(0x3D); //Start communication with slave
  for(y=y1; y<y2; y++){ //Iterate over y range, draw line on x2 
    draw_pixel(x2,y); //Draw pixel
  }
  Wire.endTransmission(); //End communication with slave
}
 
void draw_pixel(int x, int y){
  if((x<0) || (x>=WIDTH) || (y<0) || (y>=HEIGHT)){ //Check for boundaries
    return;
  }
  else{
    array[x+(y/8)*WIDTH] |= _BV((y%8)); //Store pixel in array
  }
}
 
void Flush(){
 Wire.beginTransmission(0x3D); //Start communication with slave
 Wire.write(0x00); //Command stream
 Wire.write(0x00); //Set lower column start address for page addressing mode
 Wire.write(0x10); //Set higher column start address for page addressing mode
 Wire.write(0x40); //Set display start line
 Wire.endTransmission(); //End communication with slave
 
 unsigned char twbrbackup = TWBR; //Two wire bit rate register
 TWBR = 12; //Set to 400 kHz
 
 for(unsigned short q=0; q<(WIDTH*HEIGHT/8); q++){
 Wire.beginTransmission(0x3D); //Start communication with slave
 Wire.write(0x40); //Data stream
 for(unsigned char w=0; w<16; w++){
    Wire.write(array[q]); //Transmit data to be displayed
    q++;
 }
 q--;
  Wire.endTransmission(); //End communication with slave
}
TWBR = twbrbackup;
}

The square function takes four arguments, a start and end point on the x and y axis, to define the height and width of our rectangle. Then four lines are drawn independently using, once again, the pixel function. The first line to be drawn is the bottom line of our square from x1-x2 on the y1 axis. The second line to be drawn is the upper line of our square from x1-x2 on the y2 axis. The third line to be drawn is the left side of our square from y1-y2 on the x1 axis. The last line to be drawn is the right side of our square from y1-y2 on the x2 axis. Once all the lines’ pixels have been stored in the array, the Flush function is used to display the four lines. Below is the picture of a 10 x 10 pixels square located at the top right corner of our screen:

How to draw a circle?

We can use the following code to generate a circle on our screen:

#include<Wire.h> //Include Wire library for I2C communication
#define HEIGHT 64
#define WIDTH 128
const int RST = 10; //Assign pin 10 for Reset 
int i; //Set variable i as integer
static unsigned char array[1024]; //Buffer array
 
void setup() {
  pin_init(); //Initialize pins
  initialize_OLED(); //Initialize screen
  memset(array, 0, sizeof(array)); //Initialize array with 0s
  circle(63,31,10); //Draw circle, (x,y,R)
  Flush(); //Send data
}
 
void loop() {   
}
 
void pin_init(){
  Serial.begin(9600); //Set baud for serial transmission
  pinMode(RST, OUTPUT); //Set RST as output
}
 
void initialize_OLED(){
  Wire.begin(); //Initialize I2C interface
  digitalWrite(RST, LOW); //Set RST pin low
  delay(100); //Wait 100 ms
  digitalWrite(RST, HIGH); //Set RST pin high
  Wire.beginTransmission(0x3D); // Start communication with slave
  Wire.write(0x00); //Command stream
  Wire.write(0xAE); //Set display Off
  Wire.write(0xD5); //Set display clock divide ratio/oscillator frequency
  Wire.write(0x80);
  Wire.write(0xA8); //Set multiplex ratio
  Wire.write(0x3F);
  Wire.write(0xD3); //Set display offset
  Wire.write(0x00);
  Wire.write(0x40); //Set display start line
  Wire.write(0x8D); //Set charge pump
  Wire.write(0x14); //VCC generated by internal DC/DC circuit
  Wire.write(0xA1); //Set segment re-map 
  Wire.write(0xC0); //Set COM output scan direction
  Wire.write(0xDA); //Set COM pins hardware configuration
  Wire.write(0x12);
  Wire.write(0x81); //Set contrast control
  Wire.write(0xCF);
  Wire.write(0xD9); //Set pre-changed period
  Wire.write(0xF1);
  Wire.write(0xDB); //Set VCOMH Deselected level
  Wire.write(0x40);
  Wire.write(0xA4); //Set entire display on/off
  Wire.write(0xA6); //Set normal/inverse display 
  Wire.write(0x20); //Set memory address mode
  Wire.write(0x00); //Horizontal
  Wire.write(0xAF); //Set display on
  Wire.endTransmission(); //End communication with slave
}
 
/*Midpoint circle algorithm*/
 
void circle(int x0, int y0, int R){
  int x = R; //Set x equal to radius
  int y = 0;
  int de = 1-x; 
 
  while(x>=y){
    draw_pixel(x+x0, y+y0); //First octant
    draw_pixel(y+x0, x+y0); //Second octant 
    draw_pixel(-y+x0, x+y0); //Third octant
    draw_pixel(-x+x0, y+y0); //Fourth octant
    draw_pixel(-x+x0, -y+y0); //Fifth octant
    draw_pixel(-y+x0, -x+y0); //Sixth octant
    draw_pixel(y+x0, -x+y0); //Seventh octant
    draw_pixel(x+x0, -y+y0); //Eight octant                 
    y++; 
    if(de<=0){
      de += 2*y+1;
    }
    else{ 
      x--;
      de += 2*(y-x)+1; 
   }
  }
}
 
void draw_pixel(int x, int y){
  if((x<0) || (x>=WIDTH) || (y<0) || (y>=HEIGHT)){ //Check for boundaries
    return;
  }
  else{
    array[x+(y/8)*WIDTH] |= _BV((y%8)); //Store pixel in array
  }
}
 
void Flush(){
 Wire.beginTransmission(0x3D); //Start communication with slave
 Wire.write(0x00); //Command stream
 Wire.write(0x00); //Set lower column start address for page addressing mode
 Wire.write(0x10); //Set higher column start address for page addressing mode
 Wire.write(0x40); //Set display start line
 Wire.endTransmission(); //End communication with slave
 
 unsigned char twbrbackup = TWBR; //Two wire bit rate register
 TWBR = 12; //Set to 400kHz
 
 for(unsigned short q=0; q<(WIDTH*HEIGHT/8); q++){
 Wire.beginTransmission(0x3D); //Start communication with slave
 Wire.write(0x40); //Data stream
 for(unsigned char w=0; w<16; w++){
    Wire.write(array[q]); //Transmit data to be displayed
    q++;
 }
 q--;
  Wire.endTransmission(); //End communication with slave
}
TWBR = twbrbackup;
}
 

The circle function is based on the Midpoint Circle Algorithm used to generate circles with pixels. The algorithm draws one pixel at a time in each octant of the circle by means of symmetry until the circumference is completed. Once more, the pixel function is used to draw each pixel of the circle every time it is called in the program. Refer to the following link for more information on the algorithm:

By giving our circle function three arguments (origin at (x,y) and radius), we can now draw a circle like the one below:

 

In this example the circle’s origin is at (63,31) and has a radius of 10, thus generating a circle at the center of the page with a radius of 10 pixels.

Miscellaneous

If we use a buffered display with defined values, we can display any image we want on our screen. The following code shows how this is done:

#include<Wire.h> //Include Wire library for I2C communication
 
const int RST = 10; //Assign pin 10 for Reset
int i; //Set variable i as integer
const unsigned char js[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x80, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0,
0x80, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x80, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x80, 0xC0, 0xC0, 0xC0,
0x80, 0xC0, 0xC0, 0xC0, 0x80, 0xC0, 0xC0, 0xC0, 0x80, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0x80, 0x80,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x0F, 0x1F,
0x1F, 0x1F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x1F, 0x3F, 0x3F, 0x3F, 0x3F, 0x1F, 0x1F, 0x3F, 0x3F,
0x3F, 0x3F, 0x1F, 0x1F, 0x3F, 0x3F, 0x1F, 0x1F, 0x3F, 0x3F, 0x1F, 0x1F, 0x3F, 0x3F, 0x1F, 0x1F,
0x3F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x40, 0x80, 0x60,
0x50, 0xA0, 0xA8, 0x50, 0xA0, 0x5C, 0xA4, 0x48, 0xA8, 0x50, 0xA6, 0x58, 0x90, 0x64, 0x58, 0xA4,
0x58, 0x24, 0x98, 0x68, 0xA8, 0x54, 0x68, 0x22, 0x50, 0xA4, 0x48, 0xB4, 0xA0, 0x5C, 0x94, 0x48,
0x94, 0x68, 0xA4, 0x50, 0x58, 0xA4, 0x58, 0x94, 0x24, 0x58, 0xA4, 0x58, 0xA0, 0x58, 0x04, 0x00,
0x54, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x84, 0x00, 0x60, 0x2A, 0x90, 0x64, 0x58, 0xA4, 0x58, 0x24, 0x98, 0x68, 0xA8, 0x54, 0x68, 0x24,
0x24, 0x58, 0xA4, 0x58, 0xA4, 0x48, 0x94, 0x68, 0x90, 0x6A, 0xD4, 0x28, 0x20, 0xD4, 0x68, 0x12,
0x50, 0xA4, 0x48, 0xB4, 0xA4, 0x18, 0x50, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x25, 0x7E, 0x9A, 0xE5,
0x76, 0x59, 0xCA, 0xB5, 0x9D, 0x6A, 0xFD, 0x92, 0xDA, 0x61, 0x60, 0xA0, 0x80, 0xC0, 0x20, 0xC0,
0x40, 0xA0, 0x80, 0xE0, 0x40, 0xC0, 0x40, 0xA0, 0x80, 0xC0, 0x20, 0xC0, 0x40, 0xA0, 0x80, 0xE0,
0x40, 0xC0, 0x40, 0xA0, 0x80, 0xC0, 0x20, 0xC0, 0x40, 0xC0, 0x80, 0x60, 0xC0, 0xA0, 0x80, 0xC0,
0xC0, 0x6D, 0x6F, 0xDF, 0xBF, 0xAF, 0x7F, 0xCF, 0xBF, 0xBF, 0xDF, 0x6F, 0x7F, 0xDF, 0x9F, 0xAF,
0xC0, 0x40, 0x80, 0xA0, 0xC0, 0x40, 0x40, 0xC0, 0xC0, 0x40, 0x40, 0xC0, 0xC0, 0x80, 0xE0, 0x40,
0x40, 0xC0, 0x40, 0xA0, 0x80, 0xC0, 0x20, 0xC0, 0x40, 0xC0, 0x80, 0xC0, 0x00, 0xC0, 0xC0, 0x00, 0xC0, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x81, 0xC1, 0xC3, 0xC2, 0xC2, 0x85, 0x87, 0xC6, 0x87, 0x09, 0x0F, 0x02, 0x0F, 0x05, 0x0F, 0x06, 0x06, 0x07, 0x05, 0x0F, 0x09, 0x05, 0x0F, 0x06, 0x06, 0x07, 0x0D, 0x07, 0x0C, 0x07, 0x05, 0x0F, 0x0A, 0x05, 0x0F, 0x06, 0x06, 0x07, 0x0D, 0x07, 0x0C, 0x07, 0x05, 0x0F, 0x0A, 0x05, 0x0F, 0x06, 0x06, 0x0F, 0xCA, 0xEE, 0xFB, 0xF3, 0xFD, 0xFB, 0xEE, 0xEE, 0xFB, 0xEE, 0xF3, 0xFB, 0xFE, 0xFB, 0xCE, 0x03, 0x0E, 0x0B, 0x0E, 0x03, 0x0E, 0x0A, 0x07, 0x03, 0x0E, 0x0B, 0x0E, 0x03, 0x0E, 0x0A, 0x07, 0x07, 0x0D, 0x07, 0x0C, 0x07, 0x05, 0x0F, 0x0A, 0x0E, 0x0B, 0xB5, 0xEF, 0x6A, 0xFF, 0x7B, 0xEC, 0xFF, 0x69, 0xFF, 0xAA, 0xB7, 0xFC, 0x6E, 0xF8, 0xE0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x78, 0x7C, 0xDC, 0xFC, 0x7E, 0xF8, 0xFE, 0xBC, 0xFC, 0xFE, 0xF6, 0xDC, 0xEC, 0xF6, 0xFC, 0xFE, 0xFE, 0xBC, 0x7A, 0xFC, 0xFC, 0xFE, 0xE8, 0xFE, 0xFC, 0xBE, 0xEC, 0x10, 0x34, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xD8, 0x00, 0x6C, 0xFC, 0xFC, 0xFA, 0xBE, 0xFC, 0xEC, 0xF6, 0xFC, 0xFE, 0xFE, 0x7C, 0x7A, 0xFC, 0xFC, 0x7E, 0xF6, 0xFC, 0xFE, 0xDC, 0xFC, 0xEE, 0xBE, 0xFE, 0xFF, 0xE7, 0xFE, 0x7F, 0xFB, 0x7F, 0x7E, 0x6F, 0x3F, 0x3B, 0x1E, 0x1F, 0x0F, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xF8, 0xF0, 0xF0, 0xF0, 0xE0, 0xF0, 0xE0, 0xF0, 0xE0, 0xF0, 0xE0, 0xE0, 0xE0, 0xF0, 0xF0, 0xF0, 0xE0, 0xF0, 0xE0, 0xF0, 0xE0, 0xF0, 0xE0, 0xF0, 0xE0, 0xF0, 0xE0, 0xE0, 0xE0, 0xF0, 0xF0, 0xF0, 0xE0, 0xF0, 0xE0, 0xE0, 0xF0, 0xF0, 0xF0, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x7F, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x07, 0x03, 0x03, 0x07, 0x07, 0x07, 0x07, 0x0F, 0x07, 0x0F, 0x07, 0x0F, 0x07, 0x07, 0x0F, 0x07, 0x0F, 0x0F, 0x07, 0x0F, 0x07, 0x07, 0x0F, 0x07, 0x0F, 0x0F, 0x07, 0x0F, 0x07, 0x07, 0x0F, 0x07, 0x0F, 0x0F, 0x07, 0x0F, 0x07, 0x07, 0x0F, 0x07, 0x0F, 0x0F, 0x07, 0x0F, 0x07, 0x07, 0x07, 0x0F, 0x07, 0x07, 0x07, 0x07, 0x03, 0x07, 0x07, 0x03, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };   void setup() { 
  pin_init(); //Initialize pins 
  initialize_OLED(); //Initialize screen 
  pattern(); //Display image 
} 
  void loop() {
} 
  void pin_init(){
  Serial.begin(9600); //Set baud for serial transmission 
  pinMode(RST, OUTPUT); //Set RST as output 
} 
  void initialize_OLED(){ 
  Wire.begin(); //Initialize I2C interface 
  digitalWrite(RST, LOW); //Set RST pin low 
  delay(100); //Wait 100 ms 
  digitalWrite(RST, HIGH); //Set RST pin high 
  Wire.beginTransmission(0x3D); // Start communication with slave 
  Wire.write(0x00); //Command stream 
  Wire.write(0xAE); //Set display Off 
  Wire.write(0xD5); //Set display clock divide ratio/oscillator frequency   Wire.write(0x80); 
  Wire.write(0xA8); //Set multiplex ratio 
  Wire.write(0x3F); 
  Wire.write(0xD3); //Set display offset 
  Wire.write(0x00); 
  Wire.write(0x40); //Set display start line 
  Wire.write(0x8D); //Set charge pump 
  Wire.write(0x14); //VCC generated by internal DC/DC circuit 
  Wire.write(0xA1); //Set segment re-map  
  Wire.write(0xC8); //Set COM output scan direction 
  Wire.write(0xDA); //Set COM pins hardware configuration 
  Wire.write(0x12); 
  Wire.write(0x81); //Set contrast control 
  Wire.write(0xCF); 
  Wire.write(0xD9); //Set pre-changed period 
  Wire.write(0xF1); 
  Wire.write(0xDB); //Set VCOMH Deselected level 
  Wire.write(0x40); 
  Wire.write(0xA4); //Set entire display on/off 
  Wire.write(0xA6); //Set normal/inverse display  
  Wire.write(0x20); //Set memory address mode 
  Wire.write(0x00); //Vertical 
  Wire.write(0xAF); //Set display on 
  Wire.endTransmission(); //End communication with slave 
} 
  
void pattern(){ 
  for(i=0; i<1024; i++){ 
  Wire.beginTransmission(0x3D); //Start communication with slave 
  Wire.write(0x40); //Data stream 
  for(unsigned char x=0; x<16; x++){
     Wire.write(js[i]); //Transmit data to be displayed
     i++;
  }
  i--;
  Wire.endTransmission(); //End communication with slave
 }
}
 

In the previous examples, we would use a pixel function to generate different shapes by placing each of their pixels in a specific location inside an array. Then we would use a Flush function to display the array’s data on the screen, showing the desired figure. In this case, instead of generating the values of our array buffer through the use of iterations, we give the array 1024 bytes of data that represent the image that we want to display. This allows us to display any image we want, but with certain limitations. So how do we get these values? First we need to find the image that we want to display. We have to make sure the image is 128x64 (the resolution of our screen) and monochrome (black and white). I borrowed mine from the internet:

Once we have selected our image, we have to convert it from jpeg to bitmap. This is simply done by opening the image in paint and saving it as a monochrome bitmap (.bmp). We can use other graphic software to convert the image if we want. Then, with the help of LCD Assistant, we can load the bmp image and save it as a cpp file. This will generate the desired array values that we need in our code to display the image. When the program is run we get the following result:

Here is another example with Jaycon System’s company logo:

If you have any questions about this tutorial, don't hesitate to post a comment, shoot us an email, or post it in our forum!