All tutorials

4 · Displays & buses

Write on an OLED screen

Advanced25 min

Goal

Display text on a 128×64 SSD1306 over the I²C bus.

Why it matters

I²C needs only two wires to drive dozens of peripherals.

What you need

  • Arduino UNO board
  • SSD1306 OLED display (I²C)

Nothing to buy: every component below is simulated in the browser.

Steps

  1. 01Drag the OLED 128×64 (I2C) component onto the canvas.
  2. 02Wire SDA to A4 and SCL to A5 — the screen answers at address 0x3C.
  3. 03Load the starter sketch: it ships a mini SSD1306 driver on <Wire.h>.
  4. 04Run it, then edit the oledText() calls to write your own message.

Starter sketch

// OLED Hello — SSD1306 128x64 over I2C (SDA = A4, SCL = A5).
// The cloud toolchain ships only <Wire.h>, so this sketch includes a tiny
// built-in SSD1306 driver: no external libraries needed.

#include <Wire.h>

#define OLED_ADDR 0x3C

static const uint8_t FONT5X7[] PROGMEM = {
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x07, 0x00, 0x07, 0x00,
  0x14, 0x7f, 0x14, 0x7f, 0x14, 0x24, 0x2a, 0x7f, 0x2a, 0x12, 0x23, 0x13, 0x08, 0x64, 0x62,
  0x36, 0x49, 0x56, 0x20, 0x50, 0x00, 0x08, 0x07, 0x03, 0x00, 0x00, 0x1c, 0x22, 0x41, 0x00,
  0x00, 0x41, 0x22, 0x1c, 0x00, 0x2a, 0x1c, 0x7f, 0x1c, 0x2a, 0x08, 0x08, 0x3e, 0x08, 0x08,
  0x00, 0x80, 0x70, 0x30, 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x00, 0x60, 0x60, 0x00,
  0x20, 0x10, 0x08, 0x04, 0x02, 0x3e, 0x51, 0x49, 0x45, 0x3e, 0x00, 0x42, 0x7f, 0x40, 0x00,
  0x72, 0x49, 0x49, 0x49, 0x46, 0x21, 0x41, 0x49, 0x4d, 0x33, 0x18, 0x14, 0x12, 0x7f, 0x10,
  0x27, 0x45, 0x45, 0x45, 0x39, 0x3c, 0x4a, 0x49, 0x49, 0x31, 0x41, 0x21, 0x11, 0x09, 0x07,
  0x36, 0x49, 0x49, 0x49, 0x36, 0x46, 0x49, 0x49, 0x29, 0x1e, 0x00, 0x00, 0x14, 0x00, 0x00,
  0x00, 0x40, 0x34, 0x00, 0x00, 0x00, 0x08, 0x14, 0x22, 0x41, 0x14, 0x14, 0x14, 0x14, 0x14,
  0x00, 0x41, 0x22, 0x14, 0x08, 0x02, 0x01, 0x59, 0x09, 0x06, 0x3e, 0x41, 0x5d, 0x59, 0x4e,
  0x7c, 0x12, 0x11, 0x12, 0x7c, 0x7f, 0x49, 0x49, 0x49, 0x36, 0x3e, 0x41, 0x41, 0x41, 0x22,
  0x7f, 0x41, 0x41, 0x41, 0x3e, 0x7f, 0x49, 0x49, 0x49, 0x41, 0x7f, 0x09, 0x09, 0x09, 0x01,
  0x3e, 0x41, 0x41, 0x51, 0x73, 0x7f, 0x08, 0x08, 0x08, 0x7f, 0x00, 0x41, 0x7f, 0x41, 0x00,
  0x20, 0x40, 0x41, 0x3f, 0x01, 0x7f, 0x08, 0x14, 0x22, 0x41, 0x7f, 0x40, 0x40, 0x40, 0x40,
  0x7f, 0x02, 0x1c, 0x02, 0x7f, 0x7f, 0x04, 0x08, 0x10, 0x7f, 0x3e, 0x41, 0x41, 0x41, 0x3e,
  0x7f, 0x09, 0x09, 0x09, 0x06, 0x3e, 0x41, 0x51, 0x21, 0x5e, 0x7f, 0x09, 0x19, 0x29, 0x46,
  0x26, 0x49, 0x49, 0x49, 0x32, 0x03, 0x01, 0x7f, 0x01, 0x03, 0x3f, 0x40, 0x40, 0x40, 0x3f,
  0x1f, 0x20, 0x40, 0x20, 0x1f, 0x3f, 0x40, 0x38, 0x40, 0x3f, 0x63, 0x14, 0x08, 0x14, 0x63,
  0x03, 0x04, 0x78, 0x04, 0x03, 0x61, 0x59, 0x49, 0x4d, 0x43, 0x00, 0x7f, 0x41, 0x41, 0x41,
  0x02, 0x04, 0x08, 0x10, 0x20, 0x00, 0x41, 0x41, 0x41, 0x7f, 0x04, 0x02, 0x01, 0x02, 0x04,
  0x40, 0x40, 0x40, 0x40, 0x40, 0x00, 0x03, 0x07, 0x08, 0x00, 0x20, 0x54, 0x54, 0x78, 0x40,
  0x7f, 0x28, 0x44, 0x44, 0x38, 0x38, 0x44, 0x44, 0x44, 0x28, 0x38, 0x44, 0x44, 0x28, 0x7f,
  0x38, 0x54, 0x54, 0x54, 0x18, 0x00, 0x08, 0x7e, 0x09, 0x02, 0x18, 0xa4, 0xa4, 0x9c, 0x78,
  0x7f, 0x08, 0x04, 0x04, 0x78, 0x00, 0x44, 0x7d, 0x40, 0x00, 0x20, 0x40, 0x40, 0x3d, 0x00,
  0x7f, 0x10, 0x28, 0x44, 0x00, 0x00, 0x41, 0x7f, 0x40, 0x00, 0x7c, 0x04, 0x78, 0x04, 0x78,
  0x7c, 0x08, 0x04, 0x04, 0x78, 0x38, 0x44, 0x44, 0x44, 0x38, 0xfc, 0x18, 0x24, 0x24, 0x18,
  0x18, 0x24, 0x24, 0x18, 0xfc, 0x7c, 0x08, 0x04, 0x04, 0x08, 0x48, 0x54, 0x54, 0x54, 0x24,
  0x04, 0x04, 0x3f, 0x44, 0x24, 0x3c, 0x40, 0x40, 0x20, 0x7c, 0x1c, 0x20, 0x40, 0x20, 0x1c,
  0x3c, 0x40, 0x30, 0x40, 0x3c, 0x44, 0x28, 0x10, 0x28, 0x44, 0x4c, 0x90, 0x90, 0x90, 0x7c,
  0x44, 0x64, 0x54, 0x4c, 0x44, 0x00, 0x08, 0x36, 0x41, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00,
  0x00, 0x41, 0x36, 0x08, 0x00, 0x02, 0x01, 0x02, 0x04, 0x02
};

void oledCmd(uint8_t c) {
  Wire.beginTransmission(OLED_ADDR);
  Wire.write(0x00);
  Wire.write(c);
  Wire.endTransmission();
}

void oledInit() {
  Wire.begin();
  const uint8_t init[] = {
    0xAE, 0xD5, 0x80, 0xA8, 0x3F, 0xD3, 0x00, 0x40,
    0x8D, 0x14, 0x20, 0x00, 0xA1, 0xC8, 0xDA, 0x12,
    0x81, 0xCF, 0xD9, 0xF1, 0xDB, 0x40, 0xA4, 0xA6, 0xAF
  };
  for (uint8_t i = 0; i < sizeof(init); i++) oledCmd(init[i]);
}

void oledWindow(uint8_t col, uint8_t page, uint8_t width) {
  oledCmd(0x21); oledCmd(col); oledCmd(col + width - 1);
  oledCmd(0x22); oledCmd(page); oledCmd(page);
}

void oledClear() {
  oledCmd(0x21); oledCmd(0); oledCmd(127);
  oledCmd(0x22); oledCmd(0); oledCmd(7);
  for (uint16_t i = 0; i < 1024; i += 16) {
    Wire.beginTransmission(OLED_ADDR);
    Wire.write(0x40);
    for (uint8_t j = 0; j < 16; j++) Wire.write(0x00);
    Wire.endTransmission();
  }
}

// Prints text at a character cell. scale = 1 (5x7) or 2 (10x14).
void oledText(uint8_t col, uint8_t page, const char *text, uint8_t scale) {
  for (uint8_t row = 0; row < scale; row++) {
    uint8_t x = col;
    oledWindow(col, page + row, 128 - col);
    Wire.beginTransmission(OLED_ADDR);
    Wire.write(0x40);
    uint8_t sent = 0;
    for (const char *p = text; *p && x < 128; p++) {
      for (uint8_t i = 0; i < 6; i++) {
        uint8_t bits = (i < 5 && *p >= 32) ? pgm_read_byte(&FONT5X7[(*p - 32) * 5 + i]) : 0x00;
        uint8_t slice = 0;
        for (uint8_t b = 0; b < 8; b++) {
          uint8_t src = (row * 8 + b) / scale;
          if (src < 8 && (bits >> src) & 1) slice |= (1 << b);
        }
        for (uint8_t s = 0; s < scale && x < 128; s++) {
          Wire.write(slice);
          x++;
          if (++sent >= 16) { Wire.endTransmission(); Wire.beginTransmission(OLED_ADDR); Wire.write(0x40); sent = 0; }
        }
      }
    }
    Wire.endTransmission();
  }
}

int counter = 0;

void setup() {
  Serial.begin(9600);
  oledInit();
  oledClear();
  oledText(4, 0, "Circuitly", 2);
  oledText(4, 3, "SSD1306 over I2C", 1);
  Serial.println("OLED ready");
}

void loop() {
  char line[24];
  snprintf(line, sizeof(line), "uptime: %lu s", millis() / 1000);
  oledText(4, 5, line, 1);
  snprintf(line, sizeof(line), "frames: %d   ", counter++);
  oledText(4, 6, line, 1);
  delay(500);
}

This tutorial runs inside the Circuitly simulator: your wiring and your code are checked automatically at every step.

Frequently asked questions

Do I need to buy hardware for this tutorial?
No. Everything runs in the browser: Circuitly simulates the board, the components and the code. Real hardware is optional if you want to rebuild the circuit afterwards.
Which components are used?
Arduino UNO board, SSD1306 OLED display (I²C). They are already available in the simulator library.
How long does it take?
About 25 minutes, advanced level, in 4 guided steps.
What should I know before starting?
The previous tutorial in the series, “7-segment display”, is enough preparation. No software install is required.
Is the code provided and checked?
Yes: a starter sketch is provided, and the simulator checks your wiring and your code automatically at every step.

For teachers

Get the teaching pack: 6 ready-to-run sessions

Full progression, objectives, student steps and an assessment grid — printable, free.

One address, no spam: just the pack and classroom updates.

In the same series

Explore other topics