1602 LCD/Five volts on a 3.3 V bench/06. Wiring it to an ESP32
Five volts on a 3.3 V bench · 06 of 11

Wiring it to an ESP32

Six wires through a two-channel converter, and one line in setup() that decides whether the bus ends up on your GPIOs or on the board's defaults. Get the order wrong and there is no error, just a blank screen.

What goes where

FromTo
ESP32 3V3converter LV
ESP32 5Vconverter HV, and display VCC
ESP32 GNDconverter GND, and display GND
ESP32 IO6converter A1
ESP32 IO7converter A2
converter B1display SDA
converter B2display SCK

Three power wires before any signal wire. A converter with one side unpowered passes nothing at all and is indistinguishable from a broken display.

The A side faces the ESP32 and the B side faces the display, on every board in the converter kit. Use a two-channel MOSFET board or a TXS0108 — not a TXB0108, which fights the pull-up resistors an I²C bus is built on.

The line that decides everything

Whichever call starts the bus wins
lcd.init() first
Order of the two lines in setup()
Jumpers on
6 / 7
Bus ended up on
Compiler warnings
0
The library gets there first and picks the defaults. lcd.init() calls Wire.begin() with no arguments, which starts the bus on GPIO 8 and 9 on an ESP32-S3. Your Wire.begin(6, 7) then finds a running bus and does nothing. No error, no warning, a blank screen — and the scanner finds nothing either, which sends people looking for a wiring fault that is not there.

lcd.init() calls Wire.begin() with no arguments. On the ESP32 core, a call to start the bus checks whether the bus is already running and returns immediately if it is — so the first call wins and the second one is ignored without a word.

Start the bus yourself, with your pins, before you touch the display object:

Wire.begin(6, 7, 100000);   // your SDA, your SCL, 100 kHz
lcd.init();

Do it the other way round and the bus comes up on the board's defaults — GPIO 8 and 9 on an ESP32-S3 — your jumpers are on pins nothing is driving, and the sketch compiles, uploads and runs to completion with a blank screen and no warning. The scanner finds nothing either, because the scanner is looking at the same wrong pins.

Hold the bus at 100 kHz

The PCF8574's datasheet specifies a 100 kHz clock. The ESP32 will happily run faster, and through a converter and thirty centimetres of jumper wire it will happily run wrong — characters that are nearly right, or a display that works until you touch the wires.

The third argument to Wire.begin is the clock. Set it, and leave it set.

The code

lcd_on_esp32.ino

The same hello-world sketch, moved to an ESP32-S3 through a two-channel converter. The only line that is different is the first one in setup(), and it is the line that matters.

// Wiring (ESP32-S3 through a 2-channel logic level converter):
//
//   ESP32 3V3  -> converter LV
//   ESP32 5V   -> converter HV, and LCD VCC -> 5V
//   ESP32 GND  -> converter GND, and LCD GND -> GND
//   ESP32 IO6  -> converter A1     B1 -> LCD SDA
//   ESP32 IO7  -> converter A2     B2 -> LCD SCK
//
// Arduino IDE: Tools > Board "ESP32S3 Dev Module", Serial Monitor 115200,
//   Sketch > Include Library > Manage Libraries... > "LiquidCrystal I2C"
//   by Frank de Brabander.
//
// Never wire this display's SDA and SCK straight to a 3.3 V board.
// See /manuals/lcd1602/why-five-volts.

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

const int SDA_PIN = 6;
const int SCL_PIN = 7;

LiquidCrystal_I2C lcd(0x27, 16, 2);

void setup() {
  // This line MUST come first. lcd.init() calls Wire.begin() with no
  // pins, and whichever call starts the bus decides which GPIOs it uses.
  Wire.begin(SDA_PIN, SCL_PIN, 100000);

  lcd.init();
  lcd.backlight();

  lcd.setCursor(0, 0);
  lcd.print("LONELY BINARY");
  lcd.setCursor(0, 1);
  lcd.print("ESP32 + 1602");
}

void loop() {
}

Wire.begin() must come before lcd.init(). The library calls Wire.begin() with no pins inside init(), and on the ESP32 core the first call to start the bus is the one that sets the pins — the second one finds a running bus and returns without touching it.

When it does not work

Nothing on screen, and the scanner finds nothing either

Check the order of the two lines in setup() before anything else. If lcd.init() runs first the bus starts on the board's default pins — GPIO 8 and 9 on an ESP32-S3 — and your own Wire.begin() is silently ignored. The scanner looks at the wrong pins too, which is what makes this so convincing a wiring fault.

The converter is in and still nothing works

A converter with only one side powered passes nothing and looks broken. LV goes to 3V3 and HV goes to 5 V, and both need the shared ground. Measure both with a meter before you touch the sketch.

It scans but the characters are wrong or intermittent

The expander is specified to 100 kHz. The ESP32 defaults higher, and long breadboard wires through a converter make that worse. Pass 100000 as the third argument to Wire.begin() and keep the two signal wires short.

I used a TXB0108 and the display does not answer

The TXB drives each line with a weak push of its own that fights the pull-up resistors an I²C bus depends on, and TI names I²C as an application not to use it for. Swap it for a two-channel MOSFET board or a TXS0108.

Which GPIOs can I use?

Almost any free ones — the ESP32's I²C is not tied to fixed pins. Avoid the strapping pins and the ones wired to flash — the ESP32 topic “Pins you cannot use” has the list for your board.

Where this goes next

What the expander actually sends, and why a full redraw is something you can see happen.

Eight pins, one byte at a time

Edit this page — content/books/lcd1602/wiring-it-to-an-esp32.mdx

Community

Questions about this product

See what other owners have asked, and read their solutions.

Ask a question ↗

3-Pack 1602 LCD Display Module, I2C 16x2 Blue

Loading discussions…

Discuss this article

Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.

Browse Modules and blocks on the forum