OLED Animation Maker OLED Maker
📚 U8g2 vs Adafruit

U8g2 vs Adafruit SSD1306 for OLED Animations — Which Library?

I get this question every workshop week: “Should I install Adafruit SSD1306 or U8g2 for animations?” Both draw white pixels on the same 0.96" panel. Both can blink a logo. The difference shows up when the Uno is …

By Ashish Aug 8, 2026 ~23 min Libraries, SSD1306, U8g2
📖

Clear tutorial

Written for makers — wiring, code, and common mistakes.

🔌

Hardware ready

SSD1306 / SH1106 friendly with pin tables where needed.

💻

Copy-paste code

Working sketches you can upload in Arduino IDE.

✨

Free maker tool

Preview & export more animations at oledanimationmaker.com.

U8g2 vs Adafruit SSD1306 for OLED Animations — Which Library?

I get this question every workshop week: “Should I install Adafruit SSD1306 or U8g2 for animations?” Both draw white pixels on the same 0.96" panel. Both can blink a logo. The difference shows up when the Uno is out of SRAM, when the seller shipped an SH1106 labeled as SSD1306, or when you want thirty tiny frames instead of three.

This is the comparison I wish I had printed on the lab wall. It is biased toward animation work — not toward drawing a single “Hello” string — and it includes side-by-side blink sketches, migration notes, and a recommendation matrix you can trust for Uno vs ESP32.

The short answer

  • Start with Adafruit SSD1306 + GFX if you are teaching beginners, using examples from Adafruit guides, or exporting from tools that target drawBitmap and GFX primitives.
  • Prefer U8g2 when you need SH1106 support in one constructor name, tighter RAM options, huge font selection, or page-buffer mode on a memory-starved AVR board.
  • On ESP32 with plenty of RAM, either is fine; pick the ecosystem your exporters and teammates already use.

I still keep both libraries installed. Switching mid-project is a few hours of renaming calls — not a rewrite of your art.

How they think about the screen

Adafruit SSD1306: full framebuffer by default

You allocate (or get) a 128×64 / 8 = 1024-byte RAM buffer. Every drawPixel, drawBitmap, and print edits that buffer. display.display() pushes the whole buffer over I2C/SPI. Mental model: paint a canvas, then photograph it to the glass.

That model is easy to reason about for animation: clear → draw frame N → display → wait → next frame. It also costs a fixed 1 KB of precious Uno SRAM whether your frame is busy or empty.

U8g2: constructor chooses the buffer strategy

U8g2 constructors encode the chip, resolution, bus, and buffer mode. Common ones for makers:

  • Full buffer (*f variants) — similar idea to Adafruit: large RAM, single send after drawing.
  • Page buffer / 1-page (*1) — only a strip of rows lives in RAM. You loop with firstPage / nextPage and redraw the scene for each page.
  • 2-page (*2) — middle ground.
// examples — names vary by board/bus; check U8g2 examples
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
// full buffer (~1 KB+ housekeeping)

U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
// page buffer — much less RAM, redraw per page

Page mode is the reason U8g2 fans love AVR. The trade-off: animation code that builds a complex scene must be callable repeatedly inside the page loop. Casual “draw once then display” does not map 1:1.

Drawing bitmaps: drawBitmap vs XBM helpers

Adafruit path most animation tools emit:

display.drawBitmap(0, 0, frame0, 128, 64, SSD1306_WHITE);
display.display();

Bytes are usually horizontal MSB packing matching Adafruit / image2cpp style. U8g2 often wants XBM bit order (vertical bits in classic XBM) or you use drawXBMP / drawBitmap carefully with the right format flag. Practical rule from support emails: do not paste an Adafruit array into U8g2 and assume the image is upright. Re-export for the target library, or convert packing once with a known-good script.

If you already generate frames in OLED Animation Maker, pick the Adafruit or U8g2 export explicitly so bit order matches. That single choice prevents the classic “my logo is shredded spaghetti” afternoon.

Speed: what actually limits you

For 128×64 over hardware I2C, the bus and the full-buffer transfer dominate — not your C++ circle math. Adafruit full buffer and U8g2 full buffer feel similar on Uno when both send ~1 KB each frame. Differences show up when:

  • You redraw a huge scene inside every U8g2 page (CPU bound on AVR).
  • You call Adafruit display() every millisecond while also doing Serial prints.
  • You run SPI OLEDs — both libraries get faster transfers; then CPU drawing cost matters more.

In class I target 15–25 FPS for simple blinks. Above that, human eyes rarely thank you on a tiny monochrome panel, and USB power noise plus long jumpers create mystery flicker people blame on “the wrong library.”

Flash and RAM: Uno vs ESP32

ConstraintAdafruit SSD1306 (typical)U8g2 (typical)
Uno RAM for frame buffer~1024 B full buffer (fixed)~1024 B full, or much less in page mode
Uno flash libraries + fontsGFX + driver: moderateCan grow fast if you pull large fonts
ESP32 RAMComfortableComfortable; still avoid shipping unused fonts
Two 128×64 panels on UnoOften too tightPage mode can save the project
PROGMEM animation framesSame math for both — frames still cost 1 KB each full screenSame

Remember: choosing U8g2 does not shrink your PROGMEM art. A 12-frame 128×64 loop is still ~12 KB of flash either way. Library choice mainly changes the live buffer and the driver feature set. For frame-count math see Uno OLED memory limits.

SH1106 and odd modules

Vendor modules mislabeled as SSD1306 but shipping SH1106 are everywhere. Symptom: image looks shifted by about two pixels or wraps weirdly. U8g2’s strength here is a clear constructor for SH1106 so the driver maps columns correctly. Adafruit users often move to Adafruit_SH110X or hack an X offset.

If a kit is mixed (some SSD1306, some SH1106), U8g2 constructors per board avoid shipping “magic +2” firmware variants. For the deeper hardware story, read SH1106 vs SSD1306.

Learning curve and community

  • Adafruit: Excellent beginner docs, CircuitPython cousins, GFX tutorials everywhere. Function names map cleanly to what teachers already show on projectors.
  • U8g2: Steeper first hour (constructors look loud), then very powerful. Font list is unmatched. Page loop surprises people coming from Adafruit.

For a first week Arduino course, I stay on Adafruit so students are not debugging constructors while learning I2C. For a product firmware pass on AVR, I often migrate the UI to U8g2 page mode to claw back RAM for sensors.

Side-by-side: simple 2-frame blink

Same idea — two PROGMEM bitmaps named eyeOpen and eyeClosed (replace with your exported arrays). Timing via millis(), not delay().

Adafruit SSD1306 version

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

Adafruit_SSD1306 display(128, 64, &Wire, -1);

// placeholder — paste real PROGMEM arrays from your exporter
extern const unsigned char eyeOpen[];
extern const unsigned char eyeClosed[];

unsigned long lastSwap = 0;
bool lidClosed = false;

void setup() {
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) for (;;);
  display.clearDisplay();
  display.display();
}

void loop() {
  unsigned long now = millis();
  if (now - lastSwap > (lidClosed ? 120UL : 900UL)) {
    lastSwap = now;
    lidClosed = !lidClosed;
    display.clearDisplay();
    display.drawBitmap(0, 0, lidClosed ? eyeClosed : eyeOpen, 128, 64, SSD1306_WHITE);
    display.display();
  }
}

U8g2 full-buffer version

#include <U8g2lib.h>
#include <Wire.h>

U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

extern const unsigned char eyeOpen[];
extern const unsigned char eyeClosed[];

unsigned long lastSwap = 0;
bool lidClosed = false;

void setup() {
  u8g2.begin();
}

void loop() {
  unsigned long now = millis();
  if (now - lastSwap > (lidClosed ? 120UL : 900UL)) {
    lastSwap = now;
    lidClosed = !lidClosed;
    u8g2.clearBuffer();
    // use the draw call that matches your export bit order
    u8g2.drawXBMP(0, 0, 128, 64, lidClosed ? eyeClosed : eyeOpen);
    u8g2.sendBuffer();
  }
}

U8g2 page-buffer version (RAM saver)

#include <U8g2lib.h>
#include <Wire.h>

U8G2_SSD1306_128X64_NONAME_1_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);

extern const unsigned char eyeOpen[];
extern const unsigned char eyeClosed[];

unsigned long lastSwap = 0;
bool lidClosed = false;

void drawFrame() {
  const unsigned char* bmp = lidClosed ? eyeClosed : eyeOpen;
  u8g2.firstPage();
  do {
    u8g2.drawXBMP(0, 0, 128, 64, bmp);
  } while (u8g2.nextPage());
}

void setup() {
  u8g2.begin();
  drawFrame();
}

void loop() {
  unsigned long now = millis();
  if (now - lastSwap > (lidClosed ? 120UL : 900UL)) {
    lastSwap = now;
    lidClosed = !lidClosed;
    drawFrame();
  }
}

Notice the page-mode sketch redraws the bitmap inside the do…while. If your “frame” is actually fifty shape calls (robot eyes from vectors), wrap them in a function and call that function inside the page loop — that is the main architectural shift coming from Adafruit.

Parts wiring (same for both)

PartArduino UnoESP32 (common)
OLED VCC5V (typical)3.3V
OLED GNDGNDGND
SDAA421
SCLA522

Library choice will not fix swapped SDA/SCL. Run an I2C scanner once before blaming drivers.

Migration tips (Adafruit → U8g2)

  1. Pick the constructor for your controller (SSD1306 vs SH1106) and bus (HW I2C).
  2. Replace clearDisplay → clearBuffer (full) or rely on page loop clears.
  3. Replace display.display() → sendBuffer() or nextPage completion.
  4. Map GFX calls: drawLine/drawCircle/drawFrame names are similar but not identical — keep a cheat sheet open for one afternoon.
  5. Re-export bitmaps; verify with a single static logo before porting the animation scheduler.
  6. Keep your millis() state machine. Only the paint layer should change.

Migration the other way (U8g2 → Adafruit) is common when a teammate’s exporter only emits Adafruit code. Budget an hour to rename sends and confirm SH1106 units still look aligned.

When I use each (workshop rules of thumb)

  • Hour-of-code robot eyes with circles: Adafruit — matches the shapes tutorial and most paste-ready sketches.
  • Avatar blink with 8 cropped bitmaps on Uno + ultrasonic + servo: U8g2 page mode — reclaim RAM.
  • ESP32 dashboard with Wi-Fi: Adafruit is fine; U8g2 if you need fancy fonts / SH1106 kits.
  • Unknown ebay OLED in a mixed bin: try U8g2 SH1106 constructor before declaring the panel dead.
  • Sharing code with MicroPython kids later: concepts transfer either way; Adafruit’s buffer model is closer to framebuf.

Recommendation matrix

Project situationPickWhy
Beginner + Adafruit docsAdafruitLowest friction, great examples
Uno RAM almost fullU8g2 pageSmall live buffer
Confirmed SH1106 hardwareU8g2 (or SH110X)First-class constructor
Tool exports Adafruit arraysAdafruitBit order already matched
Need many built-in fontsU8g2Font ecosystem
Dual OLED animation on UnoU8g2 page / or one panelTwo Adafruit buffers hurt
ESP32 + lots of RAMEitherTeam preference wins
Vector robot eyes onlyAdafruit or U8g2 fullPage mode forces redraw boilerplate

Common mistakes I see

  • Installing U8g2 but still calling Adafruit APIs from a copied blog sketch.
  • Using page mode while drawing only outside the page loop (blank or partial updates).
  • Assuming U8g2 “is always faster” — full buffer transfers are still full buffer transfers.
  • Linking every font header “just in case” and blowing flash on Uno.
  • Never calling display() / sendBuffer() after drawing (blank forever — library does not matter).

Fonts, HUDs, and mixed UI + animation

Pure bitmap animations barely care which library you pick. Mixed UIs do. If your robot shows a status line under the eyes, Adafruit’s default font is fine and tiny in flash. If you want a readable 12-pixel UI font plus icons, U8g2’s catalog saves you from shipping homemade fonts — but only include the one or two faces you call, or the linker still has to carry their tables on many setups.

A pattern that works on both: animate eyes in the upper band, reserve a 12–14 pixel strip at the bottom for text, and redraw text only when values change. That dirty-flag approach matters more than micro- optimizing the driver. Students who clear and reprint a long String every frame “because the library is slow” usually invent their own performance problem.

Partial updates and dirty regions (expectations check)

Makers sometimes ask for “only send the pupil pixels.” Neither library makes true region patches trivial on classic SSD1306 I2C the way a modern GPU would. You usually rebuild the buffer (or pages) and send a full or page-sized chunk. If you need lower bus traffic, reduce resolution of the art (crop sprites), drop FPS, or move to SPI. Do not expect a library rename to invent a free partial-DMA path on Uno hardware.

Where U8g2 page mode helps is RAM, not magical bandwidth. You still pay CPU to recreate the scene for each page. Complex vector robot faces can feel softer in page mode on 16 MHz AVR; full buffer may look snappier if you still have the SRAM — measure with a wall-clock blink period rather than forum lore.

Team and toolchain considerations

If half your GitHub issues paste Adafruit snippets, staying on Adafruit reduces support load even when U8g2 is “better” on paper. If you already standardized on PlatformIO library specs for U8g2 across ESP boards, do not yank that for one blog sketch. Consistency beats purity. Export pipelines (maker tools, CI that checks sketch size) should name the target library explicitly so PRs do not mix bit orders.

FAQ

Can I use both libraries in one sketch?

Technically yes, practically a mess on Uno (two drivers, two buffers). Pick one per firmware image.

Which library does OLED Animation Maker target?

The tool can export for Adafruit SSD1306 and U8g2 — choose the matching option so bitmap packing lines up with your sketch.

Is Adafruit_SH110X better than U8g2 for SH1106?

Both work. If the rest of your project is already Adafruit GFX, SH110X keeps API familiarity. If you want one library for many panel types, U8g2 is broader.

Do animations look smoother on U8g2?

Smoothness comes from frame timing and how much you transfer each update — not from the brand name on the include. Same art, same bus, similar FPS for full buffers.

What about SPI OLEDs?

Both libraries support SPI variants. Wiring changes (CS/DC/RES); the animation scheduler stays the same. SPI often frees you from I2C bandwidth complaints.

Why is page mode slower on my robot face even though it uses less RAM?

A *1 constructor redraws the scene once per page. Eight pages means eight passes over the same circles, then eight smaller I2C bursts. Full-buffer Adafruit draws once and sends 1024 bytes once. Page mode saves SRAM; it does not reduce CPU on a vector face.

I pasted an image2cpp array into drawXBMP and got snow

Adafruit drawBitmap and U8g2 drawXBMP do not share bit order. Re-export the frame for the call you are making. A length mismatch (a 1024-byte full frame drawn as 32×32) produces the same snow even when the library is correct.

Does Wire.setClock(400000) apply to both libraries?

Yes, if both are on hardware I2C. Call it after the display begin. It shortens the 1024-byte transfer; it does not change how many times page mode repaints the scene. Long jumper wires may need to stay at the default ~100 kHz.

How long one frame spends on the wire

“Which library is faster?” is usually a question about one 128×64 update at address 0x3C. The payload is the same 1024 bytes either way when you are in full-buffer mode. Arduino Wire comes up near 100 kHz. Moving 1024 bytes with address and ACK overhead is on the order of 90–100 ms of bus time. At 400 kHz that falls toward roughly 25 ms. Those figures are bit-time arithmetic, not a certificate from one module, and library overhead adds a little on top. They are enough to stop a debate about brand names.

Update styleWhat the CPU doesWhat the bus doesWhen it wins
Adafruit full buffer, display()Draw the scene once into 1024 RAM bytesOne transfer of that bufferVector eyes, single sprite, ESP32
U8g2 full buffer (*F), sendBuffer()Same shape: draw onceSame ~1024-byte transferWhen you want U8g2 fonts and can spare the RAM
U8g2 1-page (*1), firstPage / nextPageRedraw the whole scene about 8 timesEight shorter bursts that still cover the panelUno SRAM is the thing that is about to run out

Time the paint, not the legend on the box. On either library, record micros() immediately before the send and again when it returns. Do this with a static frame so drawing cost stays out of the number. On a default 100 kHz bus you should see a full-buffer send in the neighborhood of 100 ms. If Adafruit and U8g2 full-buffer times match and page mode is several times the CPU, the libraries are behaving. A page-mode robot face that feels soft on a 16 MHz Uno is that redraw loop, which the partial-update section already warned about; the stopwatch tells you whether switching to *F is worth the SRAM.

Second case: the array that worked last week

A student exports a blink from a tool as an Adafruit bitmap, then drops the same braces into a U8g2 sketch that calls drawXBMP. Hello World still prints, because text never touches that array. The eyes become a diagonal hash. The fix is to export again with the U8g2 option, not to swap constructors. Check three numbers before you touch timing: the array length is width * height / 8 (1024 for 128×64, 128 for a 32×32 crop), the width and height arguments match that art, and the pointer is read from PROGMEM the way that library’s example does. Mixing a RAM pointer with a PROGMEM array on AVR draws whatever happens to sit at the low address, which looks like a library bug and is a cast bug.

Wiring does not change between the two tests: Uno SDA on A4, SCL on A5; ESP32 SDA on GPIO21, SCL on GPIO22; ESP8266 SDA on D2, SCL on D1. If both sketches miss the panel, stop comparing buffers and scan the address. If only the ported sketch misses, the constructor names a different controller or a software-I2C pin pair. Hardware I2C constructors plus an explicit Wire.setClock after begin keep the timing table above honest. Leave the clock at 100 kHz until the static logo is clean, then raise it and look for sparkle on the longest jumper — that sparkle is electrical, and it hits both libraries on the same wires.

Related

Export frames for the library you chose

Draw or import animation frames, then download Adafruit or U8g2-ready code without hand-packing bits.

Open oledanimationmaker.com →