The Solomon Systech SSD1306 module is a CMOS driver for an OLED matrix display, making it useful for displaying information in a compact manner. The SSD1306 includes built-in hardware such as display RAM (GDDRAM) and an oscillator, making it convenient to couple with an MCU. The particular SSD1306 module used with the library, SSD1306.h, is the I2C variant. The Characters.h library is called upon in line #6 of the following code to include a list of commonly used ASCII characters.

/*             OLED Display: Solomon Systech SSD1306 (library)                */
/*                          Filename: SSD1306.h                               */
/*                      Communication Protocol: I2C                           */
/*                          Author: Michael                                   */

#include "Characters.h"                                                 

uint8_t OLED_Slave_Address_Write = 0b01111000;                                                  // SSD1306 Slave address <bit 1 to bit 7> with write bit <bit 0>

void I2C_Delay(void)                                                                            // Function to establish I2C delay
{   
    while (I2C1STATbits.TRSTAT == 1);                                                           // Wait for transmit to finish (8 bits + ACK)
}

void OLED_Command(uint8_t OLED_Slave_Address_Write, uint8_t Command)                            // Function to write commands to SSD1306 
{   
    I2C1CONLbits.SEN = 1;                                                                       // START condition (automatically cleared by hardware)
    while (I2C1CONLbits.SEN == 1);                                                              // Wait for START condition to finish
    I2C1TRN = OLED_Slave_Address_Write;                                                         // Write Slave address (with write bit) to transmission register
    I2C_Delay();
    I2C1TRN = 0b00000000;                                                                       // Next byte is command (SSD1306 datasheet p.20 step #5)
    I2C_Delay();
    I2C1TRN = Command;                                                                          // Write command
    I2C_Delay();
    I2C1CONLbits.PEN = 1;                                                                       // STOP condition
    while (I2C1CONLbits.PEN == 1);                                                              // Wait for STOP condition to end
}

void OLED_Setup(void)                                                                           // Function to set operating conditions for SSD1306
{    
    
    // Fundamental commands/settings
    OLED_Command(OLED_Slave_Address_Write, 0b10000001);                                         // Contrast Control (command)
    OLED_Command(OLED_Slave_Address_Write, 0b00000000);                                         // Contrast Control (setting)
    OLED_Command(OLED_Slave_Address_Write, 0b10100100);                                         // Entire Display ON (RAM content display) (command + setting)
    OLED_Command(OLED_Slave_Address_Write, 0b10100110);                                         // Normal/Inverse Display (normal display) (command + setting)
    OLED_Command(OLED_Slave_Address_Write, 0b10101110);                                         // Display ON/OFF (display OFF) (command + setting)

    // Addressing commands/settings
    OLED_Command(OLED_Slave_Address_Write, 0b00000000);                                         // Lower Column Start Address for Page Addressing Mode (command + setting)
    OLED_Command(OLED_Slave_Address_Write, 0b00010000);                                         // Higher Column Start Address for Page Addressing Mode (command + setting)
    OLED_Command(OLED_Slave_Address_Write, 0b00100000);                                         // Memory Addressing Mode (command)
    OLED_Command(OLED_Slave_Address_Write, 0b00000010);                                         // Memory Addressing Mode (page addressing mode) (setting)
    OLED_Command(OLED_Slave_Address_Write, 0b10110000);                                         // Page Start Address for Page Addressing Mode (command + setting)
    
    // Hardware configuration commands/settings
    OLED_Command(OLED_Slave_Address_Write, 0b01000000);                                         // Display Start Line (command + setting)
    OLED_Command(OLED_Slave_Address_Write, 0b10100001);                                         // Segment Re-map (column address 127 is mapped to SEG0) (command + setting)
    OLED_Command(OLED_Slave_Address_Write, 0b10101000);                                         // Multiplex Ratio (command)
    OLED_Command(OLED_Slave_Address_Write, 0b00111111);                                         // Multiplex Ratio (setting)
    OLED_Command(OLED_Slave_Address_Write, 0b11001000);                                         // COM Output Scan Direction (remapped mode, scan from COM[N-1] to COM0) (command + setting)
    OLED_Command(OLED_Slave_Address_Write, 0b11010011);                                         // Display Offset (command)
    OLED_Command(OLED_Slave_Address_Write, 0b00000000);                                         // Display Offset (setting)
    OLED_Command(OLED_Slave_Address_Write, 0b11011010);                                         // COM Pins Hardware Configuration (command)
    OLED_Command(OLED_Slave_Address_Write, 0b00010010);                                         // COM pins Hardware Configuration (disable COM left/right remap & use alternative COM pin configuration) (setting)

    // Timing & driving scheme commands/settings
    OLED_Command(OLED_Slave_Address_Write, 0b11010101);                                         // Display Clock Divide Ratio/Oscillator Frequency (command)
    OLED_Command(OLED_Slave_Address_Write, 0b10000000);                                         // Display Clock Divide Ratio/Oscillator Frequency (setting)
    OLED_Command(OLED_Slave_Address_Write, 0b11011001);                                         // Pre-charge Period (command)
    OLED_Command(OLED_Slave_Address_Write, 0b00100010);                                         // Pre-charge Period (setting)
    OLED_Command(OLED_Slave_Address_Write, 0b11011011);                                         // VCOMH Deselect Level (command)
    OLED_Command(OLED_Slave_Address_Write, 0b00100000);                                         // VCOMH Deselect Level (~0.77 x VCC) (setting)

    // Charge pump command/setting
    OLED_Command(OLED_Slave_Address_Write, 0b10001101);                                         // Charge Pump (command)
    OLED_Command(OLED_Slave_Address_Write, 0b00010100);                                         // Charge Pump (enable charge pump during display ON) (setting)

    // Fundamental command/setting
    OLED_Command(OLED_Slave_Address_Write, 0b10101111);                                         // Display ON/OFF (display ON) (command + setting)

}

void OLED_Clear(void)                                                                           // Function to clear GDDRAM
{

    // Addressing setting commands
    OLED_Command(OLED_Slave_Address_Write, 0b00000000);                                
    OLED_Command(OLED_Slave_Address_Write, 0b00010000);                                
     
    unsigned int p = 0;                                                                         // Page position of OLED display
    unsigned int c = 0;                                                                         // Column position of OLED display
    
    for (p = 0; p < 8; p ++)                                                                    // Cycle through all 8 pages (64 pixel rows)
    {

        OLED_Command(OLED_Slave_Address_Write, 0b10110000 + p);                                 // Page START address (0 - 7)
        I2C1CONLbits.SEN = 1;
        while (I2C1CONLbits.SEN == 1);
        I2C1TRN = OLED_Slave_Address_Write;
        I2C_Delay();
        I2C1TRN = 0b01000000;
        I2C_Delay();
  
        for (c = 0; c < 128; c ++)                                                              // Cycle through all 128 pixel columns for current page
        {
            I2C1TRN = 0b00000000;
            I2C_Delay();
        }
    
        I2C1CONLbits.PEN = 1;
        while (I2C1CONLbits.PEN == 1);

    }

}

void OLED_String(uint8_t OLED_Row, uint8_t OLED_Column, char OLED_String[])                     // Function to write characters to SSD1306 (character pages: 1 to 8) (character columns: 1 to 16) (character input)
{   

    OLED_Command(OLED_Slave_Address_Write, 0b00000000 | ((OLED_Column - 1) * 8 & 0b00001111));  // Set lower column START address (pixel columns 1 to 16)
    OLED_Command(OLED_Slave_Address_Write, 0b00010000 | ((OLED_Column - 1) * 8 >> 4));          // Set higher column START address (pixel columns 17 to 128)
    OLED_Command(OLED_Slave_Address_Write, 0b10110000 | (OLED_Row - 1));                        // Set page START address (1 to 8) (page = 8 pixel rows)    
    
    uint8_t j = 0;                                                                              // Character counter (j) represents total number of characters in OLED_String

    for (uint8_t i = 0; i < 16; i ++)                                                           // Maximum of 16 character blocks (1 block = 8(pixel rows) x 8(pixel columns)) (128 pixel columns) span across OLED display
    {
        
        if (OLED_String[i] > 0)                                                                 // Increase value of j by 1 if character present
        {
            j ++;                                                                                   
        }

        else
        {
            break;                                                                              // Terminate for loop if no more characters present
        }

    }

    I2C1CONLbits.SEN = 1;
    while (I2C1CONLbits.SEN == 1);
    I2C1TRN = OLED_Slave_Address_Write;
    I2C_Delay();
    I2C1TRN = 0b01000000;                                                                       // Next byte is data (SSD1306 datasheet p.20 step #5)
    I2C_Delay();
      
    for (uint8_t i = 0; i < j; i ++)                                                            // Print characters in OLED_String
    {
   
        for (uint8_t r = 0; r < 91; r ++)                                                       // Cycle through all 91 character rows ([0] to [90]) of Character_Lookup table
        {       
     
            if (OLED_String[i] == Character_Lookup[r][0])                                       // First column ([0]) of Character_Lookup table used to search for matching character
            {
      
                for (uint8_t c = 1; c < 9; c ++)                                                // Cycle through columns [1] to [8] of character block, for matching character, of Character_Lookup table
                {
                    I2C1TRN = Character_Lookup[r][c];                                           // Print column of 8 pixels of character block (built in GDDRAM address pointer points to next column of character block)
                    I2C_Delay();
                }
      
                break;                                                                          // Terminate for loop after character displayed on OLED display
     
            }
   
        }
 
    }
    
    I2C1CONLbits.PEN = 1;
    while (I2C1CONLbits.PEN == 1);

}

void OLED_Number(uint8_t OLED_Row, uint8_t OLED_Column, uint16_t Number)                        // Function to write characters to SSD1306 (unsigned integer input)
{
    
    uint8_t Digit_Counter = 0;                                                                  // Counts number of digits in Number      
    char Character_Array[6];                                                                    // Store character equivalent of Number 
    
    if (Number >= 0 && Number <= 9)
    {
        Digit_Counter = 1;
    }

    else if (Number >= 10 && Number <= 99)
    {
        Digit_Counter = 2;
    }
    
    else if (Number >= 100 && Number <= 999)
    {
        Digit_Counter = 3;
    }
    
    else if (Number >= 1000 && Number <= 9999)
    {
        Digit_Counter = 4;
    }
    
    else
    {
        Digit_Counter = 9;
    }
    
    for (uint8_t i = 0; i < Digit_Counter; i ++)
    {                     
        Character_Array[Digit_Counter - 1 - i] =  Number % 10 + '0';                            // Modulus 10 (extract lowest digit) & convert to character
        Number = Number / 10;
    }
        
    OLED_String(OLED_Row, OLED_Column, Character_Array); 

}

The following list describes how each of the functions in the SSD1306.h library works:

  • I2C_Delay (line #10): Establishes a delay that waits for each I2C transmission to finish, i.e., 8 bits + ACK
  • OLED_Command (line #15): Writes individual commands to the SSD1306
  • OLED_Setup (line #29): Sets specific operating conditions for the SSD1306
  • OLED_Clear (line #74): Clears the GDDRAM of the SSD1306
  • OLED_String (line #108): Writes pixels representing ASCII characters to the GDDRAM of the SSD1306 with a character input
  • OLED_Number (line #167): Writes pixels representing ASCII characters to the GDDRAM of the SSD1306 with an unsigned intiger input

Figure 1 represents the character 2 displayed on the SSD1306 OLED display.

Figure 1: Character Block Array

The display is comprised of an array of pixels, i.e., LEDs, spanning 128 x 64. There is an array of character blocks (orange squares) spanning 16 x 8, each of which is comprised of an array of pixels, spanning 8 x 8. In each character block, a custom character font can be displayed, as shown in Figure 2.

Figure 2: Character Block

The character 2 is displayed in a custom font; however, it seems pixelated close-up. By zooming out, the pixelations appear to be less prevalent, as shown in Figure 1. Figure 3 represents the set of I2C commands that are transmitted by the MCU to the SSD1306 to result in the character 2 being displayed.

Figure 3: Character 2

As per the I2C communication standard, the MSB is transmitted first and the LSB is transmitted last. The SSD1306 waits for bytes of pixel data, after which the GDDRAM is updated, which in turn updates pixel states, i.e., ON or OFF, in the OLED display simultaneously, one page at a time. Referring back to figure 2, all the pixels in Row 7 and Column 7 are left turned OFF. This is to give some space between characters that are displayed next to each other. Referring back to Figure 1, a character can be displayed in any of the orange squares by transmitting the GDDRAM Row (page) and Column Start Addresses of the square using the OLED_String function. This function accepts a character row input from 1 to 8, and a character column input from 1 to 16. Tables 1 & 2 detail the breakdown of selecting the Lower and Higher Column Start Address, respectively.

0b00000000 | ((OLED_Column – 1)*8 & 0b00001111)
1Lower Column Start Address00000000min.
2Bitwise operator|OR
3(OLED_Column – 1)*801111000120
4Bitwise operator&AND
5Lower Column Start Address00001111max.
6Result of Lines 3 & 500001000
7Result of Lines 1 & 600001000
lower nibble8
Table 1: Lower Column Start Address

An OLED square must be selected first; in this case, it is the last square, i.e., 16. Assigning the value 16 to the variable OLED_Column in the equation in row 3 results in the value 120. As can be seen in Figure 1, pixel column #120 is the first column of the very last character block. The reason why the “-1” is used in the equation is for ease of using a range of 1 to 16, for columns, rather than 0 to 15. The multiplication by 8 is because each character block has 8 pixel columns. Following the table, a lower nibble should be reached. Continue to Table 2.

0b00010000 | ((OLED_Column – 1)*8 >> 4)
1Higher Column Start Address00010000min.
2|OR
3(OLED_Column – 1)*801111000120
4>> 4
500000111
600010111
higher nibble7
.
7Column Start Address01111000120
higher nibblelower nibble
Table 2: Higher Column Start Address

Similar to Table 1, follow Table 2 until a higher nibble is reached. The higher and lower nibbles are concatenated by the SSD1306 to form an 8-bit binary number, which in turn sets the Column Start Address.