How to Build a Mini OLED Character with the XIAO ESP32-C6
In this guide, we will build a cute mini OLED character that changes its facial expressions. Even if you have never assembled an electronic circuit before, you will learn where every wire goes, which software to install, and how to upload the code to the board.
1. Materials
- XIAO ESP32-C6 Development Board — The brain of the project.
- 1.3-inch 128×64 I2C OLED Display — Displays the character’s face.
- 3.7 V 180 mAh LiPo Battery — Provides portable power.
- 2-position, 3-pin Slide Switch — Turns the device on and off.
- Thin hookup wire, soldering iron, solder, a USB-C data cable, and an optional 3D-printed enclosure.
2. How does the system work?
The XIAO ESP32-C6 calculates the face that should be drawn and sends it to the OLED through a two-wire communication method called I2C. SDA is the data wire and carries the drawing information. SCL is the clock wire and controls the timing of the data. VCC and GND provide electrical power to the display.
3. Wiring diagram

Red indicates power, black indicates ground/negative, green indicates data, and blue indicates the clock connection.
| From | To | Purpose |
|---|---|---|
| OLED VCC | XIAO 3V3 | Supplies a safe 3.3 V to the display. |
| OLED GND | XIAO GND | Provides the common ground connection. |
| OLED SDA | XIAO D4 / SDA | Sends display data. |
| OLED SCL | XIAO D5 / SCL | Provides the I2C clock signal. |
| Battery positive (+) | Middle switch pin | Carries battery power to the switch. |
| One outer switch pin | XIAO BAT+ | Connects the battery when the switch is on. |
| Battery negative (−) | XIAO BAT− | Provides the battery’s negative connection. |
Connecting the three-pin slide switch

The switch has three pins, but we only use two: the middle pin and one of the outer pins. Leave the other outer pin unconnected. If the on/off direction feels reversed, move the wire from the selected outer pin to the other outer pin.
Recommended soldering order
- Disconnect the USB cable and battery.
- Prepare the OLED’s VCC, GND, SDA, and SCL wires.
- Solder these four wires to the XIAO according to the table.
- Solder the battery’s positive wire to the middle pin of the switch.
- Connect one outer switch pin to the BAT+ pad on the XIAO.
- Connect the battery’s negative wire to the XIAO BAT− pad.
- Cover exposed metal with heat-shrink tubing or suitable insulation.
- Check for short circuits before connecting the battery.
4. Software: Arduino IDE
Arduino IDE is the free program we will use to send the code to the XIAO board. Download and install the correct version for your operating system from the Arduino IDE download page.
Installing ESP32 board support
- Open Arduino IDE.
- Go to File > Preferences.
- Add this address to Additional Boards Manager URLs:
https://espressif.github.io/arduino-esp32/package_esp32_index.json - Open Boards Manager from the left sidebar.
- Search for esp32 and install version 3.0.0 or newer of the package by Espressif Systems.
- Select Tools > Board > ESP32 Arduino > XIAO_ESP32C6.
- Connect the board with a USB-C data cable and select its port from Tools > Port.
Installing the U8g2 display library
- Click the Library Manager icon on the left side of Arduino IDE.
- Type U8g2 in the search box.
- Install the U8g2 library by Oliver Kraus.
5. Run a simple display test first
Testing the display and wires before uploading the full animation makes troubleshooting much easier. In Arduino IDE, select File > New Sketch, delete the example code, and paste the following:
#include <Wire.h>
#include <U8g2lib.h>
// Try the SH1106 driver first for this display.
U8G2_SH1106_128X64_NONAME_F_HW_I2C display(U8G2_R0, U8X8_PIN_NONE);
void setup() {
Wire.begin(D4, D5); // D4 = SDA, D5 = SCL
display.begin();
display.clearBuffer();
display.setFont(u8g2_font_ncenB14_tr);
display.drawStr(25, 38, "Hello!");
display.sendBuffer();
}
void loop() {
// Nothing is required here because the text remains on the display.
}Click the check-mark button to verify the code, then click the arrow-shaped Upload button. If “Hello!” appears, the wiring is correct.
U8G2_SH1106_128X64_NONAME_F_HW_I2C with U8G2_SSD1306_128X64_NONAME_F_HW_I2C and upload the code again.6. Full animation code
The following code automatically searches for address 0x3C or 0x3D. It draws the character, makes it blink and look from side to side, and changes its facial expression at random intervals.
#include <Wire.h>
#include <U8g2lib.h>
// Use this line if your display uses the SH1106 controller.
U8G2_SH1106_128X64_NONAME_F_HW_I2C display(U8G2_R0, U8X8_PIN_NONE);
// If SH1106 produces no image, comment out the line above and enable this one:
// U8G2_SSD1306_128X64_NONAME_F_HW_I2C display(U8G2_R0, U8X8_PIN_NONE);
enum Emotion { NEUTRAL, HAPPY, SAD, SURPRISED, SLEEPY, ANGRY };
Emotion emotion = NEUTRAL;
int lookX = 0;
int targetLookX = 0;
bool eyesClosed = false;
unsigned long nextBlink = 0;
unsigned long openEyesAt = 0;
unsigned long nextAction = 0;
byte findOledAddress() {
byte possibleAddresses[] = {0x3C, 0x3D};
for (byte i = 0; i < 2; i++) {
Wire.beginTransmission(possibleAddresses[i]);
if (Wire.endTransmission() == 0) return possibleAddresses[i];
}
return 0;
}
void drawEye(int centerX, int centerY, bool leftEye) {
if (eyesClosed || emotion == SLEEPY) {
display.drawLine(centerX - 9, centerY, centerX + 9, centerY);
display.drawLine(centerX - 7, centerY + 1, centerX + 7, centerY + 1);
return;
}
int width = (emotion == SURPRISED) ? 22 : 18;
int height = (emotion == SURPRISED) ? 30 : 25;
int x = centerX - width / 2 + lookX;
int y = centerY - height / 2;
display.drawRBox(x, y, width, height, 6);
if (emotion == ANGRY) {
display.setDrawColor(0);
if (leftEye) display.drawTriangle(x - 2, y - 1, x + width + 2, y - 1, x + width + 2, y + 8);
else display.drawTriangle(x - 2, y - 1, x + width + 2, y - 1, x - 2, y + 8);
display.setDrawColor(1);
}
}
void drawMouth() {
switch (emotion) {
case HAPPY:
display.drawLine(51, 46, 58, 52); display.drawLine(58, 52, 64, 54);
display.drawLine(64, 54, 70, 52); display.drawLine(70, 52, 77, 46);
break;
case SAD:
display.drawLine(51, 55, 58, 49); display.drawLine(58, 49, 64, 47);
display.drawLine(64, 47, 70, 49); display.drawLine(70, 49, 77, 55);
break;
case SURPRISED: display.drawCircle(64, 51, 7); break;
case SLEEPY: display.drawLine(58, 52, 70, 52); break;
case ANGRY:
display.drawLine(54, 55, 64, 50); display.drawLine(64, 50, 74, 55);
break;
default: display.drawLine(58, 52, 70, 52); break;
}
}
void drawFace() {
display.clearBuffer();
drawEye(38, 29, true);
drawEye(90, 29, false);
drawMouth();
display.sendBuffer();
}
void setup() {
Serial.begin(115200);
delay(300);
Wire.begin(D4, D5); // XIAO ESP32-C6: D4 = SDA, D5 = SCL
byte address = findOledAddress();
if (address == 0) {
Serial.println("OLED not found. Check the wiring.");
while (true) delay(1000);
}
display.setI2CAddress(address << 1);
display.begin();
randomSeed(micros());
nextBlink = millis() + random(2000, 6000);
nextAction = millis() + random(1500, 4000);
Serial.print("OLED found at address 0x");
Serial.println(address, HEX);
}
void loop() {
unsigned long now = millis();
if (!eyesClosed && now >= nextBlink) {
eyesClosed = true;
openEyesAt = now + 150;
}
if (eyesClosed && now >= openEyesAt) {
eyesClosed = false;
nextBlink = now + random(2000, 6000);
}
if (now >= nextAction) {
if (random(100) < 40) targetLookX = random(-5, 6);
else {
emotion = (Emotion)random(0, 6);
targetLookX = 0;
}
nextAction = now + random(1500, 4000);
}
if (lookX < targetLookX) lookX++;
if (lookX > targetLookX) lookX--;
drawFace();
delay(33); // Approximately 30 frames per second
}7. Simple explanation of the code
| Code section | What does it do? |
|---|---|
#include lines | Enable I2C communication through Wire and OLED drawing through U8g2. |
enum Emotion | Lists the facial expressions the character can display. |
findOledAddress() | Automatically detects whether the display uses address 0x3C or 0x3D. |
drawEye() | Draws each eye and controls its size, position, and closed state. |
drawMouth() | Draws a different mouth shape for the selected emotion. |
setup() | Runs once when the board starts and prepares the display. |
loop() | Runs repeatedly and manages blinking, looking, and expression changes. |
delay(33) | Produces approximately 30 frames per second for smooth animation. |
8. Uploading the code
- Open a new sketch in Arduino IDE and paste the full code.
- Make sure XIAO_ESP32C6 is selected under Tools > Board.
- Select the board’s connection under Tools > Port.
- Click the check mark to compile the code.
- Click the arrow button to upload it.
- The display should come to life a few seconds after the upload finishes.
If the board does not appear, your USB-C cable may be a charging-only cable. Try a cable that supports data. If uploading does not start, hold the BOOT button, press RESET once, release BOOT, and try again.
9. Troubleshooting
| Problem | What to check |
|---|---|
| The display is completely off | Make sure VCC is connected to 3V3 and GND is connected to GND. |
| The display is powered but blank | Check SDA/D4 and SCL/D5; try the SSD1306 constructor instead of SH1106. |
| “OLED not found” appears | Open Serial Monitor at 115200 baud and inspect the solder joints for breaks or short circuits. |
| The code does not compile | Confirm that U8g2 and a current ESP32 board package are installed. |
| The board is missing from the port list | Try another USB data cable or USB port. |
| The project does not run on battery | Check that the middle and one outer switch pin are used and verify BAT+ and BAT− polarity. |
| The battery does not charge | Move the switch to the on position while USB-C is connected. |
| The board continuously restarts | Charge the battery and make sure no solder joints touch each other. |
10. Using a 3D-printed enclosure
The project that inspired this guide provides 3D model files on GitHub. Measure your components before printing. The OLED board is approximately 35.5 × 33.5 mm, the battery is approximately 24 × 15.5 × 6.7 mm, and the selected switch body is approximately 10.5 × 5.5 mm. You may need to enlarge the original model’s switch opening.
Conclusion
You now have a portable mini character powered by the XIAO ESP32-C6, an I2C OLED display, a small LiPo battery, and a slide switch. Running the simple “Hello!” test before uploading the full animation is the easiest way to find wiring problems.
Original project video: Building a Tiny OLED Keychain
Example source code: TechTalkies / 123_Keychain
Official XIAO ESP32-C6 getting-started guide: Seeed Studio Wiki
.png)






