Connecting an Arduino LCD display is one of the first skills every maker should learn. Whether you are building a weather station, a digital clock, or a sensor readout, a 16×2 LCD is the go-to display for beginner and intermediate Arduino projects. In this comprehensive tutorial, we cover everything from the basic 16-pin direct wiring to the much simpler I2C adapter, along with real working code, custom characters, scrolling text, and troubleshooting tips.
Table of Contents
Types of LCD Displays for Arduino
The most common LCD used with Arduino is the 16×2 LCD (HD44780 compatible), which has 16 columns and 2 rows giving you 32 characters of display space. These come in several backlight colors: green/yellow-green (classic), blue with white text, and white with black text. All HD44780-compatible LCDs use the same library and wiring, making them completely interchangeable in code.
Beyond 16×2, you will also find 20×4 LCDs for more display space (popular for menu-driven projects and data loggers), 8×2 compact LCDs, and graphical displays like 128×64 OLEDs. For this tutorial we focus on the ubiquitous 16×2, but the wiring and code principles apply to any HD44780-based LCD.
LCDs come in two variants from a wiring perspective:
- Bare LCD module – 16 pins, requires a potentiometer for contrast, uses 6 Arduino digital pins minimum in 4-bit mode
- LCD + I2C adapter – PCF8574 I2C backpack soldered onto the LCD, uses only 2 Arduino pins (SDA + SCL), much simpler and leaves more pins free for sensors
If you are buying new, always prefer the I2C version. It saves pins and simplifies your breadboard dramatically. The cost difference is minimal and the time saved on wiring is substantial.
16×2 LCD Pinout Explained
The standard HD44780 16×2 LCD has 16 pins numbered left to right. Understanding each pin helps you wire correctly and troubleshoot faster.
| Pin # | Name | Function |
|---|---|---|
| 1 | VSS | Ground (GND) |
| 2 | VDD | +5V Power supply |
| 3 | V0 | Contrast adjustment (potentiometer wiper) |
| 4 | RS | Register Select: HIGH for data, LOW for command |
| 5 | RW | Read/Write: connect to GND for write-only mode |
| 6 | E | Enable: clock pulse to latch data |
| 7-10 | D0-D3 | Data bits 0-3 (not used in 4-bit mode, leave unconnected) |
| 11-14 | D4-D7 | Data bits 4-7 (the 4 active data pins in 4-bit mode) |
| 15 | A (LED+) | Backlight anode: +5V via 220 ohm resistor |
| 16 | K (LED-) | Backlight cathode: GND |
Wiring Without I2C (Direct Method)
For the direct wiring method you need a 10k ohm potentiometer for contrast and 6 digital pins on your Arduino. This works on every Arduino board without installing extra libraries and is a great way to understand how the LCD hardware actually works.
Connection list (4-bit mode):
- LCD Pin 1 (VSS) to Arduino GND
- LCD Pin 2 (VDD) to Arduino 5V
- LCD Pin 3 (V0) to potentiometer center pin; outer legs to 5V and GND
- LCD Pin 4 (RS) to Arduino Digital Pin 12
- LCD Pin 5 (RW) to Arduino GND
- LCD Pin 6 (E) to Arduino Digital Pin 11
- LCD Pins 7-10 (D0-D3): leave unconnected in 4-bit mode
- LCD Pin 11 (D4) to Arduino Digital Pin 5
- LCD Pin 12 (D5) to Arduino Digital Pin 4
- LCD Pin 13 (D6) to Arduino Digital Pin 3
- LCD Pin 14 (D7) to Arduino Digital Pin 2
- LCD Pin 15 (A) to Arduino 5V via 220 ohm current-limiting resistor
- LCD Pin 16 (K) to Arduino GND
After wiring, power on the board before uploading code. You should see the backlight glow. If you see a row of solid black rectangles on row 1, the LCD is alive but needs contrast adjustment. Turn the potentiometer slowly until characters appear clearly. This is the single most common first-timer mistake and nothing is wrong with your hardware.
Wiring With I2C Adapter
The I2C LCD adapter (PCF8574 backpack) reduces the wiring to just 4 wires: VCC, GND, SDA, and SCL. This is the recommended method for any project where you need spare pins for sensors or other modules.
- I2C LCD VCC to Arduino 5V
- I2C LCD GND to Arduino GND
- I2C LCD SDA to Arduino A4 (Uno/Nano), Pin 20 (Mega), or GPIO21 (ESP32)
- I2C LCD SCL to Arduino A5 (Uno/Nano), Pin 21 (Mega), or GPIO22 (ESP32)
The default I2C address is usually 0x27 or 0x3F. Run the I2C scanner sketch (File > Examples > Wire > i2c_scanner) to confirm your adapter’s address. The PCF8574 backpack also has a small blue trim potentiometer for contrast. Adjust it with a small screwdriver until text is clearly visible. Most people set it once and never touch it again.
LiquidCrystal Library Code (Direct Wiring)
The LiquidCrystal library is bundled with the Arduino IDE, so no installation is needed. The constructor takes pin numbers in this order: RS, E, D4, D5, D6, D7. Here is a complete working example with a live uptime counter:
#include <LiquidCrystal.h>
// Constructor order: RS, E, D4, D5, D6, D7
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
void setup() {
lcd.begin(16, 2); // 16 columns, 2 rows
lcd.setCursor(0, 0); // Column 0, Row 0 = top-left
lcd.print("Hello, Zbotic!"); // Max 16 chars per row
lcd.setCursor(0, 1); // Move to row 2, column 0
lcd.print("Arduino LCD OK");
}
void loop() {
lcd.setCursor(0, 1);
lcd.print("Uptime: ");
lcd.print(millis() / 1000);
lcd.print("s "); // Trailing spaces clear old longer digits
delay(500);
}
Essential LiquidCrystal functions:
lcd.begin(cols, rows)– initialise LCD, must be called first in setup()lcd.setCursor(col, row)– move cursor, both are 0-indexedlcd.print(value)– prints string, integer, float, or char arraylcd.clear()– clears all text and homes cursor (takes ~2ms, avoid in fast loops)lcd.noDisplay()/lcd.display()– saves power without losing screen contentlcd.cursor()/lcd.noCursor()– shows or hides the underline cursorlcd.blink()/lcd.noBlink()– enables or disables blinking block cursor
I2C Library Code
For the I2C LCD, install LiquidCrystal I2C by Frank de Brabander from the Library Manager (Sketch > Include Library > Manage Libraries, search “LiquidCrystal I2C”). The API is nearly identical to the standard library with a few extra methods for backlight control.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// I2C address 0x27, 16 columns, 2 rows
// If 0x27 does not work, try 0x3F
LiquidCrystal_I2C lcd(0x27, 16, 2);
void setup() {
lcd.init(); // Initialize the LCD hardware
lcd.backlight(); // Turn on the LED backlight
lcd.setCursor(0, 0);
lcd.print("Hello, India!");
lcd.setCursor(0, 1);
lcd.print("I2C LCD Ready!");
}
void loop() {
// Non-blocking update every 1 second
static unsigned long lastUpdate = 0;
if (millis() - lastUpdate >= 1000) {
lastUpdate = millis();
lcd.setCursor(0, 1);
lcd.print(millis() / 1000);
lcd.print(" seconds ");
}
}
Displaying Custom Characters
The HD44780 LCD stores up to 8 custom characters in CGRAM (character generator RAM). Each character is a 5×8 pixel grid defined as an 8-element byte array. This is how you display a degree symbol for temperature readings, a rupee sign approximation, a heart for UI decoration, or animation frames for a loading indicator.
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
// Degree symbol - commonly used for temperature displays
byte degreeChar[8] = {
B00110,
B01001,
B01001,
B00110,
B00000,
B00000,
B00000,
B00000
};
// Heart symbol for UI decoration
byte heartChar[8] = {
B00000,
B01010,
B11111,
B11111,
B01110,
B00100,
B00000,
B00000
};
void setup() {
lcd.begin(16, 2);
lcd.createChar(0, degreeChar); // Stored in CGRAM slot 0 (0-7)
lcd.createChar(1, heartChar); // Stored in CGRAM slot 1
lcd.setCursor(0, 0);
lcd.print("Temp: 28");
lcd.write(byte(0)); // Print the degree symbol from slot 0
lcd.print("C ");
lcd.write(byte(1)); // Print the heart from slot 1
lcd.setCursor(0, 1);
lcd.print("Zbotic.in");
}
void loop() {}
Use free online tools like the LCD Character Creator at maxpromer.github.io to design custom pixel characters visually and export the byte arrays. You can create arrows, bar graphs, Indian rupee signs, and more. Custom chars are preserved through lcd.clear() calls since they live in CGRAM, not display RAM.
Scrolling Text
When your message is longer than 16 characters, scrolling it across the display creates a professional ticker effect. The software approach using substring() gives the most control over timing and behavior.
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
// Leading/trailing spaces create a smooth entry and exit
String ticker = " Welcome to Zbotic.in - India's Electronics Store! ";
void setup() {
lcd.begin(16, 2);
lcd.setCursor(0, 0);
lcd.print(" Zbotic.in ");
}
void loop() {
int msgLen = ticker.length();
for (int i = 0; i <= msgLen - 16; i++) {
lcd.setCursor(0, 1);
lcd.print(ticker.substring(i, i + 16));
delay(300);
}
delay(1500); // Pause before the next scroll cycle
}
For projects requiring both rows to scroll, use hardware scrolling: call lcd.scrollDisplayLeft() inside a timed loop to shift the entire display one column left at each step. Hardware scroll is simpler but scrolls all content together, so it works best when both rows have synchronized content.
Troubleshooting Common LCD Problems
Most LCD issues are caused by contrast settings, wiring mistakes, or wrong I2C addresses. This table covers every common problem and its fix.
| Problem | Likely Cause | Fix |
|---|---|---|
| Backlight on, no text visible | Contrast too low | Adjust potentiometer (or I2C trim pot) slowly |
| Random or garbled characters | Wrong pin numbers in constructor | Recheck RS, E, D4-D7 in LiquidCrystal() order |
| I2C LCD not responding | Wrong I2C address | Run I2C scanner; try both 0x27 and 0x3F |
| Solid black squares on row 1 only | Contrast too high or code not reaching lcd.begin() | Adjust pot; verify lcd.begin(16, 2) is in setup() |
| No backlight at all | Missing 220 ohm resistor on pin 15 or loose GND | Add resistor; re-seat pin 15 and pin 16 connections |
| I2C screen dark, no text | lcd.backlight() not called | Add lcd.backlight() in setup() right after lcd.init() |
| I2C scanner finds no devices | SDA/SCL swapped, or missing pull-up resistors | Confirm A4=SDA and A5=SCL on Uno; add 4.7k pull-ups to 5V if missing |
Frequently Asked Questions
Q: Can I use a 16×2 LCD with a 3.3V board like ESP32 or NodeMCU?
Yes, but carefully. The LCD itself needs 5V power. If using I2C, add a bidirectional level shifter on the SDA and SCL lines since the ESP32 is 3.3V only. Some I2C backpacks include a level translator built-in. Alternatively, an SSD1306 OLED is natively 3.3V compatible and often easier to use with ESP-based boards popular in Indian IoT projects.
Q: What is the difference between 4-bit and 8-bit LCD mode?
In 4-bit mode, data is sent in two 4-bit chunks using only pins D4-D7, saving 4 Arduino digital pins with negligible speed impact. In 8-bit mode, all 8 data pins transfer a full byte simultaneously. For all practical Arduino projects, 4-bit mode is completely adequate and is what the LiquidCrystal library uses by default when you pass only 4 data pins to the constructor.
Q: How many I2C LCDs can I connect to one Arduino at the same time?
The PCF8574 backpack has 3 solder jumpers (A0, A1, A2) that configure the I2C address. This allows up to 8 unique addresses per chip variant, meaning up to 8 LCDs sharing the same 2 SDA/SCL wires. In your code, instantiate each LCD with its own address: LiquidCrystal_I2C lcd1(0x27, 16, 2) and LiquidCrystal_I2C lcd2(0x26, 16, 2).
Q: My LCD shows correct text but flickers. How do I fix it?
Flickering is almost always caused by calling lcd.clear() in every loop iteration. The clear() function takes about 2ms and visibly blanks the screen between writes. Fix it by using setCursor() to overwrite specific positions and adding trailing spaces to erase old longer values. This eliminates flicker entirely and makes your display look professional.
Q: The I2C scanner finds my LCD at an address but it still does not display anything. What next?
If the address is confirmed but the display is blank, check these in order: (1) adjust the contrast trim pot on the I2C backpack, (2) verify lcd.backlight() is called in setup(), (3) confirm lcd.init() runs before any print commands, and (4) check that your power supply can source enough current. USB power from a laptop sometimes cannot drive multiple modules simultaneously.
Ready to Build Your Next Arduino Project?
Shop sensors, modules, and components at Zbotic.in – India’s trusted electronics store with fast shipping across India.
Add comment