Getting Started with ESP32 and Arduino (Part 2): Connecting an OLED Display

Wiring

Let’s first take a look at the wiring.

I am using a 0.91-inch 4-pin OLED display.

OLED Pin ESP32-S3 Pin
GND GND
VCC 3.3V
SCL 0
SDA 1

After completing the wiring, it looks like this:

Getting Started with ESP32 and Arduino (Part 2): Connecting an OLED Display

Installing the Library

The library used to connect the OLED display is:

Getting Started with ESP32 and Arduino (Part 2): Connecting an OLED Display
image-20250409152629407

GitHub link: https://github.com/ThingPulse/esp8266-oled-ssd1306

Description

This is a driver for 128×64, 128×32, 64×48 pixel OLED displays based on SSD1306 and SH1106 for ESP8266/ESP32.

This driver supports SSD1306 and SH1106 128×64, 128×32, 64×48, and 64×32 OLED displays for Arduino/ESP8266 & ESP32 and mbed-os platforms. It can be used with I2C or SPI versions of the display.

Getting Started with ESP32 and Arduino (Part 2): Connecting an OLED Display
image-20250409153031491

After successful installation, open the example:

Getting Started with ESP32 and Arduino (Part 2): Connecting an OLED Display

Running the Example

Modify this part of the example:

Getting Started with ESP32 and Arduino (Part 2): Connecting an OLED Display
image-20250409160043424

Change it to:

Getting Started with ESP32 and Arduino (Part 2): Connecting an OLED Display
image-20250409160127366

This means changing to the pins connected to SDA and SCL.

Now compile and upload to see the effect:

Displaying English

Now that the example runs successfully, you can learn from the example.

First, learn to display English.

Create a new project:

#include <Wire.h>               // Only needed for Arduino 1.6.5 and earlier
#include "SSD1306Wire.h" 

// Initialize the OLED display using Arduino Wire:
SSD1306Wire display(0x3c, 1, 0, GEOMETRY_128_32); 

void setup() {
// put your setup code here, to run once:
  Serial.begin(115200);

// Initialising the UI will init the display too.
  display.init();

  display.flipScreenVertically();
  display.setFont(ArialMT_Plain_24);
  display.drawString(0, 0, "hello world");
  display.display(); 
}

void loop() {

}

Effect:

Getting Started with ESP32 and Arduino (Part 2): Connecting an OLED Display

For example, now I want to display from 1% to 100% in a loop after showing “hello world”.

The code is as follows:

#include <Wire.h>               // Only needed for Arduino 1.5 and earlier
#include "SSD1306Wire.h" 
// Initialize the OLED display
SSD1306Wire display(0x3c, 1, 0, GEOMETRY_128_32); 
bool showHelloWorld = true;     // Initially display "hello world"
int percentage = 1;             // Start from 1%
unsigned long lastUpdate = 0;   // Record the last update time
const int updateInterval = 200; // Update interval (milliseconds)
void setup() {
  Serial.begin(115200);
  display.init();
  display.flipScreenVertically();
  display.setFont(ArialMT_Plain_24);

// Initially display "hello world"
  display.clear();
  display.drawString(0, 0, "hello world");
  display.display();

  lastUpdate = millis(); // Record initial time
}
void loop() {
unsigned long currentTime = millis();

// If current time - last update time >= interval time, update display
if (currentTime - lastUpdate >= updateInterval) {
    lastUpdate = currentTime;
    
    if (showHelloWorld) {
      // After displaying "hello world", switch to percentage display
      showHelloWorld = false;
    } else {
      // Update percentage (1% ~ 100%)
      display.clear();
      display.drawString(0, 0, String(percentage) + "%");
      display.display();
      
      percentage++;
      if (percentage > 100) {
        percentage = 1; // Loop display
      }
    }
  }
}

Effect:

Scrolling Display

The code is as follows:

#include <Wire.h>
#include "SSD1306Wire.h"
SSD1306Wire display(0x3c, 1, 0, GEOMETRY_128_32); 
String longText = "This is a very long text that cannot fit on the screen at once. It will scroll horizontally.";
int textPosition = 0;  // Current scroll position
unsigned long lastScrollTime = 0;
const int scrollDelay = 150;  // Scroll speed (milliseconds)
void setup() {
  Serial.begin(115200);
  display.init();
  display.flipScreenVertically();
  display.setFont(ArialMT_Plain_10);  // Use small font to display more content
}
void loop() {
if (millis() - lastScrollTime >= scrollDelay) {
    lastScrollTime = millis();
    
    display.clear();
    display.drawString(-textPosition, 0, longText);  // Negative coordinates for left scrolling
    display.display();
    
    textPosition++;  // Move 1 pixel each time
    
    // If the text completely scrolls out of the screen, reset position
    if (textPosition > display.getStringWidth(longText)) {
      textPosition = 0;
    }
  }
}

Effect:

Displaying Chinese

To display Chinese, you need to install an additional library: U8g2lib

Getting Started with ESP32 and Arduino (Part 2): Connecting an OLED Display
image-20250409164228239

The code is:

#include <U8g2lib.h>
U8G2_SSD1306_128X32_UNIVISION_F_SW_I2C u8g2(U8G2_R0, /*SCL=*/0, /*SDA=*/1, /*RESET=*/U8X8_PIN_NONE);
void setup() {
  Serial.begin(115200);
  u8g2.begin();
  u8g2.enableUTF8Print();
// Use a more compact font
  u8g2.setFont(u8g2_font_unifont_t_chinese2);
// Get the actual height of the font and calculate safe Y coordinate
uint8_t fontHeight = u8g2.getMaxCharHeight();
uint8_t yPos = 32 - fontHeight; // Ensure the bottom does not exceed the screen
  Serial.print("Font Height: ");
  Serial.println(fontHeight);
  Serial.print("Y Position: ");
  Serial.println(yPos);
  u8g2.clearBuffer();
  u8g2.setCursor(0, yPos);
  u8g2.print("你好世界");
  u8g2.sendBuffer();

  delay(100);
}
void loop() {}

Effect:

Getting Started with ESP32 and Arduino (Part 2): Connecting an OLED Display

Horizontal Scrolling Display of Chinese

The code is as follows:

#include <U8g2lib.h>
U8G2_SSD1306_128X32_UNIVISION_F_SW_I2C u8g2(U8G2_R0, /*SCL=*/0, /*SDA=*/1, /*RESET=*/U8X8_PIN_NONE);
// Long Chinese text to display
const char *longText = "这是一个很长很长的中文文本,它将会在屏幕上水平滚动显示。";
// Scrolling related variables
int16_t textWidth;        // Actual width of the text
int16_t scrollPosition = 0; // Current scroll position
unsigned long lastScrollTime = 0; // Last scroll time
const unsigned long scrollInterval = 5; // Scroll interval time (milliseconds)
uint8_t fontHeight;          // Font height
uint8_t yPos;              // Y coordinate
void setup() {
  Serial.begin(115200);
  u8g2.begin();
  u8g2.enableUTF8Print();
// Use a more compact font suitable for Chinese display
  u8g2.setFont(u8g2_font_unifont_t_chinese2);
// Get the actual height of the font and calculate safe Y coordinate
  fontHeight = u8g2.getMaxCharHeight();
  yPos = 32 - fontHeight; // Ensure the bottom does not exceed the screen
  Serial.print("Font Height: ");
  Serial.println(fontHeight);
// Calculate the actual width of the text
  textWidth = u8g2.getUTF8Width(longText);
  Serial.print("Text Width: ");
  Serial.println(textWidth);
}
void loop() {
// Get current time
unsigned long currentTime = millis();
// Check if scrolling is needed
if (currentTime - lastScrollTime >= scrollInterval) {
    lastScrollTime = currentTime;
    // Update scroll position
    scrollPosition += 5;
    // If scrolled to the end of the text, reset scroll position
    if (scrollPosition > textWidth) {
      scrollPosition = 0;
    }
    // Redraw the screen
    drawScrollingText();
  }
}
// Function to draw scrolling text
void drawScrollingText() {
  u8g2.clearBuffer();
// Calculate the starting X coordinate for drawing text (negative means part of the text is off-screen)
int16_t xPos = 0 - scrollPosition;
// Draw text
  u8g2.setCursor(xPos, yPos);
  u8g2.print(longText);
  u8g2.sendBuffer();
}

Effect is as follows:

The effect does not seem very good.

Segmented Display of Chinese

The code is as follows:

#include <U8g2lib.h>

// Define screen object (adjust constructor parameters based on the actual screen and interface used)
U8G2_SSD1306_128X32_UNIVISION_F_SW_I2C u8g2(U8G2_R0, /*SCL=*/0, /*SDA=*/1, /*RESET=*/U8X8_PIN_NONE);

// Long Chinese text to display
const char *longText = "这是一个很长很长的中文文本,它将会在屏幕上分段显示。";

// Global variables related to display
uint8_t fontHeight;         // Font height
uint8_t yPos;               // Display position on Y-axis
const uint16_t screenWidth = 128; // OLED screen width

// Control segment display time (milliseconds)
const unsigned long segmentDisplayTime = 2000;  // Display each segment for 2 seconds
unsigned long lastSegmentChange = 0;            // Last time segment was changed

// Record the starting byte index of the current segment in the text
int currentSegmentStart = 0;

// Function to measure the number of bytes occupied by a UTF-8 character
int utf8CharBytes(char c) {
if ((c & 0x80) == 0) return 1;          // ASCII
else if ((c & 0xE0) == 0xC0) return 2;    // 2 bytes
else if ((c & 0xF0) == 0xE0) return 3;    // 3 bytes
else if ((c & 0xF8) == 0xF0) return 4;    // 4 bytes
return 1; // Default return 1
}

// Calculate the starting position of the next segment based on the current starting position and screen width
int getNextSegmentStart(const char* text, int startIndex, int maxWidth) {
int bytePos = startIndex;
int segmentWidth = 0;
int lastValidPos = bytePos;

// Accumulate characters until exceeding screen width
while (text[bytePos] != '\0') {
    int charBytes = utf8CharBytes(text[bytePos]);
    char buffer[10] = {0};  // Temporary storage for a single character, supports up to 4-byte encoding
    for (int i = 0; i < charBytes; i++) {
      buffer[i] = text[bytePos + i];
    }
    int charWidth = u8g2.getUTF8Width(buffer);
    
    // If adding the current character exceeds the maximum width, exit the loop
    if (segmentWidth + charWidth > maxWidth) {
      break;
    }
    segmentWidth += charWidth;
    lastValidPos = bytePos + charBytes;
    bytePos += charBytes;
  }

// If reached the end of the text, start from 0 next time
if (text[bytePos] == '\0') {
    return 0;
  }
return lastValidPos;
}

// Extract and display the current segment of characters on the screen
void drawSegment(const char* text, int startIndex) {
  u8g2.clearBuffer();
char segment[256] = {0};  // Store the current segment string, adjust length based on text length
int bytePos = startIndex;
int segmentWidth = 0;
int segIndex = 0;

// Read character by character until the accumulated width exceeds screen width or encounter string terminator
while (text[bytePos] != '\0') {
    int charBytes = utf8CharBytes(text[bytePos]);
    char temp[10] = {0};
    for (int i = 0; i < charBytes; i++) {
      temp[i] = text[bytePos + i];
    }
    int charWidth = u8g2.getUTF8Width(temp);
    
    // Stop if reached maximum width
    if (segmentWidth + charWidth > screenWidth) {
      break;
    }
    
    // Copy the current character to segment
    for (int i = 0; i < charBytes; i++) {
      segment[segIndex++] = text[bytePos + i];
    }
    segmentWidth += charWidth;
    bytePos += charBytes;
  }
  segment[segIndex] = '\0';  // Add end character

// Display the segment text on the screen, keeping the Y coordinate calculated earlier
  u8g2.setCursor(0, yPos);
  u8g2.print(segment);
  u8g2.sendBuffer();
}

void setup() {
  Serial.begin(115200);
  u8g2.begin();
  u8g2.enableUTF8Print();

// Set font that supports Chinese
  u8g2.setFont(u8g2_font_wqy12_t_gb2312);

// Get font height, calculate Y coordinate (ensure text does not exceed screen)
  fontHeight = u8g2.getMaxCharHeight();
//yPos = 32 - fontHeight; // OLED resolution 128x32
  yPos = (32 + fontHeight) / 2;
  Serial.print("Font Height: ");
  Serial.println(fontHeight);

// If you need to display part of the text directly for the first time, you can call drawSegment() in setup.
  drawSegment(longText, currentSegmentStart);
}

void loop() {
unsigned long currentTime = millis();

// Check if it's time to switch segments
if (currentTime - lastSegmentChange >= segmentDisplayTime) {
    lastSegmentChange = currentTime;
    
    // Calculate the starting position of the next segment based on the current segment
    currentSegmentStart = getNextSegmentStart(longText, currentSegmentStart, screenWidth);
  }

  drawSegment(longText, currentSegmentStart);
}

Effect:

Leave a Comment