SH1106-Based OLED Display

128x64 Monochrome OLED Displays for Affordable Prices

The SH1106 is a modern, cost-optimized OLED display driver, often considered a successor to the widely popular SSD1306. It is commonly found in affordable 0.96” and 1.3” monochrome OLED displays.

Overview

The SSD1306 (released in 2007) has long been the dominant OLED driver, but the SH1106 (introduced in 2013) has gained popularity in recent years due to its lower cost. The primary trade-off is that the SH1106 lacks hardware acceleration for scrolling and animations—features available in the SSD1306. However, for most typical OLED use cases, such as displaying static text and simple graphics, these features are not essential.

The SH1106 supports monochrome OLED displays with a maximum resolution of 132x64 pixels, though most breakout boards use a 128x64 resolution. These displays typically communicate via the I2C interface.

Breakout Boards

Ready-to-use breakout boards include both the SH1106 driver and an OLED display, with 0.96” and 1.3” sizes being the most common.

Some displays are also integrated into keyboard-display-combos, such as the example below:

Display Colors

SH1106-based OLED displays are always monochrome, but the emitted light color may vary.

Color Remarks
White Best contrast and longest lifespan.
Blue Wears out faster than other colors.
Yellow A newer option, typically more expensive.
Yellow/Blue A dual-color display where a small section is yellow, while the rest is blue.

All OLED displays degrade over time. After 10,000 hours or more of continuous use, brightness may fade. Blue displays are more susceptible to wear than other colors.

I2C Interface

Most SH1106 breakout boards utilize the simple I2C protocol, although some versions support SPI for faster data transfer.

The default I2C address is typically 0x3C or 0x3D, and it is often fixed:

  • 128x64 displays usually use 0x3C.
  • 128x32 displays often use 0x3D.
  • Some exceptions exist, so always check your display’s documentation.

If you need to connect multiple OLED displays, consider using an I2C multiplexer like the PCF8575, or choose an SPI-based display, which allows selecting the active device via the CS (Chip Select) pin.

Programming

The SH1106 is supported in all major development environments.

ESPHome

Using ESPHome is the simplest approach, as it natively supports the SSD1306 OLED Display component, which is compatible with most monochrome OLED drivers, including the SH1106. The following OLED drivers are supported:

  • SSD1306: 128x32, 128x64, 96x16, 72x40, 64x48
  • SSD1305: 128x32, 128x64
  • SH1107: 128x64, 128x128
  • SH1106: 128x32, 128x64, 96x16, 64x48

Here is an example configuration:

# define the I2C pins that you use 
# to connect the display to your microcontroller
i2c:
  sda: GPIO21
  scl: GPIO22

display:
  - platform: ssd1306_i2c
    model: "SH1106 128x64"  # 0.96/1.3" 128x64 OLED display
    address: 0x3C           # default address, use 0x3D if default fails
    rotation: 180           # rotate content if needed
    update_interval: 3000ms # 1s is default (1000ms)
    lambda: |-
      it.print(0, 0, id(lato400), "Hello World!");

# you need at least one font to output text
font:
  - file:
      type: gfonts
      family: Lato
      weight: 400
    id: lato400
    size: 20

To compile and upload the sample configuration, simply follow these steps.

OLED displays are known for their crisp and clear visuals. If the text appears blurry, as shown in the image, it’s likely that a font has been scaled incorrectly. For optimal results, use pixel fonts that are specifically designed for the display size.

You can now utilize all the graphics commands supported by the ESPHome Display Component to draw images, shapes, lines, arcs, and more.

C++

For direct programming, two popular libraries support the SH1106:

  • U8G2 Library: A universal library for monochrome displays, primarily focused on text output.
  • Adafruit GFX: + Adafruit_SH110x A specific library for SH110x displays, which enables the use of the hardware-neutral Adafruit GFX library. This library excels at advanced drawing primitives.

Read more about the differences, strengths, and weaknesses of each library.

Using the U8G2 Library

The U8G2 library is a robust library with a clear focus on text output. It targets a large set of monochrome display drivers, including the SH1106.

This library is not necessarily intuitive at first. For example, it uses a character-based coordinate system, not pixels. A 128x64 pixel display has 16 columns and 8 rows. This is crucial with methods like setCursor() or drawText(). You also must load a font, else no text is outputted. Apparently there is no default font. You may want to consult the reference before diving deeper into this library.

This is the platformio.ini I used with a standard ESP32 DevKitC V4 board:

[env:esp32dev]
platform = espressif32
board = esp32dev
framework = arduino
board_upload.flash_size = 4MB
monitor_speed = 115200
upload_speed = 921600
build_flags = 
	-DARDUINO_ESP32_DEV
lib_deps = olikraus/U8g2@^2.36.4

Selecting the OLED Driver

The u8g2 library selects the display driver through classes. For a 128x64 SH1106 OLED display connected via I2C, use one of the following classes:

  • U8X8_SH1106_128X64_NONAME_HW_I2C: For hardware I2C.
  • U8X8_SH1106_128X64_NONAME_SW_I2C: For software I2C (allows custom I2C GPIOs).

Here is a simple example that is using software-emulated I2C. You can use any available digital output GPIO. Just adjust mySDA and mySCL:


#include <Arduino.h>
#include <U8x8lib.h>

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif


// try with software-emulated I2C first:
#define mySDA 21
#define mySCL 22
U8X8_SH1106_128X64_NONAME_SW_I2C u8x8(mySCL, mySDA);

// if this works, use the hardware I2C pins to connect the
// display, and use hardware-accelerated I2C
//U8X8_SH1106_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE);



void setup()
{
  u8x8.begin();
  u8x8.setFlipMode(1);

  u8x8.setFont(u8x8_font_chroma48medium8_r);  

  u8x8.inverse();
  u8x8.setCursor(0,0);  // 128x64 = 16 columns and 8 rows
  u8x8.print("U8x8 Library");
  u8x8.noInverse();
}

void loop() {
  u8x8.setCursor(0, 2);
  u8x8.print("Hardware SDA:");
  u8x8.setCursor(14, 2);
  u8x8.print(SDA);

  u8x8.setCursor(0, 4);
  u8x8.print("Hardware SCL:");
  u8x8.setCursor(14, 4);
  u8x8.print(SCL);

  delay(10000);
}

This should output some text onto your OLED display. You should now see the GPIO numbers for hardware-accelerated I2C. For classic ESP32, this should be 21 for SDA and 22 for SCL.

Hardware I2C

Next, try using the much faster hardware-accelerated I2C: wire the display to the hardware I2C GPIOs, and change the start of the code:

// try with software-emulated I2C first:
// #define mySDA 21
// #define mySCL 22
// U8X8_SH1106_128X64_NONAME_SW_I2C u8x8(mySCL, mySDA);

// if this works, use the hardware I2C pins to connect the
// display, and use hardware-accelerated I2C
U8X8_SH1106_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE);

Note how this constructor takes no GPIO numbers. It always uses the GPIOs that were defined in the constants SDA and SCL.

Hardware-accelerated I2C is so much faster that you should always use it for displays: while you can see text lines being drawn with software-emulated I2C, text shows instantaneous with hardware-accelerated I2C. For the latter to work, you must use the approriate class (U8X8_SH1106_128X64_NONAME_HW_I2C instead of U8X8_SH1106_128X64_NONAME_SW_I2C), and you must have wired the display to the GPIOs defined in the constants SDA and SCL. These may vary from board to board. If in doubt, output the constants first: display them using software-emulated I2C (as shown above).

More Examples

If this quick example works for you, then maybe you are hungry for more:

C++ Example Code using u8g2
#include <Arduino.h>
#include <U8x8lib.h>

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

// use hardware I2C (if you know the hardware i2c GPIOs for your board):
//U8X8_SH1106_128X64_NONAME_HW_I2C u8x8(U8X8_PIN_NONE);

// use software I2C (and define GPIOs yourself)
// pins below are for ESP32S, adjust as needed for other mc models:
#define SDA 21
#define SCL 22

U8X8_SH1106_128X64_NONAME_SW_I2C u8x8(SCL, SDA);

void setup(void)
{
  u8x8.begin();
  u8x8.setFlipMode(1);
}

void pre(void)
{
  u8x8.setFont(u8x8_font_amstrad_cpc_extended_f);    
  u8x8.clear();

  u8x8.inverse();
  u8x8.print(" U8x8 Library ");
  u8x8.setFont(u8x8_font_chroma48medium8_r);  
  u8x8.noInverse();
  u8x8.setCursor(0,1);
}

void draw_bar(uint8_t c, uint8_t is_inverse)
{	
  uint8_t r;
  u8x8.setInverseFont(is_inverse);
  for( r = 0; r < u8x8.getRows(); r++ )
  {
    u8x8.setCursor(c, r);
    u8x8.print(" ");
  }
}

void draw_ascii_row(uint8_t r, int start)
{
  int a;
  uint8_t c;
  for( c = 0; c < u8x8.getCols(); c++ )
  {
    u8x8.setCursor(c,r);
    a = start + c;
    if ( a <= 255 )
      u8x8.write(a);
  }
}

void loop(void)
{
  int i;
  uint8_t c, r, d;
  pre();
  u8x8.print("github.com/");
  u8x8.setCursor(0,2);
  u8x8.print("olikraus/u8g2");
  delay(2000);
  u8x8.setCursor(0,3);
  u8x8.print("Tile size:");
  u8x8.print((int)u8x8.getCols());
  u8x8.print("x");
  u8x8.print((int)u8x8.getRows());
  
  delay(2000);
   
  pre();
  for( i = 19; i > 0; i-- )
  {
    u8x8.setCursor(3,2);
    u8x8.print(i);
    u8x8.print("  ");
    delay(150);
  }
  
  draw_bar(0, 1);
  for( c = 1; c < u8x8.getCols(); c++ )
  {
    draw_bar(c, 1);
    draw_bar(c-1, 0);
    delay(50);
  }
  draw_bar(u8x8.getCols()-1, 0);

  pre();
  u8x8.setFont(u8x8_font_amstrad_cpc_extended_f); 
  for( d = 0; d < 8; d ++ )
  {
    for( r = 1; r < u8x8.getRows(); r++ )
    {
      draw_ascii_row(r, (r-1+d)*u8x8.getCols() + 32);
    }
    delay(400);
  }

  draw_bar(u8x8.getCols()-1, 1);
  for( c = u8x8.getCols()-1; c > 0; c--)
  {
    draw_bar(c-1, 1);
    draw_bar(c, 0);
    delay(50);
  }
  draw_bar(0, 0);

  pre();
  u8x8.drawString(0, 2, "Small");
  u8x8.draw2x2String(0, 5, "Scale Up");
  delay(3000);

  pre();
  u8x8.drawString(0, 2, "Small");
  u8x8.setFont(u8x8_font_px437wyse700b_2x2_r);
  u8x8.drawString(0, 5, "2x2 Font");
  delay(3000);

  pre();
  u8x8.drawString(0, 1, "3x6 Font");
  u8x8.setFont(u8x8_font_inb33_3x6_n);
  for(i = 0; i < 100; i++ )
  {
    u8x8.setCursor(0, 2);
    u8x8.print(i);			// Arduino Print function
    delay(10);
  }
  for(i = 0; i < 100; i++ )
  {
    u8x8.drawString(0, 2, u8x8_u16toa(i, 5));	// U8g2 Build-In functions
    delay(10);		
  }

  pre();
  u8x8.drawString(0, 2, "Weather");
  u8x8.setFont(u8x8_font_open_iconic_weather_4x4);
  for(c = 0; c < 6; c++ )
  {
    u8x8.drawGlyph(0, 4, '@'+c);
    delay(300);
  }
  

  pre();
  u8x8.print("print \\n\n");
  delay(500);
  u8x8.println("println");
  delay(500);
  u8x8.println("done");
  delay(1500);

  pre();
  u8x8.fillDisplay();
  for( r = 0; r < u8x8.getRows(); r++ )
  {
    u8x8.clearLine(r);
    delay(100);
  }
  delay(1000);
}

Adafruit SH110x

Adafruit display libraries are widely popular due to their modular design. They consist of two separate libraries:

This modular approach allows the source code to be compatible with a variety of displays; you simply adjust the specific driver library as needed.

Better not use “hacked” libraries!

The SSD1306 was the dominant driver for monochrome OLED displays for a long time. When the SH1106 appeared a few years later, it initially lacked driver support.

As a result, users modified the original Adafruit SSD1306 library to work with the SH1106. One such modified Adafruit library for SH1106 exists, and others can be found as well.

Unfortunately, these modifications were not always done according to standards. They may work for the original author or even for you, but you might encounter header file errors and linker issues if your environment doesn’t match the library author’s, for example, using PlatformIO instead of Arduino IDE.

Moreover, these adapted libraries are no longer updated by the original authors and could be outdated.

Adafruit has since released an official library targeting all SH110x monochrome OLED displays: Adafruit_SH110x. This is the library you should use if you want to work with the Adafruit GFX.

Conclusions

Getting started with ESPHome was incredibly fast—it took me only about five minutes to put together and run an example code. In contrast, it took over an hour (and a fair bit of frustration) to figure out the specifics of the U8G2 library. ESPHome is far more efficient, especially for developers.

However, the firmware generated by ESPHome is significantly larger than manually compiled C++ code. This is expected because ESPHome includes standard features like OTA updates and encrypted wireless communication.

As a result, C++ programming is generally better suited for highly proficient developers or situations where memory space is limited. For most users—especially those working with microcontrollers like the ESP32, which has 4+ MB of flash memory—ESPHome offers a more streamlined and user-friendly approach to firmware development.

Data Sheets

SH1106

Slow Website?

This website is very fast, and pages should appear instantly. If this site is slow for you, then your routing may be messed up, and this issue does not only affect done.land, but potentially a few other websites and downloads as well. Here are simple steps to speed up your Internet experience and fix issues with slow websites and downloads..

Comments

Please do leave comments below. I am using utteran.ce, an open-source and ad-free light-weight commenting system.

Here is how your comments are stored

Whenever you leave a comment, a new github issue is created on your behalf.

  • All comments become trackable issues in the Github Issues section, and I (and you) can follow up on them.

  • There is no third-party provider, no disrupting ads, and everything remains transparent inside github.

Github Users Yes, Spammers No

To keep spammers out and comments attributable, all you do is log in using your (free) github account and grant utteranc.es the permission to submit issues on your behalf.

If you don’t have a github account yet, go get yourself one - it’s free and simple.

If for any reason you do not feel comfortable with letting the commenting system submit issues for you, then visit Github Issues directly, i.e. by clicking the red button Submit Issue at the bottom of each page, and submit your issue manually. You control everything.

Discussions

For chit-chat and quick questions, feel free to visit and participate in Discussions. They work much like classic forums or bulletin boards. Just keep in mind: your valued input isn’t equally well trackable there.

  Show on Github    Submit Issue

(content created Apr 28, 2024 - last updated Jan 10, 2025)