SSD1306 OLED Bitmap Tutorial: Display Images on 128x64
The SSD1306 0.96-inch I2C OLED is the most common display in the maker world — cheap, crisp, and supported by every microcontroller. This tutorial walks you from a freshly-wired panel to a custom logo on screen, using the Adafruit_SSD1306 library and image2cpp.
Hardware: the four wires
Almost every SSD1306 module sold today is the I2C variant with a fixed address (0x3C or rarely 0x3D):
| OLED Pin | Arduino Uno | ESP32 | RP2040 Pico |
|---|---|---|---|
| VCC | 5V (or 3.3V) | 3V3 | 3V3 (pin 36) |
| GND | GND | GND | GND |
| SCL | A5 | GPIO22 | GP5 |
| SDA | A4 | GPIO21 | GP4 |
If you bought the SPI version (7 pins instead of 4), the wiring is different and you must use the SPI constructor of Adafruit_SSD1306.
Software: install the libraries
In the Arduino IDE Library Manager install both:
- Adafruit SSD1306 (by Adafruit)
- Adafruit GFX Library (auto-installed as a dependency)
For PlatformIO, add to platformio.ini:
lib_deps =
adafruit/Adafruit SSD1306@^2.5.13
adafruit/Adafruit GFX Library@^1.11.11
The "Hello, OLED" sketch
Verify your wiring works before going further:
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
Adafruit_SSD1306 display(128, 64, &Wire, -1);
void setup() {
Serial.begin(115200);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 init failed"));
for (;;);
}
display.clearDisplay();
display.setTextSize(2);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 16);
display.println(F("Hello"));
display.println(F("OLED"));
display.display();
}
void loop() {}
If you see "Hello / OLED" the panel is alive. If not, the most common bugs are: wrong I2C address (try 0x3D), reversed SDA/SCL, or 5V on a 3.3V-only module.
Convert your image with image2cpp
Now the interesting part — replacing text with a custom bitmap:
- Open pixel.hjlabs.in/converter
- Drop in a PNG (we recommend a 128x64 black/white PNG with thick lines)
- Set Canvas size: 128 x 64
- Set Background: white, Brightness threshold: 128 (or enable Floyd–Steinberg dithering)
- Output format: "Arduino code, Horizontal, 1 bit per pixel"
- Click "Generate code", copy the array
You'll get something like a const unsigned char myBitmap [] PROGMEM declaration with 1024 bytes (128x64/8).
Display the bitmap
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
Adafruit_SSD1306 display(128, 64, &Wire, -1);
// Paste your image2cpp output here:
const unsigned char myLogo [] PROGMEM = {
// 1024 bytes
};
void setup() {
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.drawBitmap(0, 0, myLogo, 128, 64, SSD1306_WHITE);
display.display();
}
void loop() {}
Drawing only part of the screen
Sometimes you want a 32x32 icon at a specific position, with text alongside:
display.clearDisplay();
display.drawBitmap(0, 0, iconWifi, 32, 32, SSD1306_WHITE); // 32x32 at top-left
display.setCursor(40, 10);
display.setTextSize(2);
display.print(F("Online"));
display.display();
The signature is drawBitmap(x, y, bitmap, w, h, color) — w and h are pixel dimensions, not bytes.
Inverted bitmaps and XOR drawing
To invert a bitmap (white becomes black, useful for "selected" UI states), pass SSD1306_BLACK as color:
display.drawBitmap(0, 0, myLogo, 128, 64, SSD1306_BLACK); // inverted
For XOR drawing (toggle pixels) the Adafruit library exposes display.drawBitmap(x, y, bitmap, w, h, fg, bg) — the 7-argument form forces both colors instead of leaving "off" pixels untouched.
Splash screen on boot
Show a logo for 2 seconds before the main UI takes over:
void setup() {
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.drawBitmap(0, 0, splashLogo, 128, 64, SSD1306_WHITE);
display.display();
delay(2000);
// ... continue with main UI
}
Memory footprint
A 128x64 mono bitmap is 1024 bytes in flash. Add the Adafruit_SSD1306 buffer (1024 bytes in SRAM) and the GFX library code (~5KB). Total: ~7KB on a 32KB Uno — comfortable. On an ATtiny85 (8KB flash, 512B SRAM), Adafruit_SSD1306 won't fit; use U8g2 in page-buffer mode instead.
U8g2 alternative
If you need lower memory, use U8g2's page mode — it draws the screen in 8x128-pixel slices, using only 128 bytes of SRAM at a time. The image2cpp output format for U8g2 is XBM (Vertical, 1 bit per pixel, LSB). We covered the differences in drawBitmap vs drawXBitmap.
Common pitfalls
- Bitmap is shifted by 1 pixel. Width is not divisible by 8. Bytes-per-row must be ceil(w/8); image2cpp handles this, hand-edited arrays often don't.
- Bitmap shows but text doesn't. You called
display.display()right afterdrawBitmapbut the text was added after. Always calldisplay.display()last. - Tearing / flicker. You're calling
display.display()too often inside a tight loop. Limit to ~30Hz with amillis()-based gate. - Display goes blank after a few seconds. Cheap clones with bad capacitors. Check the panel logs and consider a different vendor.
Bottom line
SSD1306 + Adafruit_GFX + image2cpp is the canonical Arduino OLED bitmap stack. Wire 4 pins, install 2 libraries, paste a 1024-byte array, call drawBitmap, done. Convert your first image now, or read our deeper Arduino OLED tutorial for advanced techniques.
If your project deserves a slick GitHub README, the favicon and OG image generator at og.hjlabs.in is the fastest way to ship social previews.
Try image2cpp now — free, in browser
Drop in a PNG, JPG or BMP. Get a paste-ready C++ array for Arduino, ESP32 or RP2040. 100% client-side. Learn more about image2cpp or jump straight to the tool.
Open the converter →