16x2 LCD with I2C, on Arduino

Four wires instead of twelve: finding the address, the contrast trimmer that makes a blank screen look broken, and printing values that do not leave debris behind.

7 min readUpdated 3 Sept 2026
ShareWhatsApp

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 used6 minimum, 11 for full control2 — and both are shared
Extra partsA 10k potentiometer for contrastTrimmer already on the board
Wires to run12 to 164
Other devices on the same pinsNoYes — that is what a bus is for

The screen itself is identical. The backpack changes only how you talk to it.

Wiring

Backpack pinArduino Uno / NanoMega 2560ESP32
GNDGNDGNDGND
VCC5V5V5V, not 3V3
SDAA420GPIO 21
SCLA521GPIO 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.

i2c_scanner.inoArduino C++ · 27 lines
#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() { }
Serial monitor at 9600. It prints the address of everything on the bus.

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. 1

    Power the board with any sketch running

    The backlight should be on. If it is not, check VCC and GND first.

  2. 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. 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.

lcd_i2c.inoArduino C++ · 28 lines
#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);
}
Change 0x27 to whatever the scanner reported.

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, with setCursor putting you back at the same column each time.

When it does not work

What you seeCauseFix
Backlight on, screen blankWrong address, or contrast at one endRun the scanner. Turn the trimmer through its whole travel.
A row of solid blocksPowered, nothing written, contrast at maximumTurn the trimmer down. The display is fine.
Scanner finds nothingSDA and SCL swapped, or no powerA4 is SDA and A5 is SCL on an Uno. Check 5 V and a shared ground.
Nothing at all, no backlightVCC or GND missingMeasure 5 V across the backpack's own pins, not at the Arduino.
Numbers with extra digits stuck on the endShorter value printed over a longer onePad with trailing spaces.
Text appears then turns to garbageSupply dipping — often a relay or motor on the same 5 VSeparate supply for the load, grounds joined. Add 100 nF at the backpack.
Works alone, fails with a second I2C deviceTwo devices at the same addressScan 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 createChar and 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.

What you need for this

Everything above works with parts from anywhere. These are what we stock, if it is easier.

Read next

Written by us for projectkit.in. If you spot something wrong, tell us — a reference page that is confidently wrong is worse than no page, and we would rather hear it from you than from a viva panel. Back to the Knowledge Base.

WhatsApp