OLED Animation Maker OLED Maker
💻 Example code

Arduino OLED 128x64 I2C Example Code: 10 Copy-Paste SSD1306 Sketches

Ten short Arduino OLED 128x64 I2C examples you can paste and upload: hello world, text, shapes, a bitmap, scrolling, a 128x32 version and an I2C scanner, with a pin table for every common board.

By Ashish Oct 29, 2026 9 min read Code
📋

One skeleton, many examples

Example 2 is the full skeleton. The rest only swap setup() and loop().

📐

128x64 and 128x32

One constant changes between the 0.96 inch and the 0.91 inch module.

🔌

Pins for 5 boards

Uno, Nano, Mega, ESP32 and ESP8266 in one table.

🩺

Error fixes

The blank screen, the half screen and the reset loop, with the cause of each.

Arduino OLED example code: a 128x64 SSD1306 I2C module showing text, a circle and a box

This page is a box of Arduino OLED 128x64 I2C example sketches. I keep it open in a tab during workshops, because most questions I get are "what is the one line that does X". Each example below is complete: paste it into the Arduino IDE, upload, and it runs on a standard 0.96 inch SSD1306 module. Examples 3 to 10 reuse the includes and the display object from Example 2, so keep those lines at the top and replace only setup() and loop(). There is also a 128x32 version, an I2C scanner and a pin table for the boards I see most.

If you have never wired an OLED before, start with my full Arduino OLED display tutorial, which explains the module, the library install and the wiring step by step. This page assumes the display is already connected and you want working code fast.

Before you paste: library and wiring in 30 seconds

In the Arduino IDE open Library Manager and install Adafruit SSD1306. Accept the prompt to install Adafruit GFX too. Every example here uses those two libraries plus the built-in Wire.

BoardSDASCLPower the OLED from
Arduino Uno / NanoA4A55V or 3.3V
Arduino Mega 256020215V or 3.3V
Arduino Leonardo / Micro235V or 3.3V
ESP32 DevKitGPIO 21GPIO 223.3V
ESP8266 NodeMCU / D1 miniD2 (GPIO4)D1 (GPIO5)3.3V

Most blue 4-pin modules have a regulator and pull-up resistors on board, so 5V on an Uno is fine. On an ESP32 or ESP8266 use 3.3V. GND goes to GND. That is the whole circuit.

Example 1: I2C scanner (run this first)

Nine out of ten "my OLED doesn't work" messages end with the address being wrong. This sketch prints every device on the bus to the Serial Monitor at 115200 baud. A normal module answers at 0x3C.

#include <Wire.h>

void setup() {
  Serial.begin(115200);
  Wire.begin();
  delay(200);
  Serial.println("Scanning I2C...");
  byte found = 0;
  for (byte addr = 1; addr < 127; addr++) {
    Wire.beginTransmission(addr);
    if (Wire.endTransmission() == 0) {
      Serial.print("Device at 0x");
      if (addr < 16) Serial.print('0');
      Serial.println(addr, HEX);
      found++;
    }
  }
  if (!found) Serial.println("Nothing found. Check SDA, SCL and power.");
}

void loop() {}

If the scanner finds nothing, swap SDA and SCL and try again. If it finds 0x3D, use that number in every example below instead of 0x3C.

Example 2: minimal hello world (128x64)

The smallest sketch that proves the display works. Every later example builds on this skeleton.

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

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1          // most I2C modules share the Arduino reset
#define OLED_ADDR 0x3C

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

void setup() {
  Serial.begin(115200);
  if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) {
    Serial.println("SSD1306 not found");
    while (true) {}
  }
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println("Hello, OLED!");
  display.display();           // nothing appears until you call this
}

void loop() {}

The comment on display.display() is the lesson. Adafruit SSD1306 draws into a 1,024-byte buffer in RAM and only sends it over I2C when you call display().

Example 3: text sizes, inverted text and numbers

// Same includes and display object as Example 2
void setup() {
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();

  display.setTextSize(1);                    // 6x8 px per character, 21 per line
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println("Size 1: 21 chars");

  display.setTextSize(2);                    // 12x16 px, 10 per line
  display.println("Size 2");

  display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);  // inverted
  display.setTextSize(1);
  display.println(" INVERTED ");

  display.setTextColor(SSD1306_WHITE);
  display.setTextSize(3);                    // 18x24 px
  display.print(23.5, 1);                    // one decimal place
  display.display();
}

void loop() {}

Size 1 fits 21 characters by 8 lines on a 128x64 panel. Size 2 fits 10 by 4. Anything wider wraps to the next line, which is why long strings seem to "jump". Call display.setTextWrap(false) if you would rather they clip.

Example 4: lines, rectangles, circles and triangles

void setup() {
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.drawLine(0, 0, 127, 63, SSD1306_WHITE);
  display.drawRect(4, 4, 40, 24, SSD1306_WHITE);
  display.fillRect(50, 4, 24, 24, SSD1306_WHITE);
  display.drawRoundRect(80, 4, 44, 24, 6, SSD1306_WHITE);
  display.drawCircle(20, 46, 14, SSD1306_WHITE);
  display.fillCircle(56, 46, 10, SSD1306_WHITE);
  display.drawTriangle(80, 60, 100, 34, 120, 60, SSD1306_WHITE);
  display.display();
}

void loop() {}

Coordinates start at the top-left corner (0, 0) and end at (127, 63). For more shapes and a gauge built from them, see the drawCircle and drawRect guide.

Example 5: a live counter that updates without flicker

unsigned long last = 0;
int count = 0;

void setup() {
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.setTextColor(SSD1306_WHITE);
}

void loop() {
  if (millis() - last >= 250) {
    last = millis();
    display.clearDisplay();             // clear the buffer, not the glass
    display.setTextSize(1);
    display.setCursor(0, 0);
    display.print("Uptime (s): ");
    display.print(millis() / 1000);
    display.setTextSize(3);
    display.setCursor(0, 24);
    display.print(count++);
    display.display();                  // one transfer per frame
  }
}

Clear, draw everything, then call display() once. Calling it after every line is what causes visible flicker. Using millis() instead of delay() keeps the rest of your loop free for buttons or sensors.

Example 6: draw a bitmap from a byte array

This draws a 16x16 heart. Replace the array with your own logo: the free Image to Byte Array tool turns a PNG into exactly this format.

const unsigned char heart16[] PROGMEM = {
  0x00, 0x00, 0x1C, 0x38, 0x3E, 0x7C, 0x7F, 0xFE,
  0x7F, 0xFE, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8,
  0x0F, 0xF0, 0x07, 0xE0, 0x03, 0xC0, 0x01, 0x80,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

void setup() {
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.drawBitmap(56, 24, heart16, 16, 16, SSD1306_WHITE);
  display.display();
}

void loop() {}

Each row of 16 pixels is 2 bytes, so a 16x16 bitmap is 32 bytes. A full 128x64 picture is 1,024 bytes. Keep the PROGMEM keyword or an Uno will run out of RAM. The image to byte array guide explains the packing in detail.

Example 7: hardware scrolling text

void setup() {
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(10, 24);
  display.println("SCROLL!");
  display.display();
}

void loop() {
  display.startscrollright(0x00, 0x0F);   // pages 0 to 15 = whole screen
  delay(3000);
  display.stopscroll();
  delay(500);
  display.startscrollleft(0x00, 0x0F);
  delay(3000);
  display.stopscroll();
  delay(500);
}

The SSD1306 chip moves the picture itself, so the Arduino does no work while it scrolls. Always call stopscroll() before drawing something new, or the new frame gets corrupted. If you want a name that assembles itself instead of sliding, compare the two approaches in scrolling text vs name animation.

Example 8: the 128x32 I2C version (0.91 inch)

The thin 0.91 inch bar is the same controller with half the rows. This is the complete Arduino OLED 128x32 I2C example. The only real change is the height.

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

Adafruit_SSD1306 display(128, 32, &Wire, -1);   // height 32

void setup() {
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) while (true) {}
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.setTextSize(1);
  display.setCursor(0, 0);
  display.println("128x32 OLED");
  display.setTextSize(2);
  display.setCursor(0, 14);
  display.println("Temp 24C");
  display.display();
}

void loop() {}

With height 32 the buffer is 512 bytes, which leaves an Uno much more RAM. If you run the 128x64 sketch on this panel you will see a stretched picture with every second line missing.

Example 9: ESP32 or ESP8266 with custom I2C pins

// ESP32: any two free GPIOs can be I2C
#define SDA_PIN 21
#define SCL_PIN 22

void setup() {
  Wire.begin(SDA_PIN, SCL_PIN);            // ESP8266: Wire.begin(4, 5)
  Wire.setClock(400000);                   // 400 kHz fast mode
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 0);
  display.println("ESP32 + OLED");
  display.display();
}

void loop() {}

Call Wire.begin(sda, scl) before display.begin(). At 400 kHz a full 128x64 frame takes about 25 ms on the wire, so roughly 30 to 40 frames per second is the practical ceiling. The ESP32 OLED animation guide goes further.

Example 10: two-frame animation

const unsigned char eyeOpen[] PROGMEM  = { 0x3C, 0x42, 0x99, 0xBD, 0xBD, 0x99, 0x42, 0x3C };
const unsigned char eyeClosed[] PROGMEM = { 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00 };

void setup() {
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
}

void loop() {
  for (byte f = 0; f < 2; f++) {
    display.clearDisplay();
    const unsigned char* img = f ? eyeClosed : eyeOpen;
    display.drawBitmap(44, 28, img, 8, 8, SSD1306_WHITE);
    display.drawBitmap(76, 28, img, 8, 8, SSD1306_WHITE);
    display.display();
    delay(f ? 150 : 2000);
  }
}

That is the pattern every OLED animation uses: an array of frames, a loop, a delay per frame. For anything bigger than a blink, draw the frames in the OLED Animation Maker and export the full sketch, or build animated robot eyes with moods in the OLED Robot Eyes generator.

Common errors and what they mean

What you seeMost likely causeFix
"SSD1306 not found" in SerialWrong address or SDA/SCL swappedRun Example 1, then use the address it prints
Screen stays black, no errorMissing display.display()Call it after drawing
Random dots on startup onlyNormal power-on RAM contentCall clearDisplay() then display() in setup
Picture shifted, noise at right edgeModule is SH1106, not SSD1306See SH1106 vs SSD1306
Every second line missing128x64 code on a 128x32 panelUse Example 8
Uno resets or freezes after a whileOut of RAM (buffer + your arrays)Use PROGMEM, the F() macro, or a 128x32 panel

Test it before you upload

If you don't have a board on your desk, paste these sketches into Wokwi (I wrote a Wokwi SSD1306 walkthrough) or preview graphics in the online OLED simulator. Both show the exact 128x64 pixel grid.

Skip hand-typed arrays

Draw frames, import a GIF or MP4, and export a complete SSD1306 sketch for Uno, ESP32, ESP8266 or Pico. Free, in the browser.

Open OLED Animation Maker →

Popular searches: arduino oled 128x64 i2c example · arduino oled 128x32 i2c example · ssd1306 i2c example code · oled i2c arduino code · adafruit ssd1306 example · i2c scanner arduino · esp32 oled i2c pins

Frequently asked questions

What is the I2C address of a 128x64 SSD1306 OLED?

Almost always 0x3C. Some modules have a solder jumper on the back that switches it to 0x3D. The label on the board often says 0x78 or 0x7A, which are the same addresses shifted left by one bit. Run the I2C scanner sketch if you are not sure.

What changes between the 128x64 and 128x32 example?

Only the height. Set SCREEN_HEIGHT to 32 in the constructor. The address stays 0x3C and the wiring is the same. Anything drawn below row 31 is simply cut off.

Which pins are SDA and SCL on Uno, Nano, Mega, ESP32 and ESP8266?

Uno and Nano use A4 for SDA and A5 for SCL. Mega uses pins 20 (SDA) and 21 (SCL). ESP32 uses GPIO 21 (SDA) and GPIO 22 (SCL) by default. ESP8266 NodeMCU and Wemos D1 mini use D2 (GPIO4, SDA) and D1 (GPIO5, SCL).

Why does my OLED stay blank even though the code uploads?

The usual causes are a wrong address, SDA and SCL swapped, a missing display.display() call after drawing, or not enough RAM on an Uno for the 1 KB buffer. Run the I2C scanner first, then check that display.begin() returns true.

Does this example code work with an SH1106 1.3 inch OLED?

Not unchanged. A 1.3 inch module is often SH1106, which needs the Adafruit_SH110X library or a U8g2 SH1106 constructor. With the SSD1306 library it shows a shifted picture or noise on the right edge.

Can I put a custom image or animation on the OLED?

Yes. Convert the image to a PROGMEM byte array with the free Image to Byte Array tool, or draw and export a full animation with the OLED Animation Maker. Then draw it with drawBitmap as shown in example 6.

Related guides you might like

Free tools: OLED Animation Maker for your SSD1306 and SH1106 animations, Image to Byte Array for your logos and icons, OLED Robot Eyes for animated robot faces, the OLED Simulator to preview before you flash, and Assemble Studio for your name and logo animations. See all tools.