The 16x2 character LCD is the most reused part in student electronics. It is on the water-level controller, the weighing scale, the attendance system and the greenhouse monitor, because a project that shows its reading on a screen demonstrates itself and a project that only prints to a laptop does not.
Wired directly it needs twelve connections and a contrast potentiometer. With an I2C backpack it needs four. Buy the version with the backpack already soldered on.
What the backpack does
The small board on the back of the display carries a PCF8574, an I2C port expander. Instead of the Arduino wiggling eight data lines and three control lines itself, it sends bytes down two wires and the expander does the wiggling.
| Direct (parallel) | With the I2C backpack | |
|---|---|---|
| Arduino pins used | 6 minimum, 11 for full control | 2 — and both are shared |
| Extra parts | A 10k potentiometer for contrast | Trimmer already on the board |
| Wires to run | 12 to 16 | 4 |
| Other devices on the same pins | No | Yes — that is what a bus is for |
The screen itself is identical. The backpack changes only how you talk to it.
Wiring
| Backpack pin | Arduino Uno / Nano | Mega 2560 | ESP32 |
|---|---|---|---|
| GND | GND | GND | GND |
| VCC | 5V | 5V | 5V, not 3V3 |
| SDA | A4 | 20 | GPIO 21 |
| SCL | A5 | 21 | GPIO 22 |
On an Uno, A4 and A5 are the I2C pins and cannot be moved. Newer boards also bring them out to two dedicated pins beside AREF — the same two wires, so use either, never both.
Step one: find the address
This is the single reason most I2C displays appear dead. Modules ship at one of two addresses, 0x27 or 0x3F, and every tutorial sketch hard-codes one of them. If yours is the other one, you get a lit backlight and a blank screen — which looks exactly like a broken display.
Run this first, every time, on any new module.
#include <Wire.h>
void setup() {
Wire.begin();
Serial.begin(9600);
while (!Serial) { } // needed on a Leonardo or Micro, harmless elsewhere
Serial.println("Scanning the I2C bus...");
int found = 0;
for (byte address = 1; address < 127; address++) {
Wire.beginTransmission(address);
// A device that is present acknowledges. endTransmission returns 0 for
// an ACK and non-zero for silence.
if (Wire.endTransmission() == 0) {
Serial.print(" device at 0x");
if (address < 16) Serial.print("0");
Serial.println(address, HEX);
found++;
}
}
if (found == 0) {
Serial.println("Nothing on the bus - check SDA, SCL, 5V and GND");
}
}
void loop() { }Whatever it reports goes into the next sketch. If it finds nothing at all, the fault is in the four wires or the power — not in the display and not in your code.
Step two: the contrast trimmer
There is a small blue potentiometer on the backpack. It sets the contrast, it ships in a random position, and at the wrong end of its travel the screen shows either nothing at all or a row of solid white blocks. Both are routinely mistaken for a dead module.
- 1
Power the board with any sketch running
The backlight should be on. If it is not, check VCC and GND first.
- 2
Turn the trimmer slowly, all the way, with a small screwdriver
Somewhere in that travel the characters appear. If you only ever see blocks or only ever see blank, you have not been to both ends.
- 3
Stop where the text is crisp and the background is clean
A little past the point where blocks fade out is usually right.
The library, and the sketch
Install LiquidCrystal_I2C through the Library Manager. There are several forks of it with slightly different function names — the one by Frank de Brabander is the one nearly every example targets, and lcd.init() rather than lcd.begin() is how you can tell you have it.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// Address from the scanner. 0x27 and 0x3F are the two common ones.
LiquidCrystal_I2C lcd(0x27, 16, 2);
void setup() {
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0); // column, then row - both count from 0
lcd.print("projectkit.in");
lcd.setCursor(0, 1);
lcd.print("Temp:");
}
void loop() {
float celsius = 27.4; // whatever your sensor returned
// Only redraw the part that changes. Clearing the whole screen every pass
// is what makes an LCD flicker.
lcd.setCursor(6, 1);
lcd.print(celsius, 1);
lcd.print((char)223); // the degree symbol in this character set
lcd.print("C "); // trailing spaces wipe the old, longer value
delay(500);
}Printing values without leaving debris
This is the bug that makes a working project look unfinished, and it is in a great many student demonstrations.
An LCD does not erase. Printing 100 and then 99 over it leaves `990` — the old third character is still there, because nothing told it to go. Your distance sensor reads a plausible number that is silently wrong by a digit.
- Pad with trailing spaces.
lcd.print("99 ")wipes what the longer value left. Simplest and what the sketch above does. - Never use `lcd.clear()` in a fast loop. It works, and it makes the screen flicker visibly because the whole display goes blank between frames.
- Redraw only what changes. Print the fixed labels once in
setup()and update just the numbers, withsetCursorputting you back at the same column each time.
When it does not work
| What you see | Cause | Fix |
|---|---|---|
| Backlight on, screen blank | Wrong address, or contrast at one end | Run the scanner. Turn the trimmer through its whole travel. |
| A row of solid blocks | Powered, nothing written, contrast at maximum | Turn the trimmer down. The display is fine. |
| Scanner finds nothing | SDA and SCL swapped, or no power | A4 is SDA and A5 is SCL on an Uno. Check 5 V and a shared ground. |
| Nothing at all, no backlight | VCC or GND missing | Measure 5 V across the backpack's own pins, not at the Arduino. |
| Numbers with extra digits stuck on the end | Shorter value printed over a longer one | Pad with trailing spaces. |
| Text appears then turns to garbage | Supply dipping — often a relay or motor on the same 5 V | Separate supply for the load, grounds joined. Add 100 nF at the backpack. |
| Works alone, fails with a second I2C device | Two devices at the same address | Scan with both connected. Move one — most modules have solder pads to change it. |
Where to take it
- Add a DHT and it is a room monitor that needs no computer.
- Add an RTC on the same two wires and it is a clock with a temperature readout — no extra pins.
- Add a 4x4 keypad and it is a password-entry panel, which is most of a door-lock project.
- Learn
createCharand you can draw a battery gauge or a level bar out of custom characters, which reads far better in a demonstration than a bare number.