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
| Parameter | Value |
|---|---|
| Display types | 16x2 or 20x4 characters |
| Controller | HD44780 compatible |
| I2C backpack | PCF8574 (or PCF8574A) |
| I2C address | 0x27 (PCF8574) or 0x3F (PCF8574A) |
| Supply voltage | $5\,\text{V}$ |
| Backlight | LED (controllable via I2C) |
| Character size | $5 \times 8$ pixel matrix per character |
🔗What You'll Need
| Component | Qty | Notes | Buy |
|---|---|---|---|
| ESP32 dev board | 1 | AliExpress | Amazon.de .co.uk .com | |
| LCD 16x2 with I2C backpack | 1 | AliExpress | Amazon.de .co.uk .com | |
| Breadboard | 1 | AliExpress | Amazon.de .co.uk .com | |
| Jumper wires | 4 | AliExpress | 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 Pin | ESP32 Pin | Notes |
|---|---|---|
| VCC | 5V (VIN) | LCD needs 5V for proper contrast |
| GND | GND | |
| SDA | GPIO 21 | Default I2C data line |
| SCL | GPIO 22 | Default 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:
- Go to Sketch > Include Library > Manage Libraries...
- 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:
- Converts each character into commands and data bytes
- Sends them over I2C to the PCF8574
- 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
| Problem | Possible Cause | Solution |
|---|---|---|
| Display is blank | Wrong I2C address | Try 0x3F instead of 0x27. Run an I2C scanner sketch. |
| Display shows solid blocks | Contrast too high or LCD not initialized | Adjust the contrast potentiometer on the back of the module |
| Garbled or missing characters | I2C communication error | Check wiring, try a logic level shifter, or reduce I2C speed |
| Backlight works but no text | Contrast potentiometer turned all the way down | Adjust the potentiometer slowly |
| Display flickers | Loose wiring or insufficient power | Check all connections, ensure stable 5V supply |
| Only shows on one line (16x2) | Wrong display dimensions in constructor | Verify 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