A 16×2 LCD over I2C
| Difficulty | Intermediate |
|---|---|
| Category | Displays |
| Supported boards | Arduino Uno R3 and compatible ATmega328P boards |
| Time needed | 50 minutes |
| Prerequisites | I2C, Two Wires and Many Devices |
| Tested board | Not yet verified |
| Tested on | Not yet verified |
| Arduino IDE version | Not yet verified |
| Library | Not yet verified |
What it does
A 16×2 character LCD shows two lines of sixteen characters. It is the display to reach for when a project needs to say something to a person standing next to it, rather than to a serial monitor on a laptop.
The Siratek part has an I2C backpack soldered to the back, which is the whole point of it. A bare 1602 panel needs sixteen connections and six Arduino pins. With the backpack it needs four wires and two pins, and those two pins are shared with every other I2C device on the bus.
That convenience costs you something, and this tutorial is honest about what: the backpack hides the display’s own timing behind a chip you cannot see, so when it goes wrong the symptom is usually a blank screen with no clue attached. Most of this page is about reading that blank screen.
Before you begin
Required parts and tools
- An Arduino Uno or compatible board.
- One 16×2 LCD with the I2C backpack fitted. This is Siratek product 2484.
- Four female-to-male jumper wires.
No breadboard is needed. The backpack’s header is four pins and goes straight to the Uno.
What is on the back
Turn the display over and you are looking at a small green board soldered across the LCD’s sixteen pins. On it:
- A four-pin header, labelled
GND,VCC,SDA,SCLin that order. - A blue trimmer, which sets contrast. This is the single most common cause of a “dead” display.
- A jumper near one corner, which switches the backlight. Remove it and the backlight goes off permanently; the display still works, just unreadably in most rooms.
- Three pairs of solder pads marked
A0,A1andA2, which set the I2C address.
The backlight colour varies between batches, yellow-green or blue-white. It makes no difference to anything in this tutorial.
The address problem
The backpack chip is a PCF8574, and its I2C address depends on which of those A0 A1 A2 pads are bridged. Unbridged, the usual addresses are 0x27 or 0x3F, depending on which variant of the chip the maker fitted.
Two things follow:
- You must find your own address before anything will work. Do not copy
0x27from a tutorial and hope. It is a coin toss. - If you ever fit two of these displays on one bus, you have to bridge a pad on one of them, or they will fight over the same address.
Run the bus scanner from I2C, Two Wires and Many Devices before you go any further. It takes thirty seconds and it removes the biggest single source of wasted evenings on this part.
Contrast, before you blame the code
A character LCD with the contrast set wrong looks exactly like a broken one: backlight on, nothing on the screen. Or it shows a row of solid blocks on the top line and nothing else, which looks like a crash.
Neither is a fault. Turn the blue trimmer slowly with a small screwdriver, through its whole range, while the sketch is running. Somewhere in that range the text appears. Set it just past the point where the character blocks stop being visible behind the text.
Do this once, on the very first sketch, and then stop suspecting it.
Pin reference
| Pin or label | Connect to | Purpose | Notes |
|---|---|---|---|
GND |
Uno GND |
Ground | |
VCC |
Uno 5V |
Power and backlight | Draws roughly 20 to 40 mA with the backlight on |
SDA |
Uno A4 |
Data | Also SDA on an R3 header |
SCL |
Uno A5 |
Clock | Also SCL on an R3 header |
A4 and A5 are not free afterwards. They are the I2C bus, shared with any other I2C device, and they can no longer be used as ordinary analog inputs while the display is connected.
Wiring steps
- Unplug the USB cable.
- Connect
GNDon the backpack toGNDon the Uno. - Connect
VCCto5V. - Connect
SDAtoA4. - Connect
SCLtoA5. - Plug the USB cable back in. The backlight should come on at once, before you upload anything. If it does not, the problem is power, not code.
Software and libraries
The core LiquidCrystal library that ships with the IDE will not drive this display. It talks to the sixteen pins directly and knows nothing about the backpack.
Install LiquidCrystal I2C through Library Manager. Several libraries share that name; the one by Frank de Brabander is the common choice and the one this tutorial is written against. See Installing and Managing Libraries if Library Manager is new to you.
Record which one you installed. Their constructors differ, and a sketch written for one will often compile against another and then produce nothing.
Minimal working example
Find the address first.
// Siratek: find the I2C address of the display.
#include <Wire.h>
void setup() {
Wire.begin();
Serial.begin(9600);
while (!Serial) {
;
}
Serial.println("Siratek: scanning the I2C bus.");
int found = 0;
for (byte address = 1; address < 127; address++) {
Wire.beginTransmission(address);
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 found. Check SDA, SCL and power.");
}
}
void loop() {
}
Whatever address that prints is the one to use below.
// Siratek: hello on a 16x2 I2C display.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
// Use the address the scanner printed. 0x27 and 0x3F are both common.
LiquidCrystal_I2C lcd(0x27, 16, 2);
void setup() {
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Siratek");
lcd.setCursor(0, 1);
lcd.print("Kumasi, Ghana");
}
void loop() {
}
setCursor(column, row) counts from zero, and column comes first. That order catches almost everyone once.
Showing something that changes
A display is only useful when it shows live data, and that is where the first real mistake appears.
// Siratek: a counter on the display, updated without clearing the screen.
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 16, 2);
unsigned long lastUpdate = 0;
int count = 0;
void setup() {
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Count:");
}
void loop() {
if (millis() - lastUpdate >= 500) {
lastUpdate = millis();
count++;
lcd.setCursor(7, 0);
lcd.print(count);
lcd.print(" "); // wipes the tail of a longer previous number
}
}
Two things are deliberate here.
The label is printed once, in setup(). Only the number is rewritten.
There is no lcd.clear() in the loop. Clearing the whole screen every pass makes the display flicker visibly, and it is slow: the LCD needs about two milliseconds to process a clear, and the I2C traffic is serialised on top of that. Overwrite only what changed.
The trailing spaces matter. Going from 100 to 99 leaves the old 0 on screen, so you read 990. Padding the field with spaces is the fix, and forgetting it produces a bug that only appears when a number gets shorter.
Expected result
Backlight on as soon as power reaches the board. Two lines of text after upload, legible once the contrast is set.
The counter example should tick every half second with no flicker and no leftover digits.
Troubleshooting
| Symptom | Likely cause | Check or fix |
|---|---|---|
| Backlight on, screen blank | Contrast | Turn the blue trimmer through its whole range |
| Solid blocks on the top row | Contrast, or init() not called |
Adjust the trimmer; check lcd.init() is in setup() |
| Nothing at all, no backlight | Power | Check VCC and GND; the backlight needs no code |
| Scanner finds no device | SDA and SCL swapped, or loose |
SDA is A4, SCL is A5 |
| Scanner finds a different address | Normal | Use what it printed, not what a tutorial says |
| Compiles but screen stays blank | Wrong address in the constructor | Use the scanned address |
| Text appears garbled | Wrong library for this backpack | Use a LiquidCrystal_I2C library, not core LiquidCrystal |
| Flickers on every update | lcd.clear() in loop() |
Overwrite only the field that changed |
| Numbers read wrong when they shrink | Old digits left on screen | Pad the field with trailing spaces |
| Works alone, fails with another I2C device | Address clash | Scan with both connected; bridge a pad to move this one |
| Display dims or resets with a motor running | Supply sagging | See Powering Your Project |
Next steps
- Easier first: I2C, Two Wires and Many Devices, which explains the bus and the scanner.
- Related product: the same panel without a backpack, Siratek 5685, covered in Wiring a 16×2 LCD Directly. Worth reading to see what the backpack is saving you.
- Next project: show a DHT11 reading on the display, which is a weather station in two parts.
Sources and attribution
All text, wiring guidance and code in this tutorial are original Siratek work.
- Arduino core documentation for the
Wirelibrary. - The backpack layout described here was read from the Siratek product photographs for item 2484: a four-pin
GNDVCCSDASCLheader, a contrast trimmer, a backlight jumper andA0A1A2address pads.
The I2C address is given as something to scan for rather than as a fixed value, because it varies with the chip variant fitted. No third-party tutorial text, diagrams, photographs or code has been reproduced here.

