Arduino: 128×64 OLED display (SSD1306)
The Serial Monitor is great while the Arduino is connected to a computer. But a thermometer, counter or game should work without a computer – that is what a small OLED display is for. It has 128 × 64 dots (pixels), is easy to read even in the dark, and connects to the Arduino with only four wires over I2C.
What an OLED display is
- The most common one is a 0.96 inch display with the SSD1306 chip. It is 128 dots wide and 64 dots high.
- Each dot gives its own light (like a tiny LED), so the display has no backlight and uses little current.
- There are single-color (white, blue) and two-color displays (top 16 rows yellow, the rest blue) – they are programmed the same way.
- It communicates over I2C: two wires (SDA for data and SCL for the clock), and each device has its own address. This display's address is usually
0x3C.
Wiring
| Display pin | Arduino Uno | ESP32 |
|---|---|---|
| VCC | 5V (or 3.3V) | 3V3 |
| GND | GND | GND |
| SDA | A4 | GPIO21 |
| SCL | A5 | GPIO22 |
On some modules the pin order is GND, VCC, SCL, SDA – always read the labels on the board before connecting.
In the Arduino IDE install the Adafruit SSD1306 and Adafruit GFX Library libraries (Sketch → Include Library → Manage Libraries). When the IDE asks to install the required libraries too, choose "Install all".
Check: I2C scanner
Before the first program, check whether the Arduino "sees" the display. This short program prints the addresses of all devices on the I2C bus:
// I2C scanner – prints the addresses of all I2C devices (works on Arduino and ESP32)
#include <Wire.h>
void setup() {
Serial.begin(9600);
Wire.begin(); // on the ESP32 you can write Wire.begin(21, 22);
Serial.println("Looking for I2C devices...");
int found = 0;
for (int address = 1; address < 127; address++) {
Wire.beginTransmission(address);
if (Wire.endTransmission() == 0) { // 0 = a device answered
Serial.print("Device at address 0x");
Serial.println(address, HEX);
found++;
}
}
if (found == 0) {
Serial.println("No devices - check the SDA, SCL, VCC and GND wires.");
}
}
void loop() {
}
If 0x3C (or 0x3D) is printed, everything is connected correctly. If there are no devices, check the SDA and SCL wires – they are most often swapped.
Program 1: first text
// Arduino – 128x64 OLED display (SSD1306, I2C): first text
// Libraries: "Adafruit SSD1306" and "Adafruit GFX Library" (Library Manager)
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
const int WIDTH = 128;
const int HEIGHT = 64;
// -1: the display has no separate RESET pin
Adafruit_SSD1306 display(WIDTH, HEIGHT, &Wire, -1);
void setup() {
Serial.begin(9600);
// 0x3C is the most common I2C address of these displays (sometimes 0x3D)
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("Display not found!");
while (true) {
} // stop the program
}
display.clearDisplay(); // clear the image in memory
display.setTextColor(SSD1306_WHITE);
display.setTextSize(1); // small text: 21 characters per line
display.setCursor(0, 0); // top left corner
display.println("Hello!");
display.setTextSize(2); // double-size text
display.setCursor(0, 20);
display.println("Arduino");
display.display(); // only now is the image sent to the display
}
void loop() {
}
How drawing on the display works
- Point (0, 0) is in the top left corner. x grows to the right (up to 127) and y downwards (up to 63).
- All commands draw into the Arduino's memory. The image is sent to the display only with
display()– if you forget it, the screen stays empty. clearDisplay()clears the image in memory; we call it before drawing new content.
| setTextSize | Letter height | Characters per line | Lines on screen |
|---|---|---|---|
| 1 | 8 dots | 21 | 8 |
| 2 | 16 dots | 10 | 4 |
| 3 | 24 dots | 7 | 2 |
Program 2: counter and shapes
// Arduino – OLED: time since power-on and a filling bar
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
Adafruit_SSD1306 display(128, 64, &Wire, -1);
void setup() {
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.setTextColor(SSD1306_WHITE);
}
void loop() {
unsigned long seconds = millis() / 1000;
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.print("Time since power-on:");
display.setTextSize(3);
display.setCursor(0, 16);
display.print(seconds);
display.print(" s");
int length = map(seconds % 10, 0, 9, 0, 124); // the bar fills every 10 s
display.drawRect(0, 52, 128, 12, SSD1306_WHITE); // bar outline
display.fillRect(2, 54, length, 8, SSD1306_WHITE);
display.display();
delay(200);
}
| Command | Draws |
|---|---|
drawLine(x1, y1, x2, y2, color) | a line |
drawRect(x, y, width, height, color) | a rectangle outline |
fillRect(x, y, width, height, color) | a filled rectangle |
drawCircle(x, y, r, color) | a circle |
fillCircle(x, y, r, color) | a filled circle |
drawPixel(x, y, color) | one dot |
The color is SSD1306_WHITE (the dot is lit) or SSD1306_BLACK (the dot is dark).
Program 3: thermometer with a DHT11
We combine the display with the sensor from the lesson Temperature sensor: the DHT11 DATA pin is on pin 3, and the display is still on A4 and A5.
// Arduino – OLED thermometer: temperature and humidity from a DHT11 (DATA on pin 3)
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <DHT.h>
#define DHTPIN 3
#define DHTTYPE DHT11
DHT dht(DHTPIN, DHTTYPE);
Adafruit_SSD1306 display(128, 64, &Wire, -1);
void setup() {
dht.begin();
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.setTextColor(SSD1306_WHITE);
}
void loop() {
float t = dht.readTemperature();
float h = dht.readHumidity();
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.print("Room thermometer");
display.drawLine(0, 10, 127, 10, SSD1306_WHITE); // line under the title
if (isnan(t) || isnan(h)) {
display.setCursor(0, 28);
display.print("Sensor error!");
} else {
display.setTextSize(2);
display.setCursor(0, 18);
display.print(t, 1);
display.print(" C");
display.setCursor(0, 42);
display.print(h, 0);
display.print(" %");
}
display.display();
delay(2000); // read the DHT11 every 2 seconds
}
This is the first real device that works without a computer: power the Arduino from a phone charger or a 9 V battery and put it anywhere in the room.
The OLED display on the ESP32
The same display and the same libraries also work on the ESP32. There are only two differences: the display is powered from 3V3, and the I2C connection is started with Wire.begin(21, 22).
// ESP32 – the same OLED display; only the I2C start changes
// (SDA = GPIO21, SCL = GPIO22)
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
Adafruit_SSD1306 display(128, 64, &Wire, -1);
void setup() {
Serial.begin(115200);
Wire.begin(21, 22); // SDA, SCL – default pins on the ESP32
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println("Display not found!");
while (true) {
delay(100);
}
}
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
display.setTextSize(2);
display.setCursor(0, 0);
display.println("ESP32");
display.setTextSize(1);
display.println("OLED runs on 3.3 V");
display.display();
}
void loop() {
}
We will also use the display in the final project smart classroom.
Common problems
- The screen is completely black: check the address with the scanner (
0x3Cor0x3D) and whether you calleddisplay(). - The program stops at "Display not found": SDA and SCL are swapped or the display has no power.
- The Arduino Uno behaves strangely when you add more code: the screen image takes 1024 bytes, and the Uno has only 2048 bytes of RAM. Use shorter texts and fewer variables (or an ESP32, which has much more memory).
- Text overlaps:
clearDisplay()is missing before drawing again.
Practice tasks
Try to solve each task yourself first, then open the solution.
- Show on the display how many times the button on pin 2 has been pressed, in large digits (text size 4).
- Draw a light graph: LDR on A0, a new point every 100 ms, while the old part of the graph scrolls to the left.
- Make an animation: a ball with a radius of 4 dots moves around the screen and bounces off the edges.
Solution 1: press counter on the display
// Task – button press counter (pin 2) on the OLED display, in large digits
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
Adafruit_SSD1306 display(128, 64, &Wire, -1);
const int BUTTON = 2;
int count = 0;
int previous = HIGH;
void show() {
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.print("Presses:");
display.setTextSize(4);
display.setCursor(0, 20);
display.print(count);
display.display();
}
void setup() {
pinMode(BUTTON, INPUT_PULLUP);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.setTextColor(SSD1306_WHITE);
show();
}
void loop() {
int now = digitalRead(BUTTON);
if (previous == HIGH && now == LOW) {
count++;
show();
delay(30);
}
previous = now;
}
Solution 2: light graph
// Task – light graph: LDR on A0, a new point every 100 ms,
// and the graph scrolls to the left
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
Adafruit_SSD1306 display(128, 64, &Wire, -1);
byte heights[128]; // keep the last 128 readings (one per column)
void setup() {
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
for (int x = 0; x < 128; x++) {
heights[x] = 63;
}
}
void loop() {
for (int x = 0; x < 127; x++) { // shift all points to the left
heights[x] = heights[x + 1];
}
heights[127] = map(analogRead(A0), 0, 1023, 63, 0); // bigger value = higher point
display.clearDisplay();
for (int x = 1; x < 128; x++) {
display.drawLine(x - 1, heights[x - 1], x, heights[x], SSD1306_WHITE);
}
display.display();
delay(100);
}
Solution 3: bouncing ball
// Task – a ball bouncing off the edges of the screen
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
Adafruit_SSD1306 display(128, 64, &Wire, -1);
int x = 10, y = 10; // ball position
int dx = 2, dy = 1; // movement in each step
const int R = 4; // radius
void setup() {
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
}
void loop() {
x += dx;
y += dy;
if (x - R <= 0 || x + R >= 127) dx = -dx; // hit the left or right edge
if (y - R <= 0 || y + R >= 63) dy = -dy; // hit the top or bottom edge
display.clearDisplay();
display.drawRect(0, 0, 128, 64, SSD1306_WHITE);
display.fillCircle(x, y, R, SSD1306_WHITE);
display.display();
delay(20);
}
Check your knowledge
Open the quiz
Quiz: OLED display
1. Which Arduino Uno pins do SDA and SCL connect to?
2. Where is point (0, 0) on the display?
3. What happens if you forget display()?
4. What is the most common I2C address of an SSD1306 display?
