I2C LCD Display (16x2 / 20x4)

How to use an I2C character LCD display with ESP32

Character LCDs have been around for decades and remain popular for good reason. They are cheap, easy to read (even in direct sunlight where OLEDs struggle), and available in 16x2 (16 characters, 2 rows) and 20x4 sizes. The version with an I2C backpack (based on the PCF8574 I/O expander) is especially convenient -- it reduces the wiring from 12+ connections down to just four. If you need to display text like sensor values, status messages, or simple menus, an I2C LCD is an excellent choice.

This guide uses the ESP32-WROOM-32 DevKit. Pin labels and GPIO numbers may differ on your board -- always check your board's pinout diagram.

Alternatives: For pixel-level graphics, charts, or a compact form factor, the SSD1306 OLED is a great choice. See our display comparison guide for help choosing.

🔗Key Specs

ParameterValue
Display types16x2 or 20x4 characters
ControllerHD44780 compatible
I2C backpackPCF8574 (or PCF8574A)
I2C address0x27 (PCF8574) or 0x3F (PCF8574A)
Supply voltage$5\,\text{V}$
BacklightLED (controllable via I2C)
Character size$5 \times 8$ pixel matrix per character

🔗What You'll Need

ComponentQtyNotesBuy
ESP32 dev board1AliExpress | Amazon.de .co.uk .com
LCD 16x2 with I2C backpack1AliExpress | Amazon.de .co.uk .com
Breadboard1AliExpress | Amazon.de .co.uk .com
Jumper wires4AliExpress | Amazon.de .co.uk .com

Links marked Amazon/AliExpress are affiliate links. We may earn a small commission at no extra cost to you.

🔗Wiring

LCD I2C PinESP32 PinNotes
VCC5V (VIN)LCD needs 5V for proper contrast
GNDGND
SDAGPIO 21Default I2C data line
SCLGPIO 22Default I2C clock line

Note on voltage levels: The ESP32 outputs $3.3\,\text{V}$ on its I2C lines (GPIO 21 and 22), while the LCD module runs at $5\,\text{V}$. This usually works fine because the I2C protocol uses open-drain signaling with pull-up resistors, and $3.3\,\text{V}$ is above the minimum HIGH threshold for most $5\,\text{V}$ I2C devices. However, if you experience communication issues (garbled characters or no response), a logic level shifter between the ESP32 and the LCD may be necessary.

🔗Required Libraries

Install the LiquidCrystal I2C library through the Arduino Library Manager:

  1. Go to Sketch > Include Library > Manage Libraries...
  2. Search for LiquidCrystal I2C by Frank de Brabander and install it

🔗Code Example: Display Text

This sketch displays a static message on the first line and a running counter on the second.

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

// Set the I2C address and display dimensions
// Try 0x3F if 0x27 does not work
LiquidCrystal_I2C lcd(0x27, 16, 2);

int counter = 0;

void setup() {
    lcd.init();
    lcd.backlight();    // Turn on the backlight
    lcd.setCursor(0, 0);
    lcd.print("IoT With ESP");
    lcd.setCursor(0, 1);
    lcd.print("LCD Ready!");
    delay(2000);
}

void loop() {
    lcd.setCursor(0, 1);       // Move cursor to second row
    lcd.print("Count: ");
    lcd.print(counter);
    lcd.print("    ");         // Clear any leftover characters

    counter++;
    delay(500);
}

🔗Code Example: Display Sensor Readings

A practical example showing temperature and humidity values on the LCD.

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

LiquidCrystal_I2C lcd(0x27, 16, 2);

void setup() {
    lcd.init();
    lcd.backlight();
}

void displayReadings(float temp, float humidity) {
    lcd.setCursor(0, 0);
    lcd.print("Temp: ");
    lcd.print(temp, 1);
    lcd.print(" C   ");

    lcd.setCursor(0, 1);
    lcd.print("Hum:  ");
    lcd.print(humidity, 1);
    lcd.print(" %   ");
}

void loop() {
    // Replace with actual sensor readings
    float temp = 22.5;
    float hum = 48.3;

    displayReadings(temp, hum);
    delay(2000);
}

🔗Code Example: Custom Characters

The HD44780 controller lets you define up to 8 custom characters (5x8 pixels each). This is useful for special symbols like a degree sign, battery icon, or arrows.

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

LiquidCrystal_I2C lcd(0x27, 16, 2);

// Define a custom degree symbol (5x8 pixels)
byte degreeSymbol[] = {
    0b00110,
    0b01001,
    0b01001,
    0b00110,
    0b00000,
    0b00000,
    0b00000,
    0b00000
};

// Define a custom thermometer icon
byte thermometer[] = {
    0b00100,
    0b01010,
    0b01010,
    0b01010,
    0b01010,
    0b10001,
    0b10001,
    0b01110
};

void setup() {
    lcd.init();
    lcd.backlight();

    lcd.createChar(0, degreeSymbol);
    lcd.createChar(1, thermometer);

    lcd.setCursor(0, 0);
    lcd.write(1);              // Print thermometer icon
    lcd.print(" Temp: 23.5");
    lcd.write(0);              // Print degree symbol
    lcd.print("C");
}

void loop() {}

🔗Code Example: Scrolling Text

For messages longer than the display width, you can scroll text across the screen.

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

LiquidCrystal_I2C lcd(0x27, 16, 2);

void setup() {
    lcd.init();
    lcd.backlight();

    lcd.setCursor(0, 0);
    lcd.print("IoT With ESP");
}

void loop() {
    lcd.setCursor(0, 1);
    lcd.print("  Scrolling text demo  ");

    for (int i = 0; i < 24; i++) {
        lcd.scrollDisplayLeft();
        delay(300);
    }

    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("IoT With ESP");
    delay(1000);
}

🔗How It Works

🔗The HD44780 Controller

The HD44780 is the standard controller chip used in character LCDs. It manages the dot-matrix display, character generator ROM (with built-in ASCII characters), and display RAM. Normally, this chip requires 8 or 4 data pins, plus control pins -- a lot of wiring.

🔗The I2C Backpack

The I2C backpack (PCF8574) sits between the ESP32 and the HD44780 controller. It is a simple I/O expander that converts I2C serial data into the parallel signals the HD44780 expects. This reduces the interface to just two wires (SDA and SCL), plus power and ground.

When you call lcd.print("Hello"), the LiquidCrystal_I2C library:

  1. Converts each character into commands and data bytes
  2. Sends them over I2C to the PCF8574
  3. The PCF8574 toggles the appropriate parallel pins to communicate with the HD44780

🔗Contrast Adjustment

Most I2C LCD modules have a small potentiometer (blue or white trimmer) on the back of the PCF8574 board. If the display is powered but you see no text (or only solid blocks), turn this potentiometer with a small screwdriver until the contrast is correct. This is one of the most common "gotchas" with these displays.

🔗Troubleshooting

ProblemPossible CauseSolution
Display is blankWrong I2C addressTry 0x3F instead of 0x27. Run an I2C scanner sketch.
Display shows solid blocksContrast too high or LCD not initializedAdjust the contrast potentiometer on the back of the module
Garbled or missing charactersI2C communication errorCheck wiring, try a logic level shifter, or reduce I2C speed
Backlight works but no textContrast potentiometer turned all the way downAdjust the potentiometer slowly
Display flickersLoose wiring or insufficient powerCheck all connections, ensure stable 5V supply
Only shows on one line (16x2)Wrong display dimensions in constructorVerify LiquidCrystal_I2C lcd(0x27, 16, 2) matches your display

🔗Next Steps

  • Display real-time sensor readings from a temperature sensor or air quality sensor
  • Create a simple menu system with buttons for navigation
  • Build a countdown timer or clock
  • Compare with the SSD1306 OLED to decide which display fits your project best