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.
| Board | SDA | SCL | Power the OLED from |
|---|---|---|---|
| Arduino Uno / Nano | A4 | A5 | 5V or 3.3V |
| Arduino Mega 2560 | 20 | 21 | 5V or 3.3V |
| Arduino Leonardo / Micro | 2 | 3 | 5V or 3.3V |
| ESP32 DevKit | GPIO 21 | GPIO 22 | 3.3V |
| ESP8266 NodeMCU / D1 mini | D2 (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 see | Most likely cause | Fix |
|---|---|---|
| "SSD1306 not found" in Serial | Wrong address or SDA/SCL swapped | Run Example 1, then use the address it prints |
| Screen stays black, no error | Missing display.display() | Call it after drawing |
| Random dots on startup only | Normal power-on RAM content | Call clearDisplay() then display() in setup |
| Picture shifted, noise at right edge | Module is SH1106, not SSD1306 | See SH1106 vs SSD1306 |
| Every second line missing | 128x64 code on a 128x32 panel | Use Example 8 |
| Uno resets or freezes after a while | Out 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
- Arduino OLED display tutorial (full guide)
- SH1107 and SSD1315 OLED guide
- Raspberry Pi Pico + SSD1306 in Arduino IDE
- U8g2 vs Adafruit SSD1306
- Arduino OLED blank screen fix
- SPI vs I2C OLED
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.