SSD1306 OLED Display

How to display text and graphics on an SSD1306 OLED with ESP32

The SSD1306 is a tiny $0.96''$ OLED display with $128 \times 64$ pixels. Despite its small size, it produces sharp, high-contrast text and graphics because each pixel emits its own light -- there is no backlight. It communicates over I2C using just two data pins, making it one of the easiest displays to get started with. Once you learn to use it, you can display sensor readings, status messages, simple graphs, and more on a screen that draws very little power.

This guide uses the ESP32-WROOM-32 DevKit. Pin labels and GPIO numbers may differ on your board -- always check your board's pinout diagram.

Alternatives: For large, readable text in bright environments, an I2C LCD may be a better fit. See our display comparison guide for help choosing.

🔗Key Specs

ParameterValue
Display size$0.96''$ diagonal
Resolution$128 \times 64$ pixels
InterfaceI2C (also available in SPI versions)
I2C address0x3C (some modules use 0x3D)
Supply voltage$3.3\,\text{V}$ (some modules accept $5\,\text{V}$ via onboard regulator)
Current draw$\approx 20\,\text{mA}$ (depends on pixels lit)
ColorsMonochrome (white, blue, or yellow/blue split)

🔗What You'll Need

ComponentQtyNotesBuy
ESP32 dev board1AliExpress | Amazon.de .co.uk .com
SSD1306 OLED display (0.96")1AliExpress | Amazon.de .co.uk .com
Breadboard1AliExpress | Amazon.de .co.uk .com
Jumper wires4AliExpress | Amazon.de .co.uk .com

Links marked Amazon/AliExpress are affiliate links. We may earn a small commission at no extra cost to you.

🔗Wiring

The I2C version of the SSD1306 has four pins.

SSD1306 PinESP32 PinNotes
VCC3.3VCheck your module -- some need 5V
GNDGND
SDAGPIO 21Default I2C data line on ESP32
SCLGPIO 22Default I2C clock line on ESP32

Tip: The ESP32's default I2C pins are GPIO 21 (SDA) and GPIO 22 (SCL). Most tutorials and libraries assume these pins. You can remap I2C to other pins, but the defaults are recommended for beginners.

🔗Required Libraries

Install these two libraries through the Arduino Library Manager:

  1. Adafruit SSD1306 -- display driver for the SSD1306 chip
  2. Adafruit GFX Library -- graphics primitives (text, shapes, bitmaps)

To install:

  1. Go to Sketch > Include Library > Manage Libraries...
  2. Search for Adafruit SSD1306 and install it
  3. When prompted, also install Adafruit GFX Library and its dependencies

🔗Code Example: Display Text

This sketch displays a greeting and a counter on the OLED.

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1         // No reset pin on most modules
#define OLED_ADDRESS 0x3C     // Common I2C address

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

int counter = 0;

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

    if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS)) {
        Serial.println("SSD1306 allocation failed!");
        while (true);  // Halt if display not found
    }

    display.clearDisplay();
    display.setTextSize(1);       // Normal size (6x8 pixels per character)
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(0, 0);
    display.println("IoT With ESP");
    display.println("OLED Display Ready");
    display.display();            // Push buffer to screen
    delay(2000);
}

void loop() {
    display.clearDisplay();
    display.setTextSize(1);
    display.setCursor(0, 0);
    display.println("IoT With ESP");

    display.setTextSize(2);       // Double size for emphasis
    display.setCursor(0, 20);
    display.print("Count: ");
    display.println(counter);

    display.display();

    counter++;
    delay(500);
}

🔗Code Example: Draw Shapes

The Adafruit GFX library provides functions for drawing basic shapes.

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define OLED_ADDRESS 0x3C

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

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

    if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS)) {
        Serial.println("SSD1306 allocation failed!");
        while (true);
    }

    display.clearDisplay();

    // Draw a rectangle outline
    display.drawRect(0, 0, 128, 64, SSD1306_WHITE);

    // Draw a filled circle
    display.fillCircle(64, 32, 15, SSD1306_WHITE);

    // Draw a line
    display.drawLine(0, 0, 128, 64, SSD1306_WHITE);

    // Draw a triangle
    display.drawTriangle(100, 10, 90, 50, 120, 50, SSD1306_WHITE);

    display.display();
}

void loop() {
    // Nothing to update
}

🔗Code Example: Display Sensor Data

A practical example that reads a value and displays it. This simulates a temperature reading, but you could easily replace it with a real sensor.

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define OLED_ADDRESS 0x3C

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

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

    if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDRESS)) {
        Serial.println("SSD1306 allocation failed!");
        while (true);
    }
}

void displayReading(float temperature, float humidity) {
    display.clearDisplay();

    // Header
    display.setTextSize(1);
    display.setCursor(0, 0);
    display.println("--- Sensor Data ---");

    // Temperature (large text)
    display.setTextSize(2);
    display.setCursor(0, 16);
    display.print(temperature, 1);
    display.setTextSize(1);
    display.print(" ");
    display.println("C");

    // Humidity
    display.setTextSize(2);
    display.setCursor(0, 40);
    display.print(humidity, 1);
    display.setTextSize(1);
    display.print(" ");
    display.println("%RH");

    display.display();
}

void loop() {
    // Replace these with actual sensor readings
    float temp = 23.5 + random(-10, 10) / 10.0;
    float hum = 55.0 + random(-20, 20) / 10.0;

    displayReading(temp, hum);
    delay(2000);
}

🔗How It Works

The SSD1306 OLED uses I2C (Inter-Integrated Circuit) to communicate with the ESP32. I2C is a two-wire protocol:

  • SDA (Serial Data) carries the data
  • SCL (Serial Clock) provides the timing

The ESP32 acts as the I2C master and sends commands and pixel data to the display. The Adafruit SSD1306 library maintains a frame buffer in the ESP32's memory -- a copy of every pixel on the screen. When you call drawing functions like println() or drawCircle(), they modify this buffer in RAM. The pixels only update on the physical display when you call display.display(), which transfers the entire buffer over I2C.

This buffer-based approach means you can compose a full frame (clear, draw text, draw shapes) and then send it all at once, avoiding flicker.

🔗Display Memory

The $128 \times 64$ pixel display requires:

$$\frac{128 \times 64}{8} = 1024\,\text{bytes}$$

of buffer RAM (one bit per pixel). The Adafruit library allocates this buffer automatically.

🔗Troubleshooting

ProblemPossible CauseSolution
Blank screenWrong I2C addressTry 0x3D instead of 0x3C. Run an I2C scanner sketch (see below).
Blank screenSDA/SCL swappedDouble-check that SDA is on GPIO 21 and SCL on GPIO 22
Display shows garbageWrong screen dimensions in codeVerify SCREEN_WIDTH and SCREEN_HEIGHT match your module ($128 \times 64$ or $128 \times 32$)
"SSD1306 allocation failed"Not enough memory or wrong addressCheck wiring and I2C address
Display is very dimMost pixels are off (by design)OLED brightness is per-pixel. More lit pixels = brighter appearance. This is normal.
Text appears cut offCursor position out of boundsKeep coordinates within $0$--$127$ (x) and $0$--$63$ (y)

🔗I2C Scanner

If you are unsure of your display's address, upload this sketch. It scans the I2C bus and reports all detected devices.

#include <Wire.h>

void setup() {
    Wire.begin();
    Serial.begin(115200);
    Serial.println("Scanning I2C bus...");

    for (byte address = 1; address < 127; address++) {
        Wire.beginTransmission(address);
        if (Wire.endTransmission() == 0) {
            Serial.print("Device found at 0x");
            Serial.println(address, HEX);
        }
    }
    Serial.println("Scan complete.");
}

void loop() {}

🔗Next Steps

  • Display real sensor data from a DS18B20 or BME280 temperature sensor
  • Build a clock display using NTP time synchronization over WiFi
  • Draw simple bar graphs or sparklines from sensor history
  • Create a menu system with button navigation