Home ▸ Arduino ▸ Displays & Interfaces ▸ SSD1306 (0.96") Tutorial

SSD1306 Arduino OLED Tutorial: I2C Wiring, Text, Bitmaps & Animations

The SSD1306 is a 0.96-inch monochrome OLED display with a resolution of 128 by 64 pixels, and it connects to an Arduino with only two signal wires. It draws very little power, stays readable in the dark and is supported by well-maintained libraries. These qualities make it one of the most common displays for sensor readouts, small menus and simple graphics.

This tutorial shows how to wire the SSD1306 to an Arduino, find its I2C address and install the Adafruit libraries. After the setup, you will print text, integers and floating-point numbers, draw bitmaps and build frame-based animations. All examples were written for the Arduino Uno and also run on the Nano and the Mega.

The display uses the I2C bus, so the Arduino I2C tutorial is a useful background read. The Arduino UNO pinout guide shows where the I2C pins sit on the board. If you plan to show sensor data on the screen, the Arduino sensor tutorials cover temperature, motion and distance sensors that pair well with this display.

Browse the full Arduino Display Interfacing tutorial collection for more.

SSD1306 OLED Display Specifications

The SSD1306 is the name of the driver chip that controls a small OLED (organic light-emitting diode) panel. Each pixel on the panel produces its own light, so the display needs no backlight. This gives deep black levels, high contrast and a viewing angle of nearly 180 degrees. The driver supports I2C and SPI, and this tutorial uses the I2C version because it needs only two data lines.

ControllersTech logo shown as a bitmap on a 0.96-inch SSD1306 OLED connected to an Arduino
SpecificationDetail
Display size0.96 inch, measured diagonally
Resolution128 × 64 pixels
Driver ICSSD1306
InterfaceI2C (some modules also offer SPI)
Operating voltage3.3 V to 5 V
ColorMonochrome, available in white, blue or yellow
Viewing angleNearly 180 degrees

Why Use the SSD1306 with Arduino

The I2C version needs only four wires, so it leaves most of the Arduino pins free for other parts. The panel is bright, small and light, which suits battery-powered projects and small enclosures. The Adafruit SSD1306 and Adafruit GFX libraries handle the low-level work, so a few lines of code are enough to print text, show sensor values or draw an animation.


SSD1306 Arduino Wiring

The circuit needs an Arduino board with I2C support, an I2C version of the 0.96-inch SSD1306 display with the four pins GND, VCC, SDA and SCL, and a few jumper wires.

Connecting the SSD1306 to an Arduino Uno

The display uses SDA for data and SCL for the clock, and it also needs power and ground. On the Uno and the Nano, SDA is on pin A4 and SCL is on pin A5.

SSD1306 OLED to Arduino Uno I2C wiring diagram with VCC to 5V, GND to GND, SDA to A4 and SCL to A5
OLED pinArduino Uno and Nano pinDescription
VCC5V (or 3.3V on some modules)Power supply for the display
GNDGNDCommon ground
SDAA4I2C data line
SCLA5I2C clock line

On the Arduino Mega, SDA is on pin 20 and SCL is on pin 21. Newer boards such as the Leonardo have separate SDA and SCL pins that are labeled on the board. The Arduino UNO pinout guide shows the I2C pins in more detail. Because A4 and A5 are used for I2C, they are no longer available as analog inputs while the display is connected.


SSD1306 I2C Address

Most SSD1306 modules respond at the I2C address 0x3C, and some use 0x3D. Several modules also print a different value on the back of the board, so it is safer to scan the bus than to trust the label. The following sketch checks every address from 1 to 126 and prints the ones that answer.

#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(9600);
  Serial.println("Scanning for I2C devices...");
  for (byte i = 1; i < 127; i++) {
    Wire.beginTransmission(i);
    if (Wire.endTransmission() == 0) {
      Serial.print("I2C device found at address 0x");
      Serial.println(i, HEX);
    }
  }
}

void loop() {}

Upload the sketch, open the Serial Monitor at 9600 baud and note the address that appears. The examples in this tutorial use 0x3C.

Arduino Serial Monitor showing the I2C scanner finding the SSD1306 OLED at address 0x3C

The Arduino I2C tutorial explains how this scanner works and how to handle two devices with the same address.


SSD1306 Arduino Library Setup

Two libraries from Adafruit make the display easy to use. The Adafruit SSD1306 library communicates with the display, and the Adafruit GFX library provides the drawing functions for text, shapes and bitmaps.

Install the Adafruit SSD1306 and GFX Libraries

  1. Open the Arduino IDE and go to Sketch, then Include Library, then Manage Libraries.
  2. Type “SSD1306” in the search box and install Adafruit SSD1306.
  3. Search for “Adafruit GFX” and install Adafruit GFX Library. The IDE may offer to install it together with the SSD1306 library, and you can accept that offer.
Arduino IDE Library Manager with the Adafruit SSD1306 library selected for installation
Arduino IDE Library Manager with the Adafruit GFX Library selected for installation

Install the latest version of both libraries, because older versions can cause compile errors on newer boards.

Run the Built-in Example Sketch

The library includes a ready-made sketch that is a quick way to test the wiring.

  1. Go to File, then Examples, then Adafruit SSD1306, then ssd1306_128x64_i2c.
  2. Find the line #define SCREEN_ADDRESS 0x3C and change it to 0x3D if your scanner found that address.
  3. Select the board and the port, and click Upload.
Arduino IDE with the ssd1306_128x64_i2c example sketch from the Adafruit SSD1306 library open

The Adafruit logo appears first, followed by a series of shapes, scrolling text and small animations. This confirms that the Arduino and the display communicate correctly.

SSD1306 OLED showing the Adafruit logo after the built-in example sketch has started

The example sketch uses a screen buffer of 1024 bytes for the 128 by 64 display, which is half of the 2 KB of SRAM on the Uno. If a sketch resets or behaves strangely after you add more code, the lack of free memory is a likely cause.


SSD1306 Arduino Text and Numbers

The Adafruit GFX library prints to the display in the same way that Serial.print() prints to the Serial Monitor. The text is first written to a buffer in the memory of the Arduino, and a call to display.display() then sends the whole buffer to the screen.

Display Text on the SSD1306

This sketch initializes the display and prints a short message. The check on display.begin() stops the sketch with a message when the display does not answer, which makes wiring and address errors easy to spot.

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

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

void setup() {
  Serial.begin(9600);

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println("SSD1306 not found. Check the wiring and the address.");
    for (;;);
  }

  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.setCursor(0, 10);
  display.print("Hello, World!");
  display.display();
}

void loop() {
}

The functions in this sketch have the following roles.

  • display.clearDisplay() erases the buffer.
  • display.setTextSize(1) selects the smallest font size.
  • display.setTextColor(SSD1306_WHITE) draws white pixels on the black background.
  • display.setCursor(0, 10) sets the starting position of the text in pixels.
  • display.print() writes the text into the buffer.
  • display.display() sends the buffer to the screen.

The screen changes only when display.display() is called, which gives you control over when it refreshes and prevents flicker.

SSD1306 OLED showing the text Hello, World! printed with the Adafruit GFX library on Arduino

Change the Text Size and Position

The setTextSize() function takes a whole number, where 1 is the smallest size and 2 doubles the width and height of every character. The setCursor(x, y) function sets the horizontal and vertical start position in pixels.

display.clearDisplay();
display.setTextSize(2);     // Larger text
display.setCursor(20, 20);  // Move the text to a new position
display.print("Arduino");
display.display();
SSD1306 OLED showing the word Arduino in size 2 text positioned with setCursor

At size 1 the screen fits 21 characters per line and 8 lines, and at size 2 it fits 10 characters per line and 4 lines.


Text Wrapping and Inverted Text

The library wraps text to the next line when it reaches the right edge. You can turn this off with display.setTextWrap(false); when you want to control the layout yourself. To draw highlighted text, pass a second color for the background, which gives black text on a white block.

display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);

Inverted text is useful for headings, selected menu items and warnings.


Display Integer Values

Integers are printed with the same print() function. This sketch shows a counter that increases once per second.

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

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

int counter = 0;

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

void loop() {
  display.clearDisplay();
  display.setCursor(10, 20);
  display.print("Count: ");
  display.print(counter);
  display.display();
  counter++;
  delay(1000);
}

The call to display.clearDisplay() at the start of each pass removes the previous value. Without it, the new digits are drawn over the old ones and the text becomes unreadable.

Animated capture of a counter increasing on a 0.96-inch SSD1306 OLED connected to Arduino

Display Floating-Point Values

For a floating-point number, the second argument of print() sets the number of digits after the decimal point.

float temperature = 25.6789;

display.clearDisplay();
display.setTextSize(2);
display.setCursor(10, 20);
display.print("Temp: ");
display.print(temperature, 2);  // Prints 25.68
display.display();

Use print(temperature, 1) for one decimal place and print(temperature, 3) for three. Two decimals are enough for most sensor values, and fewer digits stay readable at a larger text size.

SSD1306 OLED showing a temperature value with two decimal places printed from a float on Arduino

This approach works with any sensor that returns a number. The DS18B20 temperature sensor tutorial already combines the sensor with an OLED, and the DHT11 and DHT22 tutorial, the BME280 tutorial and the MPU6050 tutorial provide values that you can print the same way. For analog sensors, the Arduino ADC tutorial shows how to convert a reading to a voltage first. A clock display is another popular use, and the DS3231 real-time clock tutorial provides the time.


SSD1306 Arduino Bitmaps

Bitmaps let you show logos, icons and other custom graphics. The display is monochrome, so every pixel is either on or off, and a bitmap is simply a list of bytes in which each bit represents one pixel.

How Bitmaps Work on the OLED

A bit with the value 1 turns a pixel on, and a bit with the value 0 turns it off. Each byte describes eight pixels in a row, so an 8 by 8 icon needs eight bytes.

const unsigned char myBitmap[] PROGMEM = {
  0x3C, 0x42, 0xA9, 0x85, 0xA9, 0x91, 0x42, 0x3C
};

The PROGMEM keyword keeps the data in flash memory instead of SRAM, which is important on the Uno because SRAM is limited. A full-screen 128 by 64 bitmap uses 1024 bytes of flash, and a 64 by 64 logo uses 512 bytes.


Convert an Image to a Bitmap Array

The free Image2CPP online tool converts an image to a C array.

  1. Prepare a black-and-white image, such as a logo.
  2. Upload the image to Image2CPP.
  3. Set the canvas size to the size you want on the display, and check the preview.
  4. Adjust the brightness threshold until the image looks clear.
  5. Select Arduino code as the output format and choose the horizontal, 1 bit per pixel drawing mode.
  6. Click Generate Code and copy the array.
Image2CPP settings for converting a logo to a monochrome C byte array for the SSD1306 OLED

Paste the array into a new header file, such as logo.h, which keeps the main sketch short. Then copy the file to the folder of your sketch.

Arduino IDE with a logo.h header file that stores the SSD1306 bitmap byte array
File explorer showing the logo.h header file copied into the Arduino sketch folder

Display a Bitmap with drawBitmap()

Include the header file and call drawBitmap() with the position, the array, the width, the height and the color.

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

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

void setup() {
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.clearDisplay();
  display.drawBitmap(32, 0, myBitmap, 64, 64, SSD1306_WHITE);
  display.display();
}

void loop() {
}

The image is drawn with its top-left corner at x = 32 and y = 0, and the values 64 and 64 are its width and height. Change the position to move the image. The array name and the size must match the values that Image2CPP generated.

ControllersTech logo drawn with drawBitmap on an SSD1306 OLED connected to an Arduino Uno

Combine Text and Bitmaps

A bitmap and text can share the screen, for example a Wi-Fi icon next to a status message.

display.drawBitmap(0, 0, wifiIcon, 16, 16, SSD1306_WHITE);
display.setCursor(20, 5);
display.setTextSize(1);
display.print("Connected");
display.display();

Small icons of 16 by 16 or 32 by 32 pixels draw quickly and use very little flash. Always call display.clearDisplay() before you draw a new image, and keep the bitmap data in PROGMEM.


SSD1306 Arduino Animations

An animation on the OLED is a series of frames that are drawn one after another, like a flipbook. When the frames change quickly enough, the eye sees continuous motion. Each frame follows the same four steps.

  1. Clear the display buffer with display.clearDisplay().
  2. Draw the graphics for the new frame.
  3. Send the frame to the screen with display.display().
  4. Wait for a short time before the next frame.
display.clearDisplay();
display.drawCircle(x, y, 3, SSD1306_WHITE);
display.display();
delay(30);

A delay between 20 ms and 50 ms usually gives smooth motion. A shorter delay makes the animation faster, and the time that the I2C transfer needs also limits the speed. Each full-screen update sends 1024 bytes, so the frame rate is limited by the speed of the bus. You can raise the bus speed to 400 kHz by calling Wire.setClock(400000); after display.begin(), which most SSD1306 modules support.

Bouncing Ball Animation

This sketch moves a small filled circle across the screen and reverses its direction when it reaches an edge.

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

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

int x = 10, y = 10;
int xSpeed = 2, ySpeed = 2;

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

void loop() {
  display.clearDisplay();

  // Draw the ball
  display.fillCircle(x, y, 3, SSD1306_WHITE);

  // Update the position
  x += xSpeed;
  y += ySpeed;

  // Bounce from the edges
  if (x <= 3 || x >= SCREEN_WIDTH - 3) xSpeed = -xSpeed;
  if (y <= 3 || y >= SCREEN_HEIGHT - 3) ySpeed = -ySpeed;

  display.display();
  delay(20);
}

The position changes by two pixels in every pass of the loop. When the ball touches an edge, the matching speed value changes its sign, and the ball moves back in the opposite direction.

Animated capture of a ball bouncing around the screen of an SSD1306 OLED connected to Arduino

The same logic can move several objects, a logo or a progress indicator.


Custom Multi-Frame Bitmap Animations

For a hand-drawn animation, create each frame as a black-and-white image and convert it to an array with the same Image2CPP steps that were described in the bitmap section. Save each frame in its own header file, for example frame1.h, frame2.h and frame3.h, and draw the frames in sequence.

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

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

#include "frame1.h"
#include "frame2.h"
#include "frame3.h"

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

void loop() {
  display.clearDisplay();
  display.drawBitmap(0, 0, frame1, 64, 32, SSD1306_WHITE);
  display.display();
  delay(100);

  display.clearDisplay();
  display.drawBitmap(0, 0, frame2, 64, 32, SSD1306_WHITE);
  display.display();
  delay(100);

  display.clearDisplay();
  display.drawBitmap(0, 0, frame3, 64, 32, SSD1306_WHITE);
  display.display();
  delay(100);
}

Each frame stays on the screen for 100 ms, and a longer delay gives a slower animation.


Store Frames in an Array

An animation with many frames is easier to manage when the frames are stored in an array and drawn in a loop. The following example plays a three-frame blinking cat eye. The frames were taken from a free animated image on Pixabay and converted to arrays with Image2CPP, which can also generate the array of frame pointers named cat_allArray in a single header file.

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

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

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

void loop() {
  for (int i = 0; i < 3; i++) {
    display.clearDisplay();
    display.drawBitmap(0, 0, cat_allArray[i], 128, 64, SSD1306_WHITE);
    display.display();
    delay(80);  // Frame delay for the blinking effect
  }
}
Animated capture of a three-frame cat eye blinking animation on an SSD1306 OLED with Arduino

Conclusion

This tutorial showed how to wire the SSD1306 OLED to an Arduino, find its I2C address, install the Adafruit libraries and display text, numbers, bitmaps and animations. The main points are the single call to display.display() that updates the screen, the 1024-byte buffer that limits the free memory on the Uno and the use of PROGMEM for image data. You can now combine the display with a sensor, a clock or a buzzer to build a small standalone device.


Download Project Here

Download the CubeMX project files and HAL source code — all tested on real hardware.

If you find this useful, you can support us with a Donation as well.

Browse More Arduino Display Tutorials

1 2


About the Author
Arun Rawat
Arun Rawat
Embedded Systems Engineer · Founder, ControllersTech

Arun is an embedded systems engineer with 10+ years of experience in STM32, ESP32, and AVR microcontrollers. He created ControllersTech to share practical tutorials on embedded software, HAL drivers, RTOS, and hardware design — grounded in real industrial automation experience.

Subscribe
Notify of

0 Comments
Newest
Oldest Most Voted
×

Don’t Miss Future STM32 Tutorials

Join thousands of developers getting free guides, code examples, and updates.