SH1106 OLED Display Not Working on Arduino Nano ESP32

I’ve got an Arduino Nano ESP32 that I’m trying to use with an OLED I2C Display. ESPHome can’t seem to communicate with the display.

I’m going crazy trying to figure this out. Any extra eyes on the problem would be immensely appreciated. Photos of the current setup:


Pins are connected as follows:

ESP32 Display
GND GND
3.3v VCC
A5 (GPIO12) SCK
A4 (GPIO11) SDA

Log Output

[17:14:12][I][app:100]: ESPHome version 2024.6.1 compiled on Jun 20 2024, 17:13:49
[17:14:12][C][wifi:599]: WiFi:
<snip>
[17:14:12][C][logger:185]: Logger:
[17:14:12][C][logger:186]:   Level: VERY_VERBOSE
[17:14:12][C][logger:188]:   Log Baud Rate: 115200
[17:14:12][C][logger:189]:   Hardware UART: USB_CDC
[17:14:12][C][i2c.arduino:071]: I2C Bus:
[17:14:12][C][i2c.arduino:072]:   SDA Pin: GPIO11
[17:14:12][C][i2c.arduino:073]:   SCL Pin: GPIO12
[17:14:12][C][i2c.arduino:074]:   Frequency: 50000 Hz
[17:14:12][C][i2c.arduino:086]:   Recovery: bus successfully recovered
[17:14:12][I][i2c.arduino:096]: Results from i2c bus scan:
[17:14:12][I][i2c.arduino:098]: Found no i2c devices!
[17:14:12][C][homeassistant.time:010]: Home Assistant Time:
[17:14:12][C][homeassistant.time:011]:   Timezone: 'EST5EDT,M3.2.0,M11.1.0'
[17:14:12][C][captive_portal:088]: Captive Portal:
[17:14:12][C][mdns:115]: mDNS:
[17:14:12][C][mdns:116]:   Hostname: test-esp32
[17:14:12][V][mdns:117]:   Services:
[17:14:12][V][mdns:119]:   - _esphomelib, _tcp, 6053
[17:14:12][V][mdns:121]:     TXT: version = 2024.6.1
[17:14:12][V][mdns:121]:     TXT: mac = ecda3b552920
[17:14:12][V][mdns:121]:     TXT: platform = ESP32
[17:14:12][V][mdns:121]:     TXT: board = arduino_nano_esp32
[17:14:12][V][mdns:121]:     TXT: network = wifi
[17:14:12][V][mdns:121]:     TXT: api_encryption = Noise_NNpsk0_25519_ChaChaPoly_SHA256
[17:14:12][C][esphome.ota:073]: Over-The-Air updates:
[17:14:12][C][esphome.ota:074]:   Address: test-esp32.local:3232
[17:14:12][C][esphome.ota:075]:   Version: 2
[17:14:12][C][esphome.ota:078]:   Password configured
[17:14:12][C][safe_mode:018]: Safe Mode:
[17:14:12][C][safe_mode:020]:   Boot considered successful after 60 seconds
[17:14:12][C][safe_mode:021]:   Invoke after 3 boot attempts
[17:14:12][C][safe_mode:023]:   Remain in safe mode for 300 seconds
[17:14:12][C][api:139]: API Server:
[17:14:12][C][api:140]:   Address: test-esp32.local:6053
[17:14:12][C][api:142]:   Using noise encryption: YES
[17:14:12][C][ssd1306_i2c:023]: I2C SSD1306
[17:14:12][C][ssd1306_i2c:023]:   Rotations: 0 °
[17:14:12][C][ssd1306_i2c:023]:   Dimensions: 128px x 64px
[17:14:12][C][ssd1306_i2c:024]:   Address: 0x3C
[17:14:12][C][ssd1306_i2c:025]:   Model: SH1106 128x64
[17:14:12][C][ssd1306_i2c:027]:   External VCC: NO
[17:14:12][C][ssd1306_i2c:028]:   Flip X: YES
[17:14:12][C][ssd1306_i2c:029]:   Flip Y: YES
[17:14:12][C][ssd1306_i2c:030]:   Offset X: 0
[17:14:12][C][ssd1306_i2c:031]:   Offset Y: 0
[17:14:12][C][ssd1306_i2c:032]:   Inverted Color: NO
[17:14:12][C][ssd1306_i2c:033]:   Update Interval: 0.5s
[17:14:12][E][ssd1306_i2c:036]: Communication with SSD1306 failed!
[17:14:12][E][component:082]:   Component display is marked FAILED
[17:14:36][VV][scheduler:226]: Running interval '' with interval=60000 last_execution=4294941765 (now=34469)
[17:15:04][I][safe_mode:041]: Boot seems successful; resetting boot loop counter

Configuration:

esphome:
  name: test-esp32

esp32:
  board: arduino_nano_esp32
  framework:
    type: arduino
    version: 2.0.14

# Enable logging
logger:
  baud_rate: 115200
  tx_buffer_size: 512
  deassert_rts_dtr: false
  hardware_uart: USB_CDC
  level: VERY_VERBOSE
  logs: {}

# Enable Home Assistant API
api:
  encryption:
    key: [redacted]
  port: 6053
  password: ''
  reboot_timeout: 15min

safe_mode:
  reboot_timeout: 5min
  num_attempts: 3

ota:
  - platform: esphome
    password: [redacted]
    version: 2
    port: 3232

wifi:
  ap:
    ssid: Test-Esp32 Fallback Hotspot
    password: [redacted]
    ap_timeout: 1min
  domain: .local
  reboot_timeout: 15min
  power_save_mode: LIGHT
  fast_connect: false
  passive_scan: false
  enable_on_boot: true
  networks:
  - ssid: [redacted]
    password: [redacted]
    priority: 0.0
  use_address: test-esp32.local

captive_portal:

time:
  - platform: homeassistant
    id: ha_time

i2c:
  id: bus_a
  scan: true
  sda: GPIO11
  scl: GPIO12

display:
  - platform: ssd1306_i2c
    model: "SH1106 128x64"
    update_interval: 0.5s
    address: 0x3C
    lambda: |-
      it.printf(64, 0, id(font1), TextAlign::TOP_CENTER, "Temperature:");

font:
  - file: 'fonts/slkscr.ttf'
    id: font1
    size: 8

Things I’ve tried that haven’t helped so far:

  • Powering the display from the 3.3v pin on the Nano as well as the VBUS pin

  • Changing I2C frequencies. I’ve tried 50Hz and 100Hz - 900Hz in increments of 100Hz

  • Changing the display model to ssd1306 128x64 and SH1106 128x64

  • The Arduino gets power via USB-C. I’ve tried this connected to both a normal AC adapter as well as my computer.

  • Various lengths/brands of USB-C cables

  • If I test with a multi-meter across the 3.3v and ground pins on the Arduino, I get just shy of 3.3v. Same across the GND and VCC pins on the display.

  • The displays came in a 5-pack. I have tried multiple units to ensure one wasn’t bad.

  • Setting setup_priority: -500 in the display: block

I used the Arduino sketch below to verify the I2C address of the display. The output is:

I2C scanner. Scanning ...
Found address: 60 (0x3C)
Done.
Found 1 device(s).

Code:

#include <Wire.h>

void setup() {
  Serial.begin (115200);

  // Leonardo: wait for serial port to connect
  while (!Serial) 
    {
    }

  Serial.println ();
  Serial.println ("I2C scanner. Scanning ...");
  byte count = 0;
  
  Wire.begin();
  for (byte i = 8; i < 120; i++)
  {
    Wire.beginTransmission (i);
    if (Wire.endTransmission () == 0)
      {
      Serial.print ("Found address: ");
      Serial.print (i, DEC);
      Serial.print (" (0x");
      Serial.print (i, HEX);
      Serial.println (")");
      count++;
      delay (1);  // maybe unneeded?
      } // end of good response
  } // end of for loop
  Serial.println ("Done.");
  Serial.print ("Found ");
  Serial.print (count, DEC);
  Serial.println (" device(s).");
}  // end of setup

void loop() {}

In case it helps, here’s the Arduino Nano ESP32 cheat sheet which includes a pin map.

The following example code from the Adafruit_SH110x repository works with the display connected as shown in the photos in the OP.

/*********************************************************************
  This is an example for our Monochrome OLEDs based on SH110X drivers

  This example is for a 128x64 size display using I2C to communicate
  3 pins are required to interface (2 I2C and one reset)

  Adafruit invests time and resources providing this open source code,
  please support Adafruit and open-source hardware by purchasing
  products from Adafruit!

  Written by Limor Fried/Ladyada  for Adafruit Industries.
  BSD license, check license.txt for more information
  All text above, and the splash screen must be included in any redistribution

  i2c SH1106 modified by Rupert Hirst  12/09/21
*********************************************************************/



#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SH110X.h>

/* Uncomment the initialize the I2C address , uncomment only one, If you get a totally blank screen try the other*/
#define i2c_Address 0x3c //initialize with the I2C addr 0x3C Typically eBay OLED's
//#define i2c_Address 0x3d //initialize with the I2C addr 0x3D Typically Adafruit OLED's

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET -1   //   QT-PY / XIAO
Adafruit_SH1106G display = Adafruit_SH1106G(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);


#define NUMFLAKES 10
#define XPOS 0
#define YPOS 1
#define DELTAY 2


#define LOGO16_GLCD_HEIGHT 16
#define LOGO16_GLCD_WIDTH  16
static const unsigned char PROGMEM logo16_glcd_bmp[] =
{ B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000
};


void setup()   {

  Serial.begin(9600);

  // Show image buffer on the display hardware.
  // Since the buffer is intialized with an Adafruit splashscreen
  // internally, this will display the splashscreen.

  delay(250); // wait for the OLED to power up
  display.begin(i2c_Address, true); // Address 0x3C default
 //display.setContrast (0); // dim display
 
  display.display();
  delay(2000);

  // Clear the buffer.
  display.clearDisplay();

  // draw a single pixel
  display.drawPixel(10, 10, SH110X_WHITE);
  // Show the display buffer on the hardware.
  // NOTE: You _must_ call display after making any drawing commands
  // to make them visible on the display hardware!
  display.display();
  delay(2000);
  display.clearDisplay();

  // draw many lines
  testdrawline();
  display.display();
  delay(2000);
  display.clearDisplay();

  // draw rectangles
  testdrawrect();
  display.display();
  delay(2000);
  display.clearDisplay();

  // draw multiple rectangles
  testfillrect();
  display.display();
  delay(2000);
  display.clearDisplay();

  // draw mulitple circles
  testdrawcircle();
  display.display();
  delay(2000);
  display.clearDisplay();

  // draw a SH110X_WHITE circle, 10 pixel radius
  display.fillCircle(display.width() / 2, display.height() / 2, 10, SH110X_WHITE);
  display.display();
  delay(2000);
  display.clearDisplay();

  testdrawroundrect();
  delay(2000);
  display.clearDisplay();

  testfillroundrect();
  delay(2000);
  display.clearDisplay();

  testdrawtriangle();
  delay(2000);
  display.clearDisplay();

  testfilltriangle();
  delay(2000);
  display.clearDisplay();

  // draw the first ~12 characters in the font
  testdrawchar();
  display.display();
  delay(2000);
  display.clearDisplay();



  // text display tests
  display.setTextSize(1);
  display.setTextColor(SH110X_WHITE);
  display.setCursor(0, 0);
  display.println("Failure is always an option");
  display.setTextColor(SH110X_BLACK, SH110X_WHITE); // 'inverted' text
  display.println(3.141592);
  display.setTextSize(2);
  display.setTextColor(SH110X_WHITE);
  display.print("0x"); display.println(0xDEADBEEF, HEX);
  display.display();
  delay(2000);
  display.clearDisplay();

  // miniature bitmap display
  display.drawBitmap(30, 16,  logo16_glcd_bmp, 16, 16, 1);
  display.display();
  delay(1);

  // invert the display
  display.invertDisplay(true);
  delay(1000);
  display.invertDisplay(false);
  delay(1000);
  display.clearDisplay();

  // draw a bitmap icon and 'animate' movement
  testdrawbitmap(logo16_glcd_bmp, LOGO16_GLCD_HEIGHT, LOGO16_GLCD_WIDTH);
}


void loop() {

}


void testdrawbitmap(const uint8_t *bitmap, uint8_t w, uint8_t h) {
  uint8_t icons[NUMFLAKES][3];

  // initialize
  for (uint8_t f = 0; f < NUMFLAKES; f++) {
    icons[f][XPOS] = random(display.width());
    icons[f][YPOS] = 0;
    icons[f][DELTAY] = random(5) + 1;

    Serial.print("x: ");
    Serial.print(icons[f][XPOS], DEC);
    Serial.print(" y: ");
    Serial.print(icons[f][YPOS], DEC);
    Serial.print(" dy: ");
    Serial.println(icons[f][DELTAY], DEC);
  }

  while (1) {
    // draw each icon
    for (uint8_t f = 0; f < NUMFLAKES; f++) {
      display.drawBitmap(icons[f][XPOS], icons[f][YPOS], bitmap, w, h, SH110X_WHITE);
    }
    display.display();
    delay(200);

    // then erase it + move it
    for (uint8_t f = 0; f < NUMFLAKES; f++) {
      display.drawBitmap(icons[f][XPOS], icons[f][YPOS], bitmap, w, h, SH110X_BLACK);
      // move it
      icons[f][YPOS] += icons[f][DELTAY];
      // if its gone, reinit
      if (icons[f][YPOS] > display.height()) {
        icons[f][XPOS] = random(display.width());
        icons[f][YPOS] = 0;
        icons[f][DELTAY] = random(5) + 1;
      }
    }
  }
}


void testdrawchar(void) {
  display.setTextSize(1);
  display.setTextColor(SH110X_WHITE);
  display.setCursor(0, 0);

  for (uint8_t i = 0; i < 168; i++) {
    if (i == '\n') continue;
    display.write(i);
    if ((i > 0) && (i % 21 == 0))
      display.println();
  }
  display.display();
  delay(1);
}

void testdrawcircle(void) {
  for (int16_t i = 0; i < display.height(); i += 2) {
    display.drawCircle(display.width() / 2, display.height() / 2, i, SH110X_WHITE);
    display.display();
    delay(1);
  }
}

void testfillrect(void) {
  uint8_t color = 1;
  for (int16_t i = 0; i < display.height() / 2; i += 3) {
    // alternate colors
    display.fillRect(i, i, display.width() - i * 2, display.height() - i * 2, color % 2);
    display.display();
    delay(1);
    color++;
  }
}

void testdrawtriangle(void) {
  for (int16_t i = 0; i < min(display.width(), display.height()) / 2; i += 5) {
    display.drawTriangle(display.width() / 2, display.height() / 2 - i,
                         display.width() / 2 - i, display.height() / 2 + i,
                         display.width() / 2 + i, display.height() / 2 + i, SH110X_WHITE);
    display.display();
    delay(1);
  }
}

void testfilltriangle(void) {
  uint8_t color = SH110X_WHITE;
  for (int16_t i = min(display.width(), display.height()) / 2; i > 0; i -= 5) {
    display.fillTriangle(display.width() / 2, display.height() / 2 - i,
                         display.width() / 2 - i, display.height() / 2 + i,
                         display.width() / 2 + i, display.height() / 2 + i, SH110X_WHITE);
    if (color == SH110X_WHITE) color = SH110X_BLACK;
    else color = SH110X_WHITE;
    display.display();
    delay(1);
  }
}

void testdrawroundrect(void) {
  for (int16_t i = 0; i < display.height() / 2 - 2; i += 2) {
    display.drawRoundRect(i, i, display.width() - 2 * i, display.height() - 2 * i, display.height() / 4, SH110X_WHITE);
    display.display();
    delay(1);
  }
}

void testfillroundrect(void) {
  uint8_t color = SH110X_WHITE;
  for (int16_t i = 0; i < display.height() / 2 - 2; i += 2) {
    display.fillRoundRect(i, i, display.width() - 2 * i, display.height() - 2 * i, display.height() / 4, color);
    if (color == SH110X_WHITE) color = SH110X_BLACK;
    else color = SH110X_WHITE;
    display.display();
    delay(1);
  }
}

void testdrawrect(void) {
  for (int16_t i = 0; i < display.height() / 2; i += 2) {
    display.drawRect(i, i, display.width() - 2 * i, display.height() - 2 * i, SH110X_WHITE);
    display.display();
    delay(1);
  }
}

void testdrawline() {
  for (int16_t i = 0; i < display.width(); i += 4) {
    display.drawLine(0, 0, i, display.height() - 1, SH110X_WHITE);
    display.display();
    delay(1);
  }
  for (int16_t i = 0; i < display.height(); i += 4) {
    display.drawLine(0, 0, display.width() - 1, i, SH110X_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();
  for (int16_t i = 0; i < display.width(); i += 4) {
    display.drawLine(0, display.height() - 1, i, 0, SH110X_WHITE);
    display.display();
    delay(1);
  }
  for (int16_t i = display.height() - 1; i >= 0; i -= 4) {
    display.drawLine(0, display.height() - 1, display.width() - 1, i, SH110X_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();
  for (int16_t i = display.width() - 1; i >= 0; i -= 4) {
    display.drawLine(display.width() - 1, display.height() - 1, i, 0, SH110X_WHITE);
    display.display();
    delay(1);
  }
  for (int16_t i = display.height() - 1; i >= 0; i -= 4) {
    display.drawLine(display.width() - 1, display.height() - 1, 0, i, SH110X_WHITE);
    display.display();
    delay(1);
  }
  delay(250);

  display.clearDisplay();
  for (int16_t i = 0; i < display.height(); i += 4) {
    display.drawLine(display.width() - 1, 0, 0, i, SH110X_WHITE);
    display.display();
    delay(1);
  }
  for (int16_t i = 0; i < display.width(); i += 4) {
    display.drawLine(display.width() - 1, 0, i, display.height() - 1, SH110X_WHITE);
    display.display();
    delay(1);
  }
  delay(250);
}