To connect an Arduino LCD with I2C, you use a PCF8574-based I/O expander backpack that translates the serial I2C protocol into the parallel signals the HD44780 LCD controller expects. This reduces the required microcontroller pins from six down to just two (SDA and SCL). The default I2C address is typically 0x27 or 0x3F, depending on the specific expander chip variant used on the backpack.
The Physical Layer: Wiring and Pull-Up Requirements
Unlike SPI or UART, the I2C bus uses an open-drain (or open-collector) architecture. This means devices can only pull the SDA and SCL lines low to ground; they cannot drive them high. To achieve a logic HIGH, the bus relies on external pull-up resistors connected to VCC. If you omit these resistors, the bus will float, resulting in random garbage characters on your LCD or complete communication failure.
Wire.begin(), these are far too weak for reliable I2C communication at 400kHz or over long wires. Always use external 4.7kΩ pull-up resistors on both SDA and SCL for a robust physical layer.
| Backpack Pin | Arduino Uno (5V) | ESP32 (3.3V) | Function |
|---|---|---|---|
| GND | GND | GND | Common ground reference |
| VCC | 5V | 5V (via VIN/5V pin) | Logic and backlight power |
| SDA | A4 (or dedicated SDA) | GPIO 21 | Serial Data (requires 4.7kΩ pull-up) |
| SCL | A5 (or dedicated SCL) | GPIO 22 | Serial Clock (requires 4.7kΩ pull-up) |
When wiring a 3.3V microcontroller like the ESP32 or Raspberry Pi Pico to a standard 5V LCD backpack, you face a voltage mismatch. The SDA line is bidirectional. If the 5V backpack pulls SDA high, it will feed 5V back into the ESP32's 3.3V GPIO, potentially damaging the silicon. You must use a bidirectional logic level shifter (like a BSS138 MOSFET-based module) between the 3.3V MCU and the 5V LCD backpack.
I2C Bus Mechanics and Protocol Fit
Understanding why we use I2C for this specific peripheral requires looking at the bus mechanics and comparing it to alternatives. The NXP I2C-bus specification (UM10204) defines the electrical and timing rules that govern this exchange.
| Parameter | I2C (PCF8574 Backpack) | SPI (Shift Register) | Parallel (Direct HD44780) |
|---|---|---|---|
| Wires Required | 2 (SDA, SCL) + Power | 3 (MOSI, SCK, CS) + Power | 6 (RS, E, D4-D7) + Power |
| Standard Speed | 100 kHz / 400 kHz | 1 MHz to 10+ MHz | N/A (Direct GPIO toggle) |
| Addressing | 7-bit (up to 127 devices) | None (Requires individual CS pins) | None |
| Max Distance | ~1 meter (standard), up to 3m (low speed) | ~10 cm (high speed signal degradation) | ~30 cm (parallel skew issues) |
| Multi-Drop Fit | Excellent (shared bus) | Poor (daisy-chain or multiple CS) | Impossible |
Which protocol fits? I2C is the definitive choice for character LCDs when pin count is at a premium and you want to share the bus with other sensors (like a BME280 or RTC). SPI is faster but wastes pins for low-bandwidth text displays. Parallel is obsolete for modern embedded designs due to routing complexity and distance limitations. However, I2C is constrained by bus capacitance (limit of 400pF); adding too many devices or using ribbon cables longer than 1 meter will degrade the signal rise times, causing the LCD to miss bytes.
Minimal Working Exchange: Code and Initialization
Before writing text to the screen, you must verify the physical layer by scanning the bus. The most common point of failure is assuming the I2C address. The Texas Instruments PCF8574 datasheet shows that the base address for the standard PCF8574 is 0x20, while the PCF8574A variant uses a base address of 0x38. With the A0, A1, and A2 jumper pads left open (pulled high), the addresses resolve to 0x27 and 0x3F respectively.
Upload this minimal I2C scanner to verify your wiring and address:
#include <Wire.h>
void setup() {
Wire.begin();
Serial.begin(115200);
Serial.println("Scanning I2C bus...");
for (byte address = 1; address < 127; address++) {
Wire.beginTransmission(address);
byte error = Wire.endTransmission();
if (error == 0) {
Serial.print("LCD found at 0x");
if (address < 16) Serial.print("0");
Serial.println(address, HEX);
}
}
}
void loop() {}
Once you have the address, use the LiquidCrystal_I2C library (available via the Arduino Library Manager) for the minimal working exchange. Behind the scenes, this library maps the I2C expander pins to the HD44780 controller: P0=RS, P1=RW, P2=E, P3=Backlight, and P4-P7 map to data lines D4-D7.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// Initialize with the address found by the scanner (e.g., 0x27), 16 columns, 2 rows
LiquidCrystal_I2C lcd(0x27, 16, 2);
void setup() {
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Electrical Flux");
lcd.setCursor(0, 1);
lcd.print("I2C LCD Active");
}
void loop() {}
Debugging the Bus: Classic Failures and Sniffing
When your Arduino LCD with I2C displays nothing, shows white blocks, or freezes the microcontroller, you are dealing with a physical or protocol layer fault. Here are the classic failures and how to diagnose them.
1. Address Clash and Phantom Devices
If your I2C scanner returns no results, check the backpack chip. If it says PCF8574A, try 0x3F in your code. If it returns multiple addresses (e.g., 0x27 and 0x77), you have a floating address pin or a solder bridge on the A0/A1/A2 pads. Clean the flux residue off the jumper pads with isopropyl alcohol.
2. Missing Pull-Ups and NACK Errors
If the ESP32 crashes or throws a Wire library panic, the SDA/SCL lines are likely floating due to missing pull-ups. The ESP32's internal pull-ups are roughly 45kΩ, which results in an RC rise time that violates the I2C spec at 400kHz. Solder 4.7kΩ resistors between SDA-VCC and SCL-VCC directly on the backpack.
3. Baud Mismatch and Clock Stretching
Cheap PCF8574 backpacks sometimes struggle with the 400kHz Fast Mode I2C clock. If your LCD drops characters, force the bus to 100kHz Standard Mode by adding Wire.setClock(100000); immediately after Wire.begin();.
How to Sniff the Bus
When software debugging fails, hook up a $10 USB logic analyzer (like a Saleae clone) to SDA and SCL. Open PulseView or Sigrok and decode the I2C protocol. Watch the 9th clock cycle of every byte transmission. The master releases SDA, and the slave (the LCD backpack) must pull SDA low to send an ACK (Acknowledge). If you see a NACK (SDA stays high) on the address byte, your wiring is wrong or the chip is dead. If you see NACK on data bytes, the LCD controller is overwhelmed.
Arduino LCD with I2C FAQ
Why is my Arduino LCD with I2C showing white blocks on the first row?
This indicates the HD44780 controller is receiving power and the backlight is on, but it has not been successfully initialized via the I2C bus. First, adjust the blue trimpot on the back of the backpack with a small Phillips screwdriver to set the contrast. If the blocks remain, the I2C address in your code is wrong, or the SDA/SCL wires are swapped. Run the I2C scanner sketch to verify the exact hexadecimal address.
Can I connect multiple Arduino LCDs with I2C on the same bus?
Yes, I2C supports up to 127 devices, but every device must have a unique address. If you buy two identical LCD backpacks, they will both default to 0x27 (or 0x3F), causing an address clash. To fix this, look for the A0, A1, and A2 jumper pads on the PCB. By default, they are pulled high via traces. Use a hobby knife to cut the trace on A0, then solder a blob across the pad to pull it low. This shifts the address to 0x26. You can create up to 8 unique addresses per backpack variant by combining these jumper settings.
Will a 5V I2C LCD backpack damage a 3.3V ESP32 or Raspberry Pi?
Yes, it can. While the ESP32 GPIO pins are somewhat 5V tolerant on input, the I2C SDA line is bidirectional. When the 5V backpack pulls the line high, it pushes 5V back into the 3.3V microcontroller. Over time, this degrades the silicon. Always use a bidirectional logic level shifter (such as a BSS138-based module) between a 3.3V MCU and a 5V I2C LCD backpack to ensure safe voltage translation in both directions.
How do I change the I2C address of my LCD backpack?
The address is determined by the state of the A0, A1, and A2 pins on the PCF8574 chip. On most blue backpacks, these are exposed as three small sets of copper pads. If the pads are unconnected, internal pull-ups keep them HIGH. To change a pin to LOW, use an X-Acto knife to sever the tiny copper trace connecting the pad, and then apply a solder bridge across the pad to ground it. Consult the Arduino Wire library reference for standard I2C address handling once modified.






