ESP32 UNO - SSD1309 OLED Display

Need a sharp, bright screen for your ESP32 Uno? A 2.42 inch SSD1309 OLED needs only four wires and gives you 128x64 pixels of crisp monochrome output. Plug it into the ESP32 Uno form board, add the DIYables_OLED_SSD1309 library, and your sketch can print text, draw shapes and scroll pictures on this I2C OLED display.

ESP32 Uno SSD1309 OLED

By the end, you can:

  1. Wire a 2.42 inch SSD1309 OLED to the I2C pins of the ESP32 Uno
  2. Show text and numbers in different sizes
  3. Draw lines, boxes, circles and triangles, and show a bitmap
  4. Scroll the screen with hardware scrolling and change the contrast
  5. Load custom Adafruit GFX fonts

Components You Need

1×ESP32 Uno-form Board
1×USB Cable Type-A to Type-C (for USB-A PC)
1×USB Cable Type-C to Type-C (for USB-C PC)
1×SSD1309 I2C OLED Display 128x64 (2.42 inch)
1×Jumper Wires

Or you can buy the following kits:

1×DIYables Sensor Kit (18 sensors/displays)
Disclosure: Some of the links provided in this section are Amazon affiliate links. We may receive a commission for any purchases made through these links at no additional cost to you.
Additionally, some of these links are for products from our own brand, DIYables .

What Is the SSD1309 2.42-Inch OLED?

By the end of this part, you know what the chip does and why it matters for your wiring.

An OLED pixel makes its own light, so black is truly black and you can read the screen from any angle. The SSD1309 is the driver chip behind many 128×64 OLED panels. Its commands are close to the SSD1306 chip, so code moves between the two with small changes.

Feature SSD1309 Module
Resolution 128×64 pixels
Interface I2C (two signal wires)
Default I2C address 0x3C (some modules use 0x3D)
Panel supply external, built into the module as a boost circuit
Maximum panel voltage up to 16V (SSD1306 stops near 4.2V)
Color fixed by the glass - white, blue, yellow, green or two-tone

Good to know: you cannot change the color in code. Software only turns each pixel on or off.

Pins of the I2C Module

A typical module has four pins.

Pin Job
GND Ground
VCC Power input - 5V on most modules, 3.3V if your module allows it
SCL I2C clock line
SDA I2C data line
SSD1309 OLED Pinout

※ NOTE THAT:

  • Pin order is different between brands. Read the silkscreen labels on your module before you connect anything.
  • This guide was checked with the 2.42 inch SSD1309 OLED from DIYables. Other 2.4 or 2.42 inch SSD1309 modules should behave the same way.

ESP32 Uno Pinout

Every Uno header pin on the ESP32 Uno form board has its own GPIO number, and your sketch uses that number. Look at the diagram first, then find the SDA and SCL pins you need for the OLED.

ESP32 Uno pinout diagram

How to Connect the OLED

By the end of this part, four wires link the OLED and the ESP32 Uno.

The wiring diagram between ESP32 Uno SSD1309 OLED 128x64

This image is created using Fritzing. Click to enlarge image

ESP32 Uno SSD1309 OLED 128x64 real wiring

This image is created using Fritzing. Click to enlarge image

OLED Pin Connect To
VCC (Vin) 5V
GND GND
SDA SDA (GPIO21)
SCL SCL (GPIO22)

Good to know: on the ESP32 Uno, pins A4 and A5 are NOT I2C. They are input-only analog pins. Use the dedicated SDA and SCL header pins. Wire.begin() already uses GPIO21 and GPIO22, so the code needs no pin setting.

WARNING

ESP32 Uno pins are 3.3V logic and are NOT 5V tolerant. Most 2.42 inch modules have an onboard regulator, so SDA and SCL stay at 3.3V even with 5V on VCC. If your module pulls SDA and SCL up to 5V, power it from the 3.3V pin if it supports that, or add an I2C level shifter.

Set Up the Library and Learn the Pattern

By the end of this part, the library is installed and you know the five steps every OLED sketch follows.

Step 1 — Add the DIYables_OLED_SSD1309 library

  • Open Arduino IDE and click the Libraries icon in the left bar.
  • Type "DIYables_OLED_SSD1309" in the search box and pick the one made by DIYables.
  • Hit Install and wait for it to finish.
  • Search for DIYables_OLED_SSD1309 created by DIYables and click the Install button.
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32 Dev Module
Library Manager
Type:
All
Topic:
All
DIYables_OLED_SSD1309 by DIYables
Supports 128x64 and 128x32 SSD1309-based monochrome OLED displays over I2C. API compatible with Adafruit_SSD1306. Extends Adafruit_GFX for full graphics support. Works with all Arduino-compatible boards. More info
1.0.2
INSTALL
Newbiely.ino
···
1 void setup() {
Output
Serial Monitor
Ln 1, Col 1
ESP32 Dev Module on COM15
1
  • The IDE asks for a dependency, the Adafruit GFX Library. Say yes with Install All.

Step 2 — Learn the sketch pattern

Each sketch includes the headers, builds a display object, starts the display in setup(), draws into a memory buffer, and sends the buffer to the screen with display().

Headers:

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

Size of the panel, 128 wide and 64 high:

#define SCREEN_WIDTH 128 // OLED width in pixels #define SCREEN_HEIGHT 64 // OLED height in pixels

Display object (the -1 means no reset pin is wired):

// SSD1309 on the default I2C bus (SDA = GPIO21, SCL = GPIO22) DIYables_OLED_SSD1309 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);

Start-up inside setup():

// start the SSD1309 at address 0x3C, the usual default if (!oled.begin(SSD1309_SWITCHCAPVCC, 0x3C)) { Serial.println(F("SSD1309 allocation failed")); while (true); }

Next you draw with calls like clearDisplay(), drawPixel() or print(). The panel stays unchanged until oled.display() runs.

※ NOTE THAT:

Every example below uses the SSD1309 OLED 128×64 (2.42 inch) and the DIYables_OLED_SSD1309 library on the ESP32 Uno.

Upload and Test any example

  1. First time with this board? Set it up with ESP32 Uno - Getting Started before you continue.
  2. Build the circuit by following the wiring table above.
  3. Plug in the USB Type-C cable between the ESP32 Uno and your computer.
  4. Select the board in Arduino IDE: pick ESP32 Dev Module and the COM port of the board (CH340).
  5. Install the libraries from Step 1 if you have not done so.
  6. Copy the code of an example below into Arduino IDE.
  7. Upload the sketch and wait for "Done uploading".
  8. Look at the OLED. The picture shows up a second after the board restarts.
  9. Pro tip: keep the Serial Monitor open at 9600 baud while you test. If you see "SSD1309 allocation failed", the board cannot reach the OLED, so check SDA, SCL and the I2C address first.

Example 1 — Hello World

By the end of this part, the screen shows your first message.

/* * This ESP32 Uno code was developed by newbiely.com * * This ESP32 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-uno/esp32-uno-ssd1309-oled-display */ /* * ESP32 Uno + SSD1309 OLED: Hello World * Shows a greeting in two text sizes on the 2.42 inch 128x64 I2C OLED. */ #include <Wire.h> #include <Adafruit_GFX.h> #include <DIYables_OLED_SSD1309.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C // I2C bus uses the default SDA (GPIO21) and SCL (GPIO22) header pins DIYables_OLED_SSD1309 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); void setup() { Serial.begin(9600); if (!display.begin(SSD1309_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1309 allocation failed")); for (;;); } display.clearDisplay(); display.setTextSize(1); // each character is 6x8 pixels display.setTextColor(SSD1309_PIXEL_ON); // draw with lit pixels display.setCursor(0, 0); display.println(F("Hello, World!")); display.println(); display.setTextSize(2); // each character is 12x16 pixels display.println(F("DIYables")); display.setTextSize(1); display.println(); display.println(F("SSD1309 OLED 128x64")); display.display(); // copy the frame buffer to the OLED } void loop() { }
Code Part What It Does
display.begin() Starts the OLED at address 0x3C
setTextSize(1) Small 6×8 characters for the first line
setTextSize(2) Large 12×16 characters for the word DIYables
display.display() Sends the buffer to the screen once
loop() Stays empty because the picture never changes

The screen shows a greeting, a big word and a small label. If this works, your wiring and library are fine.

Example 2 — Text and Numbers

By the end of this part, you can print different sizes, integers, floats and hex numbers.

/* * This ESP32 Uno code was developed by newbiely.com * * This ESP32 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-uno/esp32-uno-ssd1309-oled-display */ /* * ESP32 Uno + SSD1309 OLED: Text Demo * Prints different text sizes and number formats on the 2.42 inch 128x64 I2C OLED. */ #include <Wire.h> #include <Adafruit_GFX.h> #include <DIYables_OLED_SSD1309.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C // I2C bus uses the default SDA (GPIO21) and SCL (GPIO22) header pins DIYables_OLED_SSD1309 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); void setup() { Serial.begin(9600); if (!display.begin(SSD1309_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1309 allocation failed")); for (;;); } delay(2000); // give the OLED a moment to start up display.clearDisplay(); // start with an empty frame buffer display.setTextSize(1); // smallest text display.setTextColor(SSD1309_PIXEL_ON); // lit pixels for the text display.setCursor(0, 10); // move the cursor to x=0, y=10 display.println(F("Text size = 1")); // write into the buffer display.display(); // show the buffer delay(2000); display.setTextSize(2); // double-size text display.setCursor(0, 30); // move the cursor lower on the screen display.println(F("Size = 2")); // write into the buffer display.display(); // show the buffer delay(2000); display.clearDisplay(); // wipe the screen before the number demo display.setTextSize(1); display.setCursor(0, 0); display.println(F("Display integer:")); display.println(12345); display.println(); display.println(F("Display float:")); display.println(1.2345); display.println(); display.println(F("Display HEX:")); display.println(0xABCD, HEX); display.display(); // show the number samples } void loop() { }
Code Part What It Does
setCursor(x, y) Chooses where the next text starts
println(F("...")) Prints fixed text and keeps it in flash, not RAM
println(12345) Prints an integer in decimal
println(1.2345) Prints a float with two decimals
println(0xABCD, HEX) Prints a number in hexadecimal

You see two text sizes first. After that, the screen clears and lists the three number formats.

Display Function Reference

Function What It Does
oled.clearDisplay() Empties the frame buffer
oled.display() Pushes the buffer out to the panel
oled.drawPixel(x, y, color) Sets or clears one pixel
oled.setTextSize(n) Text size factor n - size 1 is 6×8 pixels, size 3 is 18×24, up to 8
oled.setCursor(x, y) Sets where the next character is drawn
oled.setTextColor(SSD1309_PIXEL_ON) Draws text with a see-through background
oled.setTextColor(SSD1309_PIXEL_OFF, SSD1309_PIXEL_ON) Draws text with a solid background
oled.println("message") Prints a string and starts a new line
oled.println(number) Prints an integer in decimal
oled.println(number, HEX) Prints an integer in hexadecimal
oled.startscrollright(start, stop) Moves the picture right, between two pages
oled.startscrollleft(start, stop) Moves the picture left, between two pages
oled.startscrolldiagright(start, stop) Moves the picture up and right
oled.startscrolldiagleft(start, stop) Moves the picture up and left
oled.stopscroll() Stops any running scroll
oled.setContrast(value) Sets brightness from 0 to 255
oled.dim(true/false) Switches between the lowest brightness and the last contrast
oled.invertDisplay(true/false) Swaps lit and dark pixels in hardware

Put Text in the Middle of the Screen

A centered title looks tidy on a small display. Follow this article to measure your text and place it in the middle; the method is the same on the ESP32 Uno: How to vertical/horizontal center on OLED.

Example 3 — Shapes

By the end of this part, you can draw every basic shape.

The library is built on Adafruit_GFX, so you get pixels, lines, rectangles, circles, triangles and rounded boxes. The sketch shows each shape as an outline, then filled with inverted pixels.

/* * This ESP32 Uno code was developed by newbiely.com * * This ESP32 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-uno/esp32-uno-ssd1309-oled-display */ /* * ESP32 Uno + SSD1309 OLED: Shapes * Loops through every shape type on the 2.42 inch 128x64 I2C OLED. */ #include <Wire.h> #include <Adafruit_GFX.h> #include <DIYables_OLED_SSD1309.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C // I2C bus uses the default SDA (GPIO21) and SCL (GPIO22) header pins DIYables_OLED_SSD1309 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); void setup() { Serial.begin(9600); if (!display.begin(SSD1309_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1309 allocation failed")); for (;;); } display.clearDisplay(); } void loop() { // dotted diagonal display.clearDisplay(); for (int16_t i = 0; i < display.width(); i += 4) { display.drawPixel(i, i * display.height() / display.width(), SSD1309_PIXEL_ON); } display.display(); delay(1500); // lines fanning out from the top-left corner display.clearDisplay(); for (int16_t i = 0; i < display.width(); i += 8) { display.drawLine(0, 0, i, display.height() - 1, SSD1309_PIXEL_ON); } for (int16_t i = 0; i < display.height(); i += 8) { display.drawLine(0, 0, display.width() - 1, i, SSD1309_PIXEL_ON); } display.display(); delay(1500); // nested outline rectangles display.clearDisplay(); for (int16_t i = 0; i < display.height() / 2; i += 4) { display.drawRect(i, i, display.width() - 2 * i, display.height() - 2 * i, SSD1309_PIXEL_ON); } display.display(); delay(1500); // nested rectangles drawn with inverted pixels display.clearDisplay(); for (int16_t i = 0; i < display.height() / 2; i += 6) { display.fillRect(i, i, display.width() - 2 * i, display.height() - 2 * i, SSD1309_PIXEL_INVERSE); } display.display(); delay(1500); // concentric outline circles display.clearDisplay(); for (int16_t i = max(display.width(), display.height()) / 2; i > 0; i -= 5) { display.drawCircle(display.width() / 2, display.height() / 2, i, SSD1309_PIXEL_ON); } display.display(); delay(1500); // concentric circles drawn with inverted pixels display.clearDisplay(); for (int16_t i = max(display.width(), display.height()) / 2; i > 0; i -= 6) { display.fillCircle(display.width() / 2, display.height() / 2, i, SSD1309_PIXEL_INVERSE); } display.display(); delay(1500); // nested rounded outlines display.clearDisplay(); for (int16_t i = 0; i < display.height() / 2 - 2; i += 4) { display.drawRoundRect(i, i, display.width() - 2 * i, display.height() - 2 * i, display.height() / 4, SSD1309_PIXEL_ON); } display.display(); delay(1500); // nested rounded boxes drawn with inverted pixels display.clearDisplay(); for (int16_t i = 0; i < display.height() / 2 - 2; i += 4) { display.fillRoundRect(i, i, display.width() - 2 * i, display.height() - 2 * i, display.height() / 4, SSD1309_PIXEL_INVERSE); } display.display(); delay(1500); // nested outline triangles display.clearDisplay(); for (int16_t i = 0; i < max(display.width(), display.height()) / 2; i += 5) { display.drawTriangle( display.width() / 2, display.height() / 2 - i, display.width() / 2 - i, display.height() / 2 + i, display.width() / 2 + i, display.height() / 2 + i, SSD1309_PIXEL_ON); } display.display(); delay(1500); // nested triangles drawn with inverted pixels display.clearDisplay(); for (int16_t i = max(display.width(), display.height()) / 2; i > 0; i -= 6) { display.fillTriangle( display.width() / 2, display.height() / 2 - i, display.width() / 2 - i, display.height() / 2 + i, display.width() / 2 + i, display.height() / 2 + i, SSD1309_PIXEL_INVERSE); } display.display(); delay(1500); }

Each pattern stays on screen for 1.5 seconds, then the next one appears.

Example 4 — Hardware Scrolling

By the end of this part, the text moves without any work from the CPU.

The chip has a scroll engine with four modes: right, left, diagonal right and diagonal left. Give each mode a first page and a last page. One page is a strip 8 pixels high, which gives pages 0 to 7 on this screen.

※ NOTE THAT:

Call display() first, then start the scroll. Do not draw while a scroll runs - call stopscroll() before you draw again.

/* * This ESP32 Uno code was developed by newbiely.com * * This ESP32 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-uno/esp32-uno-ssd1309-oled-display */ /* * ESP32 Uno + SSD1309 OLED: Hardware Scroll * Runs the four built-in scroll modes on the 2.42 inch 128x64 I2C OLED. */ #include <Wire.h> #include <Adafruit_GFX.h> #include <DIYables_OLED_SSD1309.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C // I2C bus uses the default SDA (GPIO21) and SCL (GPIO22) header pins DIYables_OLED_SSD1309 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); void setup() { Serial.begin(9600); if (!display.begin(SSD1309_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1309 allocation failed")); for (;;); } display.clearDisplay(); display.setTextSize(2); display.setTextColor(SSD1309_PIXEL_ON); display.setCursor(10, 24); display.println(F("DIYables")); display.display(); delay(2000); } void loop() { // run the scroll to the right over all 8 pages display.startscrollright(0x00, 0x07); delay(3000); display.stopscroll(); delay(500); // now to the left display.startscrollleft(0x00, 0x07); delay(3000); display.stopscroll(); delay(500); // diagonal, moving right display.startscrolldiagright(0x00, 0x07); delay(3000); display.stopscroll(); delay(500); // diagonal, moving left display.startscrolldiagleft(0x00, 0x07); delay(3000); display.stopscroll(); delay(500); }

Each mode runs for 3 seconds, then stops for half a second.

Example 5 — Bitmap Images

By the end of this part, your own logo can appear on the OLED.

The OLED cannot read PNG or JPG files. Turn the picture into a C array with the free image2cpp online tool:

  1. Open the converter and add a PNG, JPG or BMP file.
  2. Set the canvas to 128×64 or smaller.
  3. Choose Arduino code for the output.
  4. Copy the array it prints into your code.
image to bitmap array

The example shows a 16×16 heart, then a full-screen DIYables logo, then inverts the whole display.

/* * This ESP32 Uno code was developed by newbiely.com * * This ESP32 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-uno/esp32-uno-ssd1309-oled-display */ /* * ESP32 Uno + SSD1309 OLED: Bitmap Images * Switches between a small heart icon and the full-screen DIYables logo. */ #include <Wire.h> #include <Adafruit_GFX.h> #include <DIYables_OLED_SSD1309.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C // I2C bus uses the default SDA (GPIO21) and SCL (GPIO22) header pins DIYables_OLED_SSD1309 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // small heart icon, 16x16 pixels const unsigned char heart16x16[] PROGMEM = { 0x00, 0x00, 0x03, 0xc0, 0x0f, 0xf0, 0x1f, 0xf8, 0x3f, 0xfc, 0x7f, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, 0x0f, 0xf0, 0x03, 0xc0, 0x00, 0x00 }; // full-screen logo, 128x64 pixels const unsigned char diyablesLogo[] PROGMEM = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x80, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0xff, 0xfc, 0x00, 0x00, 0xff, 0xf0, 0x00, 0x00, 0xff, 0xfc, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0xff, 0xfc, 0x00, 0x03, 0xff, 0xfc, 0x00, 0x00, 0x7f, 0xf8, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0x00, 0x00, 0x3f, 0xf0, 0x00, 0x03, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xf0, 0x00, 0x1f, 0xff, 0xff, 0xc0, 0x00, 0x1f, 0xe0, 0x00, 0x0f, 0xff, 0xff, 0xe0, 0x00, 0x7f, 0xf0, 0x00, 0x7f, 0xff, 0xff, 0xe0, 0x00, 0x0f, 0xc0, 0x00, 0x1f, 0xff, 0xff, 0xf8, 0x00, 0x3f, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x07, 0x80, 0x00, 0x7f, 0xff, 0xff, 0xfc, 0x00, 0x1f, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x03, 0x80, 0x00, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x1f, 0xc0, 0x01, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 0x00, 0x01, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x1f, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x07, 0xff, 0xf0, 0x7f, 0xff, 0x00, 0x0f, 0xc0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x0f, 0xff, 0xf0, 0x7f, 0xff, 0x80, 0x0f, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x1f, 0xff, 0xf0, 0x7f, 0xff, 0x80, 0x07, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xff, 0xf0, 0x7f, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x7f, 0xff, 0xf0, 0x7f, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xfe, 0x00, 0x00, 0xff, 0xfc, 0x00, 0x00, 0xff, 0xfc, 0x00, 0x03, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xfe, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x01, 0xff, 0xfc, 0x00, 0x01, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xfe, 0x00, 0x00, 0xff, 0xff, 0x00, 0x03, 0xff, 0xfc, 0x00, 0x01, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xfe, 0x00, 0x00, 0xff, 0xff, 0x00, 0x03, 0xff, 0xfc, 0x00, 0x01, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xfe, 0x00, 0x00, 0xff, 0xff, 0x00, 0x03, 0xff, 0xfc, 0x00, 0x01, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x01, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x7f, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x7f, 0xff, 0xf0, 0x7f, 0xff, 0xc0, 0x07, 0x80, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x3f, 0xff, 0xf0, 0x7f, 0xff, 0x80, 0x07, 0x80, 0x07, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x1f, 0xff, 0xf0, 0x7f, 0xff, 0x80, 0x07, 0xc0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0x80, 0x0f, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0x80, 0x00, 0x00, 0x07, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xe0, 0x01, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x03, 0x00, 0x01, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x1f, 0xe0, 0x00, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x07, 0x80, 0x00, 0xff, 0xff, 0xff, 0xfc, 0x00, 0x1f, 0xe0, 0x00, 0x7f, 0xff, 0xff, 0xf0, 0x00, 0x0f, 0xc0, 0x00, 0x3f, 0xff, 0xff, 0xf8, 0x00, 0x3f, 0xf0, 0x00, 0x3f, 0xff, 0xff, 0xe0, 0x00, 0x1f, 0xe0, 0x00, 0x1f, 0xff, 0xff, 0xf0, 0x00, 0x3f, 0xf8, 0x00, 0x1f, 0xff, 0xff, 0x80, 0x00, 0x3f, 0xe0, 0x00, 0x07, 0xff, 0xff, 0xe0, 0x00, 0x7f, 0xf8, 0x00, 0x0f, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xf8, 0x00, 0x03, 0xff, 0xff, 0xc0, 0x00, 0x7f, 0xfc, 0x00, 0x03, 0xff, 0xf8, 0x00, 0x00, 0xff, 0xfc, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x00, 0x7f, 0xe0, 0x00, 0x01, 0xff, 0xfe, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x01, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x07, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xf8, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x03, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x00, 0x00, 0x3f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x0f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; void setup() { Serial.begin(9600); if (!display.begin(SSD1309_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1309 allocation failed")); for (;;); } delay(2000); display.setCursor(0, 0); } void loop() { // show the heart in the middle display.clearDisplay(); display.drawBitmap((SCREEN_WIDTH - 16) / 2, (SCREEN_HEIGHT - 16) / 2, heart16x16, 16, 16, SSD1309_PIXEL_ON); display.display(); delay(2000); // show the full-screen logo display.clearDisplay(); display.drawBitmap(0, 0, diyablesLogo, 128, 64, SSD1309_PIXEL_ON); display.display(); delay(2000); // flip lit and dark pixels for a moment display.invertDisplay(true); delay(2000); display.invertDisplay(false); delay(500); }

※ NOTE THAT:

  • The bitmap must fit the screen (128×64 for the 2.42 inch module).

Example 6 — Contrast and Dim

By the end of this part, you can set the brightness in code.

The brightness has 256 levels. setContrast() selects an exact level, while dim() toggles between the lowest level and the last one you set.

/* * This ESP32 Uno code was developed by newbiely.com * * This ESP32 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-uno/esp32-uno-ssd1309-oled-display */ /* * ESP32 Uno + SSD1309 OLED: Contrast and Dim * Fades the contrast up and down, then switches dim mode on and off. */ #include <Wire.h> #include <Adafruit_GFX.h> #include <DIYables_OLED_SSD1309.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C // I2C bus uses the default SDA (GPIO21) and SCL (GPIO22) header pins DIYables_OLED_SSD1309 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); void setup() { Serial.begin(9600); if (!display.begin(SSD1309_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1309 allocation failed")); for (;;); } // draw a pattern so brightness changes are easy to see display.clearDisplay(); display.fillRect(0, 0, 64, 32, SSD1309_PIXEL_ON); display.fillRect(64, 32, 64, 32, SSD1309_PIXEL_ON); display.setTextSize(1); display.setTextColor(SSD1309_PIXEL_INVERSE); display.setCursor(16, 28); display.println(F("Contrast Demo")); display.display(); delay(2000); } void loop() { // fade the brightness up for (int c = 0; c <= 255; c += 5) { display.setContrast((uint8_t)c); delay(30); } delay(1000); // fade the brightness down for (int c = 255; c >= 0; c -= 5) { display.setContrast((uint8_t)c); delay(30); } delay(1000); // jump to dim mode and back display.dim(true); // lowest brightness delay(2000); display.dim(false); // go back to the set contrast delay(2000); }

The brightness fades up to full, fades down to zero, then dims and returns.

Example 7 — Custom Fonts

By the end of this part, the screen can show serif, sans and mono fonts.

Adafruit GFX ships many FreeFont typefaces. Include a font header and call setFont(). Pass NULL to setFont() when you want the default font again.

※ NOTE THAT:

  • With a custom font, the cursor Y value is the text baseline, not the top-left corner.
  • Fonts live in flash memory. The ESP32 Uno has 4MB of flash, so you can mix several fonts in one sketch.
/* * This ESP32 Uno code was developed by newbiely.com * * This ESP32 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-uno/esp32-uno-ssd1309-oled-display */ /* * ESP32 Uno + SSD1309 OLED: Custom Fonts * Shows the built-in font and three Adafruit GFX FreeFonts one after another. */ #include <Wire.h> #include <Adafruit_GFX.h> #include <DIYables_OLED_SSD1309.h> #include <Fonts/FreeSerif9pt7b.h> #include <Fonts/FreeSansBold12pt7b.h> #include <Fonts/FreeMono9pt7b.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C // I2C bus uses the default SDA (GPIO21) and SCL (GPIO22) header pins DIYables_OLED_SSD1309 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); void setup() { Serial.begin(9600); if (!display.begin(SSD1309_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1309 allocation failed")); for (;;); } display.clearDisplay(); display.display(); } void loop() { // default built-in font display.clearDisplay(); display.setFont(NULL); display.setTextSize(1); display.setTextColor(SSD1309_PIXEL_ON); display.setCursor(0, 0); display.println(F("Built-in 5x7 font")); display.println(); display.setTextSize(2); display.println(F("DIYables")); display.display(); delay(3000); // serif font display.clearDisplay(); display.setFont(&FreeSerif9pt7b); display.setTextSize(1); display.setTextColor(SSD1309_PIXEL_ON); display.setCursor(0, 14); // for GFX fonts Y is the text baseline display.println(F("FreeSerif 9pt")); display.setCursor(0, 38); display.println(F("DIYables OLED")); display.setCursor(0, 58); display.println(F("Hello World!")); display.display(); delay(3000); // bold sans font display.clearDisplay(); display.setFont(&FreeSansBold12pt7b); display.setTextSize(1); display.setTextColor(SSD1309_PIXEL_ON); display.setCursor(0, 20); display.println(F("SansBold")); display.setCursor(0, 52); display.println(F("DIYables")); display.display(); delay(3000); // monospace font display.clearDisplay(); display.setFont(&FreeMono9pt7b); display.setTextSize(1); display.setTextColor(SSD1309_PIXEL_ON); display.setCursor(0, 14); display.println(F("FreeMono 9pt")); display.setCursor(0, 34); display.println(F("0123456789")); display.setCursor(0, 54); display.println(F("!@#$%^&*()")); display.display(); delay(3000); }

Video Tutorial

See the ESP32 Uno build in action in the video below. It runs a "robot eyes" animation that changes mood when you tap a TTP223 touch sensor on D13 (GPIO18).

/* * This ESP32 Uno code was developed by newbiely.com * * This ESP32 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-uno/esp32-uno-ssd1309-oled-display */ /* * Robot Eyes v4 - ESP32 Uno + SSD1309 2.42 inch I2C OLED + TTP223 touch sensor * Libraries: DIYables_OLED_SSD1309 and Adafruit GFX * * Every part of the eyes is drawn in code, so the animation stays smooth. * * Wiring: * OLED VCC -> 5V, GND -> GND, SDA -> SDA (GPIO21), SCL -> SCL (GPIO22) * TTP223 VCC -> 3.3V, GND -> GND, I/O -> D13 (GPIO18) * * Behavior: * - Each tap blinks once and moves to the next mood: * NORMAL -> HAPPY -> WINK -> ANGRY -> SAD -> CRY -> STAR -> DIZZY * -> LOVE -> SURPRISED -> SUSPICIOUS -> ROLL -> SLEEPY -> (back to NORMAL) * - After 10 s without a tap the eyes go back to NORMAL. * - After 30 s without a tap the eyes fall asleep. * - A tap wakes them up again. */ #include <Wire.h> #include <Adafruit_GFX.h> #include <DIYables_OLED_SSD1309.h> #define SCREEN_WIDTH 128 #define SCREEN_HEIGHT 64 #define OLED_RESET -1 #define SCREEN_ADDRESS 0x3C #define TOUCH_PIN 18 // D13 (GPIO18) reads the TTP223 touch sensor #define ON SSD1309_PIXEL_ON #define OFF SSD1309_PIXEL_OFF // values you can tune const int EYE_RX = 22; // half the eye width const int EYE_RY = 25; // half the eye height const int EYE_CY = 32; // vertical middle of the eyes const int CX_L = 32; // x of the left eye const int CX_R = 96; // x of the right eye const unsigned long RETURN_NORMAL_MS = 10000; const unsigned long SLEEP_MS = 30000; // keep this larger than RETURN_NORMAL_MS const unsigned long DEBOUNCE_MS = 60; const unsigned long TRANSITION_MS = 110; // length of the blink between moods const unsigned long FRAME_MS = 33; // about 30 frames per second DIYables_OLED_SSD1309 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); enum Mood { NORMAL, HAPPY, WINK, ANGRY, SAD, CRY, STAR, DIZZY, LOVE, SURPRISED, SUSPICIOUS, ROLL, SLEEPY, SLEEP }; const Mood ORDER[] = { NORMAL, HAPPY, WINK, ANGRY, SAD, CRY, STAR, DIZZY, LOVE, SURPRISED, SUSPICIOUS, ROLL, SLEEPY }; const uint8_t ORDER_N = sizeof(ORDER) / sizeof(ORDER[0]); Mood mood = NORMAL; uint8_t moodIndex = 0; unsigned long moodStart = 0, transUntil = 0; // where the eyes look, from -1 to 1 float curX = 0, curY = 0, tgtX = 0, tgtY = 0; unsigned long nextLook = 0; // blink timing bool blinking = false; unsigned long blinkStart = 0, nextBlink = 0; const unsigned long BLINK_MS = 170; // touch debounce state bool lastReading = false, stableState = false; unsigned long lastChange = 0, lastActivity = 0, lastFrame = 0; // data that describes one expression struct Style { // settings of one expression float lx, ly; // gaze direction float pr; // pupil size (0 hides it) float topIn, topOut; // top lid height in pixels (inner / outer) float bot; // bottom lid height in pixels int rx, ry, cy; // eye size and place int shake; // sideways shake in pixels }; struct Eye { // final geometry of one eye int cx, cy, rx, ry; float topIn, topOut, bot; int inner; // +1 for the left eye (inner side on the right), -1 for the right eye }; Style baseStyle() { Style s = { 0, 0, 13, 0, 0, 0, EYE_RX, EYE_RY, EYE_CY, 0 }; return s; } Eye makeEye(int side, const Style& s) { Eye e; e.cx = (side == 0 ? CX_L : CX_R) + s.shake + (int)(s.lx * 3); // nudge the eye toward the gaze e.cy = s.cy + (int)(s.ly * 2); e.rx = s.rx; e.ry = s.ry; e.topIn = s.topIn; e.topOut = s.topOut; e.bot = s.bot; e.inner = (side == 0) ? 1 : -1; return e; } float lidTopAt(const Eye& e, float u) { // u runs from -1 to 1 across the eye float t = (e.inner * u + 1.0) * 0.5; // 0 is the outer edge, 1 is the inner edge return e.cy - e.ry + e.topOut + (e.topIn - e.topOut) * t; } // fill the eyeball one column at a time and cut it at the lids void drawEyeball(const Eye& e) { for (int dx = -e.rx; dx <= e.rx; dx++) { float u = (float)dx / e.rx; float hy = e.ry * sqrt(1.0 - u * u); float y0 = e.cy - hy, y1 = e.cy + hy; float lt = lidTopAt(e, u); float lb = e.cy + e.ry - e.bot; if (y0 < lt) y0 = lt; if (y1 > lb) y1 = lb; if (y1 >= y0) display.drawFastVLine(e.cx + dx, (int)y0, (int)(y1 - y0) + 1, ON); } } // pupil plus shine dots; the shine is hidden when a lid covers it void drawPupil(const Eye& e, const Style& s) { int pr = (int)(s.pr + 0.5); float mx = e.rx - pr - 2; if (mx < 0) mx = 0; float my = e.ry - pr - 2; if (my < 0) my = 0; int px = e.cx + (int)(s.lx * mx); int py = e.cy + (int)(s.ly * my); display.fillCircle(px, py, pr, OFF); float lb = e.cy + e.ry - e.bot; int hr = max(2, pr / 4); int hx = px - pr / 3, hy = py - pr / 3; if (hy - hr > lidTopAt(e, (float)(hx - e.cx) / e.rx) + 1 && hy + hr < lb - 1) display.fillCircle(hx, hy, hr, ON); int h2x = px + pr / 3, h2y = py + pr / 3; if (pr >= 8 && h2y + 1 < lb - 1) display.fillCircle(h2x, h2y, 1, ON); } void drawEyeSide(int side, const Style& s) { Eye e = makeEye(side, s); drawEyeball(e); if (s.pr > 0) drawPupil(e, s); } void drawPair(const Style& s) { drawEyeSide(0, s); drawEyeSide(1, s); } // extra shapes that go inside the eyeball void drawHeart(int cx, int cy, int r, uint16_t col) { display.fillCircle(cx - r, cy - r / 2, r, col); display.fillCircle(cx + r, cy - r / 2, r, col); display.fillTriangle(cx - 2 * r + 1, cy - r / 2 + 2, cx + 2 * r - 1, cy - r / 2 + 2, cx, cy + 2 * r + 2, col); } void drawStar(int cx, int cy, float R, float rot, uint16_t col) { float px[10], py[10]; for (int i = 0; i < 10; i++) { float ang = rot + i * PI / 5.0 - PI / 2.0; float rad = (i % 2 == 0) ? R : R * 0.45; px[i] = cx + cos(ang) * rad; py[i] = cy + sin(ang) * rad; } for (int i = 0; i < 10; i++) { int j = (i + 1) % 10; display.fillTriangle(cx, cy, (int)px[i], (int)py[i], (int)px[j], (int)py[j], col); } } void drawSpiral(int cx, int cy, float phase, int dir, uint16_t col) { float px = cx, py = cy; for (float a = 0.4; a <= 11.5; a += 0.4) { float rad = a * 1.3; float ang = dir * (a + phase); float x = cx + cos(ang) * rad, y = cy + sin(ang) * rad; display.drawLine((int)px, (int)py, (int)x, (int)y, col); display.drawLine((int)px + 1, (int)py, (int)x + 1, (int)y, col); px = x; py = y; } } void drawHappyEye(int cx, int cy) { // happy arch shape display.fillCircle(cx, cy + 10, 17, ON); display.fillCircle(cx, cy + 18, 17, OFF); } void drawClosedEye(int cx, int cy) { // shut eye as a thin bar display.fillRoundRect(cx - EYE_RX, cy - 1, 2 * EYE_RX, 3, 1, ON); } // one function per mood void drawNormal(unsigned long now) { float cover = 0; if (blinking) { float p = (float)(now - blinkStart) / BLINK_MS; if (p >= 1.0) { blinking = false; nextBlink = now + (random(5) == 0 ? 180 : random(2000, 5500)); // sometimes blink twice quickly } else { cover = sin(p * PI) * EYE_RY * 0.96; } } else if (now >= nextBlink) { blinking = true; blinkStart = now; } Style s = baseStyle(); s.lx = curX; s.ly = curY; s.pr = 13 + sin(now / 700.0) * 0.7; // small pulsing pupil s.topIn = s.topOut = s.bot = cover; drawPair(s); } void drawHappy(unsigned long now) { int bob = (int)(sin(now / 120.0) * 3); drawHappyEye(CX_L, EYE_CY - 4 + bob); drawHappyEye(CX_R, EYE_CY - 4 + bob); } void drawWink(unsigned long now) { Style s = baseStyle(); s.pr = 13 + sin(now / 700.0) * 0.7; drawEyeSide(0, s); drawHappyEye(CX_R, EYE_CY - 4); } void drawAngry() { Style s = baseStyle(); s.pr = 10; s.ly = 0.15; s.topIn = 26; s.topOut = 1; s.bot = 3; s.shake = random(-1, 2); drawPair(s); } void drawSad() { Style s = baseStyle(); s.ly = 0.6; s.topIn = 0; s.topOut = 18; drawPair(s); } void drawCry(unsigned long now) { Style s = baseStyle(); s.rx = 20; s.ry = 22; s.cy = 26; s.pr = 12; s.ly = 0.6; s.topIn = 0; s.topOut = 16; drawPair(s); int tx[2] = { CX_L - 8, CX_R + 8 }; for (int i = 0; i < 2; i++) { int ty = 44 + (int)((now / 55 + i * 9) % 19); display.fillCircle(tx[i], ty, 2, ON); display.fillTriangle(tx[i] - 2, ty, tx[i] + 2, ty, tx[i], ty - 5, ON); } } void drawStarEyes(unsigned long now) { float R = 15 + sin(now / 150.0) * 1.5; float rot = sin(now / 500.0) * 0.25; Style s = baseStyle(); for (int side = 0; side < 2; side++) { Eye e = makeEye(side, s); drawEyeball(e); drawStar(e.cx, e.cy, R, rot, OFF); } } void drawDizzy(unsigned long now) { float phase = now / 120.0; Style s = baseStyle(); for (int side = 0; side < 2; side++) { Eye e = makeEye(side, s); drawEyeball(e); drawSpiral(e.cx, e.cy, phase, side == 0 ? 1 : -1, OFF); } } void drawLove(unsigned long now) { int r = 7 + (int)(sin(now / 150.0) * 1.5); Style s = baseStyle(); for (int side = 0; side < 2; side++) { Eye e = makeEye(side, s); drawEyeball(e); drawHeart(e.cx, e.cy - 3, r, OFF); } } void drawSurprised() { Style s = baseStyle(); s.rx = 24; s.ry = 28; s.pr = 5; drawPair(s); } void drawSuspicious(unsigned long now) { Style s = baseStyle(); s.pr = 11; s.lx = sin(now / 700.0); s.ly = 0.1; s.topIn = s.topOut = 22; s.bot = 8; drawPair(s); } void drawRoll(unsigned long now) { float t = (float)((now - moodStart) % 1300) / 1300.0 * 2.0 * PI; Style s = baseStyle(); s.lx = cos(t); s.ly = sin(t) * 0.9; drawPair(s); } void drawSleepy(unsigned long now) { float k = (sin(now / 450.0) + 1.0) / 2.0; // slow drowsy swing Style s = baseStyle(); s.lx = curX * 0.3; s.ly = 0.5; s.topIn = s.topOut = 10 + k * 24; s.bot = 3; drawPair(s); } void drawSleep(unsigned long now) { int cx[2] = { CX_L, CX_R }; for (int i = 0; i < 2; i++) { // sleeping curve display.fillCircle(cx[i], EYE_CY - 2, 18, ON); display.fillCircle(cx[i], EYE_CY - 5, 18, OFF); } int phase = (now / 600) % 3; display.setTextColor(ON); display.setTextSize(1 + phase); display.setCursor(104 - phase * 6, 12 - phase * 4); display.print(phase == 0 ? F("z") : F("Z")); } void updateLook(unsigned long now) { if (now >= nextLook) { if (random(4) == 0) { tgtX = 0; tgtY = 0; } else { tgtX = random(-100, 101) / 100.0; tgtY = random(-70, 71) / 100.0; } nextLook = now + random(1200, 3500); } curX += (tgtX - curX) * 0.3; curY += (tgtY - curY) * 0.3; } void setMood(Mood m) { unsigned long now = millis(); if (mood == SLEEP && m != SLEEP) display.dim(false); if (m == SLEEP && mood != SLEEP) display.dim(true); if (m == NORMAL) { blinking = false; nextBlink = now + random(1500, 4000); } mood = m; moodStart = now; } bool touchPressed(unsigned long now) { bool reading = (digitalRead(TOUCH_PIN) == HIGH); if (reading != lastReading) { lastChange = now; lastReading = reading; } if ((now - lastChange) >= DEBOUNCE_MS && reading != stableState) { stableState = reading; if (stableState) return true; } return false; } void onTap(unsigned long now) { lastActivity = now; transUntil = now + TRANSITION_MS; // short blink while the mood changes if (mood == SLEEP) { moodIndex = 0; setMood(NORMAL); return; } moodIndex = (moodIndex + 1) % ORDER_N; setMood(ORDER[moodIndex]); } void setup() { Serial.begin(9600); pinMode(TOUCH_PIN, INPUT); randomSeed(analogRead(2)); // A0 (GPIO2) floating noise seeds the random numbers if (!display.begin(SSD1309_SWITCHCAPVCC, SCREEN_ADDRESS)) { Serial.println(F("SSD1309 allocation failed")); for (;;); } Wire.setClock(400000); // fast I2C at 400 kHz display.clearDisplay(); display.display(); unsigned long now = millis(); lastActivity = now; nextBlink = now + 2500; nextLook = now + 1500; } void loop() { unsigned long now = millis(); if (touchPressed(now)) onTap(now); if (stableState) lastActivity = now; unsigned long idle = now - lastActivity; if (mood != SLEEP) { if (idle >= SLEEP_MS) { moodIndex = 0; setMood(SLEEP); } else if (idle >= RETURN_NORMAL_MS && mood != NORMAL) { moodIndex = 0; setMood(NORMAL); } } if (now - lastFrame < FRAME_MS) return; lastFrame = now; if (mood == NORMAL || mood == SLEEPY) updateLook(now); display.clearDisplay(); if (now < transUntil) { drawClosedEye(CX_L, EYE_CY); drawClosedEye(CX_R, EYE_CY); } else { switch (mood) { case NORMAL: drawNormal(now); break; case HAPPY: drawHappy(now); break; case WINK: drawWink(now); break; case ANGRY: drawAngry(); break; case SAD: drawSad(); break; case CRY: drawCry(now); break; case STAR: drawStarEyes(now); break; case DIZZY: drawDizzy(now); break; case LOVE: drawLove(now); break; case SURPRISED: drawSurprised(); break; case SUSPICIOUS: drawSuspicious(now); break; case ROLL: drawRoll(now); break; case SLEEPY: drawSleepy(now); break; case SLEEP: drawSleep(now); break; } } display.display(); }

Good to know: the demo reads analogRead(2) on A0 (GPIO2) only to seed the random number generator. It does not need analogSetAttenuation.

ESP32 Uno vs UNO R4 for This Project

By the end of this part, you know what changes when you move this OLED project from the UNO R4 to the ESP32 Uno.

Item Arduino UNO R4 ESP32 Uno
Logic voltage 5V 3.3V
I2C pins A4 and A5 SDA (GPIO21) and SCL (GPIO22)
OLED libraries DIYables_OLED_SSD1309 and Adafruit GFX same libraries, no change
Touch sensor in the demo D13 D13 (GPIO18)
Random seed in the demo analogRead(A0) analogRead(2) on A0 (GPIO2)
Arduino IDE board Arduino UNO R4 WiFi ESP32 Dev Module

The OLED sketches run as they are. Only the two I2C wires move to the SDA and SCL header pins. Check that your OLED keeps SDA and SCL at 3.3V.

Go Wireless: Send Messages to the OLED

By the end of this part, you have an idea for showing phone text on this screen.

The ESP32 Uno has WiFi and Bluetooth, so the OLED can show messages from your phone. Type a line in a chat page, and your sketch prints it on the display with the same println() calls you used above.

Try These Next

  • Print a live sensor value on the OLED and update it every second.
  • Show a centered title and a small clock line.
  • Build your own bitmap logo and scroll it across the screen.
  • Add the touch sensor from the demo and change screens with each tap.

Troubleshooting

Problem Possible Cause Solution
No COM port in Arduino IDE CH340 driver missing or a charge-only USB cable Install the CH340 driver and use a data USB Type-C cable
Upload fails with "Failed to connect" or "Waiting for download" Board not in upload mode Hold the BOOT button when the upload starts, then release it
Display stays black Wires on A4 and A5, or a loose jumper Move SDA to the SDA (GPIO21) header and SCL to the SCL (GPIO22) header, then recheck VCC and GND
Serial Monitor prints the allocation error The OLED does not answer at the address in the sketch Scan the bus, then set SCREEN_ADDRESS to the address you see (0x3C or 0x3D)
Compile error about a missing header One of the two libraries is not installed Add DIYables_OLED_SSD1309 and Adafruit GFX in the Library Manager
Picture is garbled or shifted Module uses another chip such as SH1106 Check the chip on your module and use a matching library
Picture flickers or the board reboots Poor USB power or long thin jumpers Try a stronger USB port, a shorter cable and shorter I2C wires
Nothing changes after drawing display() is never called Call oled.display() after your drawing code
Strange characters in Serial Monitor Wrong baud rate Set the Serial Monitor to 9600 baud
Robot eyes demo does not react to touch Touch sensor wired to the wrong pin Connect the TTP223 I/O pin to D13 (GPIO18) and power it from 3.3V

I2C address scanner

Upload this sketch to find the real address of your OLED, then open the Serial Monitor at 9600 baud.

// I2C address scanner - lists every device it finds // ESP32 Uno: SDA = GPIO21, SCL = GPIO22 (default Wire pins) #include <Wire.h> void setup() { Wire.begin(); Serial.begin(9600); Serial.println("I2C Scanner"); } void loop() { byte error, address; int nDevices = 0; Serial.println("Scanning..."); for (address = 1; address < 127; address++) { Wire.beginTransmission(address); error = Wire.endTransmission(); if (error == 0) { Serial.print("I2C device found at address 0x"); if (address < 16) Serial.print("0"); Serial.print(address, HEX); Serial.println(" !"); nDevices++; } else if (error == 4) { Serial.print("Unknown error at address 0x"); if (address < 16) Serial.print("0"); Serial.println(address, HEX); } } if (nDevices == 0) Serial.println("No I2C devices found"); else Serial.println("done"); delay(5000); }

When the OLED is found, you see:

∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32 Dev Module
Newbiely.ino
···
8 Serial.println("Hello World!");
Output
Serial Monitor
Message (Enter to send message to 'ESP32 Dev Module' on 'COM15')
New Line
9600 baud
Scanning... I2C device found at address 0x3C ! done
Ln 11, Col 1
ESP32 Dev Module on COM15
2

FAQ

Which ESP32 Uno pins connect to an SSD1309 I2C OLED?

Plug the two I2C wires into the SDA (GPIO21) and SCL (GPIO22) headers. A4 and A5 will not work for I2C here. The default Wire.begin() already uses these two pins.

Can I power the 2.42 inch OLED from 5V?

Yes, most modules have a regulator and run from 5V. The point to check is the SDA and SCL level, because ESP32 Uno pins are not 5V tolerant. If your module pulls them to 5V, use 3.3V power or an I2C level shifter.

Will the UNO R4 OLED code work on the ESP32 Uno?

Yes. The DIYables_OLED_SSD1309 and Adafruit GFX libraries work on both boards. Only the wiring changes, since I2C moves from A4 and A5 to the SDA and SCL header pins.

Why does my SSD1309 OLED show nothing?

First check the wires and the I2C address. Then make sure your code calls display(), because drawing only changes the memory buffer. Also confirm that your module uses the SSD1309 chip and not the SH1106.

Can I change the OLED color in code?

No. The glass of the panel sets the color, so code can only turn pixels on or off.

How do I find the I2C address of my OLED?

Run the scanner sketch in the Troubleshooting section. Most SSD1309 modules use 0x3C, and some use 0x3D.

Can I share the I2C bus with other sensors?

Yes. I2C is a shared bus, so more modules can sit on the same two wires if their addresses differ. The scanner lists every address it hears.

Can I show a web page message on the OLED with the ESP32 Uno?

Yes. The ESP32 Uno has WiFi and Bluetooth, so a phone can send text that your sketch prints with println(). See the Go Wireless section for ready tutorials.

Do I need the CH340 driver for this board?

Often yes on Windows. If no COM port shows up, install the CH340 driver and try a data-capable USB Type-C cable.

Can I use an OLED shield or other Uno shields on the ESP32 Uno?

The header has the Uno shape, so many shields fit, but check the GPIO map. Shields that use A4 and A5 for I2C will not work, because those pins are not I2C on this board.

What if my project needs more pins than the Uno headers give?

The ESP32 Uno has a few extra holes near the power header. GPIO32 and GPIO33 offer I/O, PWM and analog on ADC1, so they also work while WiFi is on. GPIO15 offers I/O, PWM and analog on ADC2. Add pin headers by soldering, then use the plain GPIO number in your sketch. For an OLED project, GPIO32 and GPIO33 suit extra buttons or an analog sensor. Watch out for two things. First, GPIO15 and GPIO0 (the IO0 pin at the unused Uno NC position) are boot strapping pins, and a wrong connection can stop the board from booting or uploading code, so use GPIO32 and GPIO33 first. Second, never use the six holes under the digital header (SD0-SD3, CMD, CLK = GPIO6-GPIO11). They are wired to the flash chip, and using them crashes the board.

※ OUR MESSAGES

  • As freelancers, We are AVAILABLE for HIRE. See how to outsource your project to us
  • Please feel free to share the link of this tutorial. However, Please do not use our content on any other websites. We invested a lot of effort and time to create the content, please respect our work!