Trouble Getting SSD1306 (128×32) Working on ESP32-S3 with PlatformIO & U8g2

Hello everyone,

I’m struggling to get a simple “Hello ESP32-S3!” sketch working on my ESP32-S3 Dev Module when using PlatformIO. In Arduino IDE the exact same code drives my SSD1306 (128×32) OLED display over I²C without issues. However, in PlatformIO, nothing ever appears on the screen—no error messages, just a blank display. I’m hoping someone can spot what I’m missing or suggest debugging steps. Below is a complete summary of my setup, wiring, code, platformio.ini, and troubleshooting steps so far.

1) Hardware Setup

  • Board: ESP32-S3 Dev Module (the built-in USB port is connected to my PC)
  • Display: SSD1306 128×32 OLED (I²C interface)
    • VCC → 3.3 V on ESP32-S3
    • GND → GND
    • SDA → GPIO 4
    • SCL → GPIO 5
    • (The module has onboard pull-ups on SDA/SCL; if yours does not, you’ll need two 4.7 kΩ–10 kΩ pull-ups to 3.3 V.)

I verified that wiring is correct: SDA and SCL are not swapped, and power/ground are solid.


2) platformio.ini

This is my entire PlatformIO configuration. I deliberately removed any Adafruit SSD1306/GFX libraries to avoid conflicts and only included U8g2:

[env:esp32s3]
platform    = espressif32
board       = esp32-s3-devkitc-1
framework   = arduino
monitor_speed = 115200

lib_deps =
    olikraus/U8g2@^2.34.22
  • board = esp32-s3-devkitc-1 matches the “ESP32-S3 Dev Module” definition.
  • lib_deps installs the U8g2 library (version ≥ 2.34.22).

I ran pio run --target clean and then pio run to ensure everything was rebuilt from scratch.


3) Code in src/main.cpp

Below is the exact sketch I’m using. Note that it drives the OLED over hardware I²C on GPIO 4/5. In Arduino IDE this code displays “Hello ESP32-S3!” just fine. In PlatformIO it compiles and uploads with no errors, but the OLED remains blank.

#include <Wire.h>
#include <U8g2lib.h>

// ----------------------------------------------------------------
// 1) Configure U8g2 for SSD1306 128×32 over HW-I2C
//    SDA = GPIO 4, SCL = GPIO 5
// ----------------------------------------------------------------
U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(
  U8G2_R0,               // no rotation
  /* reset=*/ U8X8_PIN_NONE,
  /* clock=SCL=*/ 5,     // GPIO 5 → SCL
  /* data=SDA=*/ 4       // GPIO 4 → SDA
);

void setup() {
  Serial.begin(115200);
  delay(500);

  // ----------------------------------------------------------------
  // 2) Initialize I2C bus on SDA=4, SCL=5
  //    (U8g2 internally calls Wire.begin(), but we force it here)
  // ----------------------------------------------------------------
  Wire.begin(4, 5);
  // Optional: Wire.setClock(100000); // force 100 kHz if pull-ups are weak

  // ----------------------------------------------------------------
  // 3) Initialize SSD1306 @ I2C address 0x3C
  // ----------------------------------------------------------------
  if (!u8g2.begin()) {
    Serial.println("Error: SSD1306 not responding at 0x3C");
    while (true) {
      delay(100);
    }
  }

  // ----------------------------------------------------------------
  // 4) Draw “Hello ESP32-S3!” once
  // ----------------------------------------------------------------
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_ncenB08_tr);
  u8g2.drawStr(0, 16, "Hello ESP32-S3!");
  u8g2.sendBuffer();
}

void loop() {
  // Nothing to do in loop—content remains static
}
  • Wire.begin(4, 5) remaps the hardware I²C bus to pins 4 and 5.
  • After u8g2.begin(), the code clears the display buffer, sets a font, draws the string at coordinates (0, 16), and sends the buffer.

No compile errors occur, and the serial monitor prints nothing (because I have no debug prints except the potential initialization failure). The OLED remains black.


4) Troubleshooting Steps Already Taken

  1. Clean/Rebuild
  • Ran pio run --target cleanpio runpio run --target upload.
  • Confirmed upload succeeded with no errors or warnings.
  1. I²C Scanner Sketch
    I replaced main.cpp with a minimal I²C scanner to see if 0x3C is present on (4, 5):
#include <Wire.h>

void setup() {
  Serial.begin(115200);
  delay(500);
  Wire.begin(4, 5);
  Serial.println("I2C Scanner started on SDA=4, SCL=5...");

  for (uint8_t addr = 1; addr < 127; addr++) {
    Wire.beginTransmission(addr);
    if (Wire.endTransmission() == 0) {
      Serial.print("Device found at 0x");
      if (addr < 16) Serial.print('0');
      Serial.println(addr, HEX);
    }
  }
  Serial.println("Scan complete.");
}

void loop() {}
  • Result: No device found at 0x3C.
  • This indicates the SSD1306 is not responding on pins 4/5.
  1. Tried Alternative I²C Pins
    Since many ESP32-S3 boards route I²C by default to GPIO 21/22, I modified the code to:

cpp

CopiaModifica

Wire.begin(21, 22);
U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, 22, 21);

And physically wired SDA→21, SCL→22. Then reran the I²C scanner.

  • Result: Device found at 0x3C on (21, 22). OLED responds.
  • But with those changes, the “Hello ESP32-S3!” sketch using (21, 22) works correctly in PlatformIO.
  1. Conclusion from Scanning
  • The OLED module does not respond on (4, 5) despite having pull-ups.
  • Wiring to (21, 22) is the only way the display shows up in PlatformIO.
  1. Hypothesis
    For some reason, on my ESP32-S3 Dev Module in the PlatformIO environment, GPIO 4/5 are not enabled as a usable I²C bus by default, or the board definition conflicts with other peripherals. However, in Arduino IDE, it seemed that Wire.begin(4, 5) “just worked” (perhaps the Arduino core auto-enabled pull-ups differently). On PlatformIO, the preconfigured variant of Wire for esp32-s3-devkitc-1 appears to prefer GPIO 21/22 as the hardware I²C pins and might not multiplex 4/5 without additional configuration.

5) Current Working Configuration

To get the display working under PlatformIO, I had to switch to pins 21/22:

#include <Wire.h>
#include <U8g2lib.h>

// Use HW-I2C on SDA=21, SCL=22 instead of 4/5
U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(
  U8G2_R0,
  /* reset=*/ U8X8_PIN_NONE,
  /* clock=SCL=*/ 22,    // GPIO 22 → SCL
  /* data=SDA=*/ 21      // GPIO 21 → SDA
);

void setup() {
  Serial.begin(115200);
  delay(500);

  Wire.begin(21, 22);

  if (!u8g2.begin()) {
    Serial.println("Error: SSD1306 not responding @ 0x3C");
    while (true) { delay(100); }
  }

  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_ncenB08_tr);
  u8g2.drawStr(0, 16, "Hello ESP32-S3!");
  u8g2.sendBuffer();
}

void loop() {}

With this change, the display shows the text correctly. But I specifically want to understand:

  • Why Wire.begin(4, 5) worked in Arduino IDE (showed “Hello ESP32-S3!” on GPIO 4/5), but in PlatformIO with the same board definition it did not.
  • Ideally, I’d like to continue driving the screen on pins 4 and 5 (they’re more convenient on my custom wiring), so I need to either reconfigure PlatformIO’s I²C driver or understand why those pins are not available by default.

6) Questions & Requests for Help

  1. Is there a PlatformIO-specific setting (e.g. in platformio.ini or board JSON) that disables or remaps GPIO 4/5 from acting as an I²C bus on esp32-s3-devkitc-1?
  2. How can I force-enable I²C on pins 4/5 when using PlatformIO? (Perhaps a build flag or board override?)
  3. Why does the Arduino IDE core allow Wire.begin(4, 5) to work out of the box, whereas PlatformIO (with the same core version) does not?
  4. If switching to pins 21/22 is the only reliable solution in PlatformIO, what is the recommended “PlatformIO-style” way to document this in my project so others know to wire to 21/22 instead of 4/5?

Any insight into how PlatformIO’s espressif32 platform handles I²C remapping on the S3 would be greatly appreciated. Thank you in advance for your help!


(Feel free to ask for any additional details or board-revision info—I want to ensure my explanations are clear.)

That’s the question which should be solved first.
So let’s compare your ArduinoIDE setup to your PlatformIO setup.

1. Compare the used Espressif Arduino Framework.

  • Which version is installed in your ArduinoIDE?
  • What is the highest Espressif32 Platform installed in PlatformIO, because the highest version will be used when no version number is specified in the platformsetting in platformio.ini

Unfortunately the Espressif Platform version does not map directly to the Espressif Arduino version. This list will give you an overview about which platform version maps to which arduino version.

2. Compare the board settings

Does the ArduinoIDE board settings match the settings in your platformio.ini?
Please show your ArduinoIDE board settings (screenshot).


Arduino IDE

  • In Boards Manager I see:
    :arrow_forward: “esp32 by Espressif Systems” v 3.2.0
  • That core installs Arduino-ESPressif32 v 2.0.6 under the hood.

PlatformIO

  • By default PlatformIO will pick the latest espressif32 platform (currently 6.x).

  • To match exactly Arduino IDE’s v3.2.0, you must pin PlatformIO to espressif32@3.20006.221224.

    • That “3.20006.221224” maps to Arduino-ESPressif32 2.0.6 as used by Arduino IDE v3.2.0.

So in platformio.ini I wrote:

[env:esp32s3_devkitc_1_n16r8v]
platform    = espressif32@3.20006.221224
board       = esp32s3_devkitc_1_n16r8v
framework   = arduino
monitor_speed = 115200

; Lock U8g2 to the same version I use in Arduino IDE
lib_deps =
  olikraus/U8g2@2.35.30

That way PlatformIO is forced to install exactly the same “Arduino-ESPressif32 2.0.6” core that Arduino IDE’s esp32 3.2.0 provides.


Board settings

Arduino IDE board screen (for “ESP32S3 Dev Module” / “ESP32S3 DevKitC-1”):

  • Board: ESP32S3 Dev Module
  • Upload Speed: 921600
  • Flash Frequency: 80 MHz
  • Flash Mode: QIO
  • Flash Size: 4 MB (default)
  • Partition Scheme: Default
  • CPU Frequency: 240 MHz
  • Debug Level: None
  • PSRAM: Disabled
  • USB Mode: HWCDC (this pulls in -DARDUINO_USB_MODE=1)
  • LoopCore / EventCore: 1
  • Upload Port: your COM port

(You can see “FQBN=esp32:esp32:esp32s3:UploadSpeed=921600,USBMode=hwcdc,CDCOnBoot=…,CPUFreq=240,FlashMode=qio,FlashSize=4M,PartitionScheme=default…” at the top of Arduino’s verbose compile log.)

PlatformIO (in platformio.ini + custom JSON):

[env:esp32s3_devkitc_1_n16r8v]
platform    = espressif32@3.20006.221224
board       = esp32s3_devkitc_1_n16r8v
framework   = arduino
monitor_speed = 115200

lib_deps =
  olikraus/U8g2@2.35.30

I then placed my custom board JSON in boards/esp32s3_devkitc_1_n16r8v.json (relative to the project root). Its contents:

{
  "build": {
    "arduino": {
      "ldscript": "esp32s3_out.ld",
      "partitions": "default_16MB.csv"
    },
    "core": "esp32",
    "extra_flags": [
      "-DARDUINO_ESP32S3_DEV",
      "-DARDUINO_USB_MODE=1",
      "-DARDUINO_RUNNING_CORE=1",
      "-DARDUINO_EVENT_RUNNING_CORE=1"
    ],
    "f_cpu": "240000000L",
    "f_flash": "80000000L",
    "flash_mode": "qio",
    "hwids": [
      ["0x303A", "0x1001"]
    ],
    "mcu": "esp32s3",
    "variant": "esp32s3"
  },
  "connectivity": ["wifi"],
  "debug": {
    "default_tool": "esp-builtin",
    "onboard_tools": ["esp-builtin"],
    "openocd_target": "esp32s3.cfg"
  },
  "frameworks": ["arduino", "espidf"],
  "name": "esp32s3_devkitc_1_n16r8v",
  "upload": {
    "flash_size": "16MB",
    "maximum_ram_size": 327680,
    "maximum_size": 16777216,
    "require_upload_port": true,
    "speed": 460800
  },
  "url": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/esp32s3/user-guide-devkitc-1.html",
  "vendor": "Espressif"
}

(Note that I renamed the "name" field to exactly match my env name: esp32s3_devkitc_1_n16r8v.)

Because Arduino IDE’s default flash size is only 4 MB, I had to explicitly override it to 16 MB for the “N16R8V” module. In Arduino IDE that was done via “Tools → Flash Size → 16 MB (Quad SPI)”; here in PlatformIO it’s set in the JSON as "flash_size": "16MB".


Did you have this answer generated by an AI?
Unfortunately, that’s nonsense.

Rest of your post: Is this sitll a question? I don’t get it…