U8g2 tutorial for SSD1306 and SH1106 OLED: constructors, fonts, buffers and drawing
U8g2 is a monochrome display library for Arduino that drives the SSD1306 and SH1106 OLEDs (and hundreds of other
controllers) with one consistent API. To use it you install U8g2 from the Library Manager, pick the one constructor
line that matches your controller, size, bus and buffer mode, then draw between clearBuffer() and
sendBuffer(). This tutorial walks through each of those parts on a 0.96 inch SSD1306 and a 1.3 inch
SH1106: how to read a constructor name, when to use full buffer or page buffer, how the font names work, and how to
draw text, shapes and bitmaps without the usual surprises.
Quick answer. For a 0.96 inch I2C OLED use
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE);, and for a 1.3 inch one use
U8G2_SH1106_128X64_NONAME_F_HW_I2C with the same arguments. In setup() call
u8g2.begin(). In loop() call clearBuffer(), setFont(),
drawStr(x, y, "text"), then sendBuffer(). Remember that y is the text baseline, not the top.
Install U8g2 and wire the display
In the Arduino IDE open the Library Manager, search for U8g2 and install the library by oliver
(olikraus). It brings in both U8g2lib.h and the smaller U8x8 text-only API. You do not need Adafruit GFX
or any other dependency. The wiring is the same as for any I2C OLED:
| OLED pin | Arduino Uno / Nano | ESP32 DevKit | ESP8266 NodeMCU |
|---|---|---|---|
| GND | GND | GND | G |
| VCC | 5 V or 3.3 V | 3V3 | 3V |
| SCL | A5 | GPIO 22 | D1 (GPIO 5) |
| SDA | A4 | GPIO 21 | D2 (GPIO 4) |
Check the pin order printed on your module before you plug it in. Some boards put VCC first and some put GND first, and swapping them is the fastest way to kill a display. If you are unsure which controller you have, the SH1106 vs SSD1306 guide shows how to tell them apart. The short version: 0.96 inch modules are usually SSD1306 and 1.3 inch modules are usually SH1106, but sellers mix them up.
How to read a U8g2 constructor
The constructor is the part that confuses everyone at first, because it is one long name instead of a few arguments. Once you split it into pieces it is easy. Take this one:
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
| Part | Meaning | Other values you will see |
|---|---|---|
SSD1306 | Controller chip | SH1106, SSD1309, SH1107 |
128X64 | Resolution | 128X32, 72X40, 64X48 |
NONAME | Panel variant (a vendor or init sequence) | UNIVISION for 128x32, NONAME0 for SSD1309, VCOMH0 |
F | Buffer mode | 1 (one page), 2 (two pages) |
HW_I2C | Bus | SW_I2C, 4W_HW_SPI, 4W_SW_SPI |
U8G2_R0 | Rotation | U8G2_R1 (90°), U8G2_R2 (180°), U8G2_R3 (270°), U8G2_MIRROR |
The variant part cannot be guessed, so copy it from the U8g2 setup wiki rather than typing what seems logical. For a
128x32 SSD1306 the name is U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C, not NONAME. Use
HW_I2C whenever your board has hardware I2C on the pins you wired; SW_I2C bit-bangs any two
pins but is several times slower. The reset argument is U8X8_PIN_NONE for the usual 4-pin modules, which
have no reset pin broken out.
On an ESP32 the hardware I2C constructor uses the default pins 21 and 22. If you wired other pins, pass them after the
reset argument: u8g2(U8G2_R0, U8X8_PIN_NONE, /* clock=*/ 18, /* data=*/ 19).
Full buffer vs page buffer
The buffer letter decides how much RAM U8g2 keeps for the picture and how you write the drawing code. A 128x64 monochrome screen is 8192 pixels, which is 1024 bytes when packed 8 pixels per byte. The display memory is split into 8 horizontal "pages", each 8 pixels tall.
| Mode | RAM for 128x64 | Drawing pattern | Use it when |
|---|---|---|---|
| _F_ full buffer | 1024 bytes | clearBuffer() … draw … sendBuffer() | ESP32, ESP8266, RP2040, or an Uno with RAM to spare |
| _2_ two pages | 256 bytes | firstPage() / nextPage() loop | Uno projects that need some speed and some RAM |
| _1_ one page | 128 bytes | firstPage() / nextPage() loop | An Uno or Nano that also runs sensors, SD cards or long strings |
In full buffer mode you draw everything once into RAM and push it to the screen in one go, exactly like Adafruit
SSD1306. In page mode U8g2 runs your drawing code once for each page and only keeps that strip in RAM. With
_1_ on a 128x64 screen your drawing code runs 8 times per frame, with _2_ it runs 4 times.
// Page buffer pattern (constructor ends in _1_ or _2_)
u8g2.firstPage();
do {
u8g2.setFont(u8g2_font_ncenB08_tr);
u8g2.drawStr(0, 12, "Page mode");
u8g2.drawFrame(0, 20, 128, 20);
} while (u8g2.nextPage());
The rule that catches people in page mode: everything inside the do block must draw exactly the same
thing on every pass. Do not read a sensor, increment a counter or call random() inside the loop. Read the
values before firstPage(), store them in variables, and only draw inside. If you change a value halfway
through, the top of the screen shows the old value and the bottom shows the new one.
On an Uno the 1 KB full buffer is half of the 2 KB of SRAM before your own variables. If the IDE warns about low
memory, or the board resets randomly, switch _F_ to _1_ first. The
Uno OLED memory limits guide has the numbers for
frame data in flash.
U8g2 fonts: how to read the names
U8g2 ships with hundreds of fonts, and each one is compiled into flash only if you use it. The names follow a pattern:
u8g2_font_<name>_<purpose><charset>. The last two letters tell you what you get.
| Letter | Position | Meaning |
|---|---|---|
t | Purpose | Transparent, no background box (the usual choice) |
h | Purpose | All glyphs share a common height |
m | Purpose | Monospace: common height and width |
8 | Purpose | Every glyph fits an 8x8 box |
f | Charset | Up to 256 glyphs, including accented letters |
r | Charset | ASCII 32 to 127 only |
u | Charset | ASCII 32 to 95: uppercase, digits and punctuation |
n | Charset | Digits plus the few symbols needed for dates and times |
So u8g2_font_6x10_tf is a small 6x10 pixel font with the full 256 character set, and
u8g2_font_logisoso32_tn is a large 32 pixel tall digit font that only contains numbers. That
n matters: if you try to print letters with it, nothing appears, because the glyphs are not there.
It is also why it is so small in flash, which makes it ideal for a big clock or sensor reading.
A good starting set for a 128x64 screen: u8g2_font_5x7_tr for dense status lines,
u8g2_font_6x10_tf for normal text (six lines fit on the screen), u8g2_font_helvB10_tr for
headings, and u8g2_font_logisoso24_tn or u8g2_font_logisoso32_tn for one big number.
Prefer _tr over _tf when you only need English text; the reduced set saves flash on an Uno.
The baseline gotcha
By default the y value you pass to drawStr() is the baseline, the line the bottom of capital letters
sits on. drawStr(0, 0, "Hi") therefore draws the text above the screen and you see nothing. Either use a
y equal to the font height (for example drawStr(0, 10, ...) with a 10 pixel font), or call
u8g2.setFontPosTop() once so y means the top edge, which is how Adafruit GFX behaves.
Drawing text, shapes and bitmaps
These are the functions I use in almost every sketch:
drawStr(x, y, s)draws an ASCII string.drawUTF8()handles UTF-8 text such as the degree sign if the font has it.getStrWidth(s)returns the width in pixels, so(128 - u8g2.getStrWidth(s)) / 2centres text.u8g2.print()works too, because U8g2 inherits from Arduino'sPrint. Set the position withsetCursor(x, y)first. It is the easy way to print numbers.drawFrameanddrawBoxdraw an outlined and a filled rectangle;drawRFrameanddrawRBoxadd a corner radius.drawCircleanddrawDiscdraw an outlined and a filled circle;drawLineanddrawPixeldo what they say.setDrawColor(c)sets 1 to light pixels, 0 to clear them and 2 for XOR, which is handy for inverted text on a filled bar.drawXBMP(x, y, w, h, bitmap)draws a bitmap stored inPROGMEMin XBM format.setContrast(0..255)dims the panel, which also cuts current draw a little.
The bitmap format is where most migration bugs come from. U8g2's XBM functions expect each row packed least
significant bit first. Adafruit's drawBitmap() expects most significant bit first. Paste an
Adafruit array into drawXBMP() and every 8 pixel group comes out mirrored, so the image looks shredded.
Export the array for the right library instead of converting by hand. For a side-by-side look at the two
libraries, see U8g2 vs Adafruit SSD1306, and for the shapes
themselves, drawing OLED shapes.
Complete U8g2 example sketch
This sketch runs on an Uno, Nano, ESP32 or ESP8266 with a 128x64 SSD1306. It shows a centred title, an 8x8 heart bitmap, a live counter in a big digit font, and a progress bar with XOR text on top. For an SH1106, change only the constructor line.
#include <Arduino.h>
#include <U8g2lib.h>
#include <Wire.h>
// 0.96" SSD1306, full buffer, hardware I2C, no reset pin
U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
// 1.3" SH1106: use this line instead
// U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
// 8x8 heart in XBM format (LSB first in each byte)
static const unsigned char heart_bits[] PROGMEM = {
0x66, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0x00
};
unsigned long lastTick = 0;
uint16_t counter = 0;
void setup() {
// Only needed for a module at 0x3D. The address is shifted left by one.
// u8g2.setI2CAddress(0x3D * 2);
u8g2.begin();
u8g2.setContrast(160);
}
void loop() {
if (millis() - lastTick >= 100) {
lastTick = millis();
counter++;
}
u8g2.clearBuffer();
// Title, centred
u8g2.setFont(u8g2_font_6x10_tf);
const char *title = "U8g2 demo";
u8g2.drawStr((128 - u8g2.getStrWidth(title)) / 2, 10, title);
u8g2.drawXBMP(4, 3, 8, 8, heart_bits);
u8g2.drawHLine(0, 13, 128);
// Big number
u8g2.setFont(u8g2_font_logisoso24_tn);
u8g2.setCursor(4, 44);
u8g2.print(counter);
// Rounded frame on the right with a filled disc
u8g2.drawRFrame(92, 18, 32, 28, 4);
u8g2.drawDisc(108, 32, (counter % 10) + 2);
// Progress bar with XOR text so it stays readable
uint8_t fill = (counter % 101) * 124 / 100;
u8g2.drawFrame(0, 50, 128, 14);
u8g2.drawBox(2, 52, fill, 10);
u8g2.setFont(u8g2_font_5x7_tr);
u8g2.setFontMode(1);
u8g2.setDrawColor(2);
u8g2.drawStr(52, 60, "LOADING");
u8g2.setDrawColor(1);
u8g2.sendBuffer();
}
Two details worth noticing. The counter is updated with millis() rather than delay(), so you
can add buttons later without the screen freezing. And the XOR text needs setFontMode(1) (transparent)
or the glyph background is drawn as well, which turns the label into a solid block. Setting the draw color back to 1
at the end keeps the next frame's shapes normal.
To run the same sketch on an Uno with less RAM, change _F_ to _1_, move the counter update
above the drawing, and wrap everything from setFont to the last drawStr in the
firstPage() / nextPage() loop shown earlier, replacing clearBuffer() and
sendBuffer().
Animations and the OLED Animation Maker export
For frame-by-frame animation, full buffer mode is simpler: clear, draw one frame with drawXBMP(), send,
move to the next frame. A full 128x64 frame is 1024 bytes of flash, so plan the frame count before you draw. The
OLED Animation Maker can export for U8g2 directly: pick U8g2 in the
Library menu and the generated sketch uses the matching full buffer constructor for your display (including the
SH1106 and SSD1309 names), stores frames as XBM arrays in PROGMEM and plays them with
clearBuffer(), drawXBMP() and sendBuffer(). To turn a single logo into an
array, the Image to Byte Array converter does the conversion in the browser.
Troubleshooting U8g2 on an OLED
- Blank screen: run an I2C scanner first. If nothing is found, it is wiring or power. If the scanner finds 0x3D, add
u8g2.setI2CAddress(0x3D * 2)beforebegin(). U8g2 wants the 8-bit form, so passing0x3Don its own does not work. The blank OLED fix guide covers the rest. - Picture shifted two pixels, or a column of noise at the edge: the module is an SH1106 driven with an SSD1306 constructor. Switch to
U8G2_SH1106_128X64_NONAME_F_HW_I2C. - Text missing: the y position is 0 with the default baseline, or the font is an
norufont that does not contain the characters you print. - Only the top or bottom strip changes in page mode: something inside the
firstPage()loop changes between passes. Move the logic outside. - Sketch too big for the Uno: each font adds flash. Use
_trinstead of_tf, drop fonts you only use once, or use the U8x8 API for plain text. - Slow refresh on an ESP32: make sure you used
HW_I2C, notSW_I2C, and tryu8g2.setBusClock(400000)for 400 kHz I2C. - Bitmap looks scrambled: it is in Adafruit (MSB first) format. Re-export it in XBM format for U8g2.
When U8g2 is the right choice
Pick U8g2 when you have an SH1106 or another controller that the Adafruit SSD1306 library does not support, when an
Uno is short on RAM and page mode would rescue the project, or when you want a large choice of fonts, including big
digit fonts and icon fonts such as u8g2_font_open_iconic_weather_2x_t. If you are following an
Adafruit-based tutorial or already have Adafruit-format bitmaps, staying on Adafruit SSD1306 is easier. Both
libraries can live side by side in the same Arduino install, so it is fine to try each one. If you want to start
from the very beginning with the Adafruit library instead, read the
Arduino OLED display tutorial.
Frequently asked questions
Which U8g2 constructor should I use for a 0.96 inch OLED?
For the common 0.96 inch 128x64 I2C module use U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE). If the picture is shifted by two pixels or has a noisy column at one edge, it is probably an SH1106, so use U8G2_SH1106_128X64_NONAME_F_HW_I2C.
What is the difference between _F_, _1_ and _2_ in U8g2?
The letter sets the buffer size. _F_ keeps the full screen in RAM (1024 bytes for 128x64) and you draw between clearBuffer() and sendBuffer(). _1_ keeps one 8 pixel page (128 bytes) and _2_ keeps two pages (256 bytes); both use the firstPage() and nextPage() loop, which redraws the whole screen once per page.
How do I change the I2C address in U8g2?
Call u8g2.setI2CAddress(0x3D * 2) before u8g2.begin(). U8g2 expects the address shifted left by one bit, so 0x3C becomes 0x78 and 0x3D becomes 0x7A.
What do the letters at the end of U8g2 font names mean?
The last two letters are purpose and character set. Purpose t is transparent, h is common height, m is monospace and 8 fits an 8x8 box. Character set f has up to 256 glyphs, r has ASCII 32 to 127, u has only uppercase ASCII 32 to 95 and n has numbers and date and time characters.
Why is my U8g2 text drawn off the top of the screen?
By default the y coordinate in drawStr() is the baseline, the bottom of capital letters. Drawing at y = 0 puts the text above the screen. Use a y value equal to the font height, or call setFontPosTop() so y means the top of the text.
Related guides
- U8g2 vs Adafruit SSD1306: which library for animation
- SH1106 vs SSD1306: differences and how to tell
- Uno OLED animation memory limits
- Fix a blank SSD1306 OLED
- ESP32 OLED animation guide
Free tools: OLED Animation Maker for SSD1306 and SH1106 animations, Assemble Studio for name and logo animations, the Shadow Box Lamp Generator for 3D-printed shadow lamps, Text to STL for 3D-printed names and keychains, and 3MF to Web to show 3D prints on any website. New: the Image to Byte Array converter, the OLED Robot Eyes generator and the Lithophane Maker. See all tools.