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:

Installing the Library
The library used to connect the OLED display is:

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.

After successful installation, open the example:

Running the Example
Modify this part of the example:

Change it to:

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:

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

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:

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: