If you are building a sensor dashboard or a bench power supply, you need a reliable way to output text without tethering your microcontroller to a PC. The classic 16x2 parallel LCD is the workhorse of the hobbyist world, but wiring 16 pins directly to your microcontroller wastes precious GPIO and creates a rat's nest of jumper wires. The solution is an arduino display lcd equipped with an I2C backpack. By adding a $1 PCF8574 I/O expander chip to the back of the screen, you reduce the wiring from 16 pins down to just 4: VCC, GND, SDA, and SCL.
The Verdict: Which Arduino Display LCD Module Should You Buy?
Not every project needs a full-color TFT screen. Before buying, run your requirements through this decision path to avoid over-engineering your build.
| Requirement | Condition | Resulting Pick |
|---|---|---|
| Do you need to display custom graphics, bitmaps, or multiple colors? | Yes | 1.3" SH1106 OLED or 1.8" ST7735 TFT |
| Do you only need to display alphanumeric text and basic custom symbols? | Yes | HD44780 Character LCD |
| Are you short on GPIO pins or want to minimize wiring? | Yes | HD44780 with I2C Backpack (PCF8574) |
| Do you need high visibility in direct sunlight? | Yes | Reflective STN LCD (No backlight) |
Parts List and Exact Specifications
To ensure the code and pinouts below work exactly as written, match these specific board variants. Substituting an ESP32 or Raspberry Pi Pico will require different I2C pin assignments and logic-level shifting (3.3V vs 5V).
| Component | Exact Variant / Model | Key Specification |
|---|---|---|
| Microcontroller | Arduino Uno Rev3 (ATmega328P) | 5V logic, I2C on A4/A5 |
| Display Module | 16x2 Character LCD (HD44780 controller) | Blue backlight, white text, 5V VCC |
| I2C Backpack | PCF8574T (Note the 'T') | Default I2C Address: 0x27 |
| Wiring | 4x Female-to-Male Dupont Jumpers | 22 AWG stranded copper |
Difficulty Rating: 2/5 (Beginner-friendly)
Time to Complete: 15 minutes
Wiring the I2C Backpack to the Arduino Uno
The I2C bus requires only two data lines, but you must wire them to the correct hardware I2C pins on your specific board. On the classic Arduino Uno Rev3, these are shared with Analog pins A4 and A5.
| Backpack Pin | Arduino Uno Rev3 Pin | Function |
|---|---|---|
| GND | GND | Common Ground |
| VCC | 5V | Power (Do NOT use 3.3V) |
| SDA | A4 | I2C Data Line |
| SCL | A5 | I2C Clock Line |
Numbered Wiring Steps
- De-energize the board: Unplug the Arduino from USB before making connections to prevent shorting the 5V rail to the I2C data lines.
- Connect Ground and Power: Plug the backpack's GND pin into any Arduino GND header, and VCC into the 5V header. The blue backlight should turn on immediately when you plug the USB back in to test.
- Connect I2C Lines: Wire SDA to A4 and SCL to A5. If you are using an Arduino Uno R4 Minima or an Arduino Mega, use the dedicated SDA/SCL headers located near the AREF pin instead of A4/A5.
- Check the Address Jumpers: Look at the back of the PCF8574 backpack. You will see three unshorted pads labeled A0, A1, and A2. Leave them open (unbridged) for the default address of
0x27. If you solder-bridge them, the address changes, which we will cover in the debugging section.
Complete Compilable Code for Arduino Uno
This code targets the Arduino Uno Rev3. It uses the Wire library to ping the I2C bus before initializing the display, preventing the sketch from hanging if the display is disconnected or on the wrong address.
Prerequisite: Install the LiquidCrystal I2C library by Frank de Brabander via the Arduino IDE Library Manager (Sketch > Include Library > Manage Libraries).
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// --- PIN & ADDRESS DEFINITIONS ---
// Default address for PCF8574T is 0x27. For PCF8574AT, it is 0x3F.
#define LCD_I2C_ADDRESS 0x27
#define LCD_COLUMNS 16
#define LCD_ROWS 2
// Initialize the library with the I2C address and display dimensions
LiquidCrystal_I2C lcd(LCD_I2C_ADDRESS, LCD_COLUMNS, LCD_ROWS);
// Custom character: Thermometer symbol (5x8 pixels)
byte thermometer[8] = {
B00100,
B01010,
B01010,
B01010,
B01110,
B11111,
B11111,
B01110
};
void setup() {
Serial.begin(9600);
Wire.begin(); // Initialize I2C bus
// ERROR HANDLING: Verify I2C device is actually present before initializing
Wire.beginTransmission(LCD_I2C_ADDRESS);
byte error = Wire.endTransmission();
if (error != 0) {
Serial.print("FATAL: I2C device not found at address 0x");
Serial.println(LCD_I2C_ADDRESS, HEX);
Serial.println("Check wiring, pull-up resistors, or try address 0x3F.");
// Blink onboard LED to indicate hardware fault without halting CPU
pinMode(LED_BUILTIN, OUTPUT);
while(1) {
digitalWrite(LED_BUILTIN, HIGH);
delay(250);
digitalWrite(LED_BUILTIN, LOW);
delay(250);
}
}
// Initialize LCD
lcd.init();
lcd.backlight();
// Load custom character into CGRAM slot 0
lcd.createChar(0, thermometer);
// Print static header
lcd.setCursor(0, 0);
lcd.print("System Status:");
}
void loop() {
// Simulate reading a sensor
float tempC = 24.5;
lcd.setCursor(0, 1);
lcd.write((uint8_t)0); // Print custom thermometer char
lcd.print(" Temp: ");
lcd.print(tempC, 1);
lcd.print("C "); // Trailing spaces to overwrite old digits
delay(1000);
}
Debugging: Blank Screens and I2C Bus Errors
LCD modules are notorious for failing silently. If your screen isn't displaying text, do not rewrite your code. Hardware and configuration mismatches cause 95% of these failures. Here is the exact decision path to fix it.
The First 3 Things to Check When It Fails
- The Contrast Potentiometer: On the back of the I2C backpack, there is a small blue trimmer potentiometer. If the contrast is too low, the text is invisible; if it's too high, the top row displays solid black boxes. Use a small Phillips screwdriver to turn it while the screen is powered until the text is crisp against the background.
- SDA/SCL Swap: It is incredibly easy to swap SDA and SCL. If the screen backlight turns on but the I2C scanner finds nothing, reverse the A4 and A5 wires.
- The I2C Address Mismatch: Manufacturers use two different I/O expander chips. The PCF8574T defaults to
0x27. The PCF8574AT defaults to0x3F. If your code targets the wrong one, the display will ignore all commands.
Ranked Causes for Specific Error States
| Symptom / Exact Error String | Most Likely Cause | Fix |
|---|---|---|
LiquidCrystal_I2C.h: No such file or directory |
Missing library in Arduino IDE. | Open Library Manager, search 'LiquidCrystal I2C' by Frank de Brabander, and install. |
| Top row shows solid black boxes; bottom row is blank. | Display is powered but uninitialized, or contrast is maxed out. | Adjust the blue trimmer pot counter-clockwise. Ensure lcd.init() is in setup(). |
Serial Monitor prints: I2C scanner: No devices found |
Wiring fault, missing pull-ups, or dead backpack. | Verify A4/A5 connections. Run the standard Arduino I2C Scanner sketch (File > Examples > Wire > digital_potentiometer) to find the true hex address. |
| Backlight is ON, but screen is completely blank (no boxes). | Contrast potentiometer is turned all the way down. | Turn the blue trimmer pot clockwise until black boxes appear, then back off slightly. |
Extending and Simplifying the Build
Once you have the baseline 16x2 display running, you will inevitably want to adapt it to your specific enclosure or data requirements.
How to Simplify the Build
If you find 16 characters per line too restrictive, swap the physical screen for a 20x4 HD44780 LCD with an I2C backpack. The I2C backpack pinout and wiring remain exactly identical. You only need to change two lines in the code:
#define LCD_COLUMNS 20
#define LCD_ROWS 4
The LiquidCrystal_I2C library handles the memory mapping for the 4-line variant automatically. No new wiring required.
How to Extend the Build
To make the display useful for complex sensor data, utilize the HD44780's CGROM (Character Generator ROM). The controller allows you to define up to 8 custom 5x8 pixel characters. In the code block above, I included a custom thermometer symbol. You can extend this by defining arrays for battery icons, WiFi signal bars, or directional arrows, loading them into slots 0 through 7 using lcd.createChar(), and printing them with lcd.write((uint8_t)slot_number).
Final Recommendation: Do not attempt to bit-bang a parallel LCD or write your own I2C driver from scratch. Stick to the New-LiquidCrystal library by fmalpartida (packaged as LiquidCrystal_I2C in the IDE), use a PCF8574T backpack at address 0x27, and rely on the hardware Wire library for bus management. This combination guarantees a stable, compile-ready display interface that will survive your next enclosure redesign without requiring a single GPIO reassignment.






