Hello everyone! In this article, we will connect a 16x2 LCD to Arduino over I2C. Using an I2C LCD with Arduino cuts the wiring from 12+ pins down to just 4 (VCC, GND, SDA, SCL), which keeps your projects much cleaner. Let’s get started!

Read This!

If you want to learn more about the 16x2 LCD Screen in more detail and how to use it without I2C, you can find out by clicking here. This blog will also be supported with guide blogs. ✨

Advertisement

What is an I2C Adapter?

The I2C chip has an 8-Bit I/O Extender chip-PCF8574. This chip converts the I2C data from an Arduino into parallel data required by the LCD screen.

i2c-cipi image could not be loaded. Please let us know in the comments!

The I2C also has a small trim pot to fine-tune the display’s contrast. You can adjust its brightness by turning it with a screwdriver.

i2c-trimpotu image could not be loaded. Please let us know in the comments!

In addition, the I2C has a pin cable that supplies power to the backlight. To control the intensity of the backlight, you can remove the cable and apply an external voltage to the head pin marked “LED”.

Without going into more detail, let’s move on to the way it is linked.

Arduino I2C LCD Connection

The I2C LCD module connects to Arduino with only four wires: VCC to 5V, GND to GND, SDA to A4 and SCL to A5 (on an Arduino Uno).

Arduino I2C LCD wiring diagram: 16x2 LCD connected to Arduino Uno over the I2C adapter (SDA to A4, SCL to A5)

The SDA and SCL pins sit somewhere different on every board. Find yours:

BoardSDASCLVCC
Arduino Uno / NanoA4A55V
Arduino Mega 256020215V
Arduino Leonardo / Micro235V
ESP32 (default)GPIO21GPIO225V (VIN)
ESP8266 / NodeMCUD2 (GPIO4)D1 (GPIO5)5V (VIN)
Raspberry Pi PicoGP4GP5VBUS (5V)
On ESP boards the supply must be 5V
An HD44780 panel usually will not run from 3.3V, or runs so faintly you cannot read it. On ESP boards power the module from VIN / 5V and leave the data lines (SDA, SCL) at 3.3V logic. The PCF8574 on the module accepts that level.

If you are not sure which pins are I2C on your board, check the Arduino Pinout and ESP32 Pinout references.

Find the Right I2C Address

This is the step people get stuck on. Modules answer at either 0x27 or 0x3F, with exceptions. The wrong address raises no error at all, the screen simply stays blank. Scan instead of guessing:

#include <Wire.h>

void setup() {
  Wire.begin();
  Serial.begin(9600);
  while (!Serial);
  Serial.println("Scanning I2C...");

  byte found = 0;
  for (byte address = 1; address < 127; address++) {
    Wire.beginTransmission(address);
    if (Wire.endTransmission() == 0) {
      Serial.print("Device found at 0x");
      if (address < 16) Serial.print("0");
      Serial.println(address, HEX);
      found++;
    }
  }
  if (found == 0) Serial.println("Nothing found. Check the wiring.");
}

void loop() {}

Open the serial monitor at 9600 baud and put whatever address it prints into the LiquidCrystal_I2C lcd(...) line. If no device shows up at all, the problem is the wiring or the supply, not the code.

Arduino IDE Library Setup

If you don’t know how to set up a library, you can check out this page.

Download the LiquidCrystal I2C library by Frank de Brabander by typing liquidcrystal instead of searching for a library. If you can’t find it, quickly download it from this link (it downloads as soon as you click it) and add it as a ZIP. (Downloading from Arduino site.) Here’s a quick rundown on how to add it as a ZIP.

Arduino I2C LCD Code Example

#include <LiquidCrystal_I2C.h> // added the library

LiquidCrystal_I2C lcd(0x3F,16,2);  // for a screen of 16 characters and 2 lines, the LCD address is set to 0x3F.

void setup() {
  lcd.init();
  lcd.clear();
  lcd.backlight();      // backlight on
  lcd.setCursor(2,0);   // 3 rows to the right, 1 column down
  lcd.print("projedefteri.com");
}

void loop() {
}

Library Functions

Everything LiquidCrystal_I2C can do is in the list below. For a 20x4 display just change the constructor to lcd(0x27, 20, 4); nothing else differs.

FunctionWhat it does
lcd.init()Initialises the display. Must be the first call in setup().
lcd.backlight() / lcd.noBacklight()Turns the backlight on / off.
lcd.clear()Wipes the screen and returns the cursor home.
lcd.home()Moves the cursor to the top left without clearing.
lcd.setCursor(column, row)Positions the cursor. Zero based: top left is (0, 0).
lcd.print(...)Prints text, numbers or variables.
lcd.cursor() / lcd.noCursor()Shows / hides the underline cursor.
lcd.blink() / lcd.noBlink()Blinking block cursor.
lcd.display() / lcd.noDisplay()Turns the image off without losing the content.
lcd.scrollDisplayLeft() / Right()Shifts the whole content one character.
lcd.createChar(index, bytes[])Loads a custom glyph into slot 0-7.
lcd.write(index)Prints a custom glyph.
setCursor takes column first, then row
lcd.setCursor(2, 1) means the third column of the second row, not the second column of the third row. Getting this backwards is why text seems to vanish. On a 16x2 the valid range is 0-15 for the column and 0-1 for the row.

Scrolling Text

Sixteen characters is not much. There are two approaches.

1. The library’s own scroll. It shifts the entire display, which is fine for short messages:

void setup() {
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);
  lcd.print("Proje Defteri - Arduino guides");
}

void loop() {
  lcd.scrollDisplayLeft();
  delay(300);
}

2. Scrolling your own window. More control, and it works per row:

String message = "Temp 23.4 C, Humidity 48%, Pressure 1013 hPa   ";
int pos = 0;

void loop() {
  lcd.setCursor(0, 0);
  lcd.print(message.substring(pos, pos + 16));

  pos++;
  if (pos > message.length() - 16) pos = 0;

  delay(300);
}

Note there is no lcd.clear() here. Because we print exactly 16 characters every time, the old content is already overwritten; adding clear() produces visible flicker.

Custom Characters

The HD44780 controller reserves eight custom character slots, 0 to 7. Each one is a 5x8 pixel glyph described by eight bytes.

byte degree[8] = {
  0b00110,
  0b01001,
  0b01001,
  0b00110,
  0b00000,
  0b00000,
  0b00000,
  0b00000
};

void setup() {
  lcd.init();
  lcd.backlight();
  lcd.createChar(0, degree);      // load into slot 0

  lcd.setCursor(0, 0);
  lcd.print("Temp: 23.4");
  lcd.write(byte(0));             // the degree sign
  lcd.print("C");
}
Do not write the byte array by hand
Use the LCD Custom Character Generator: click the 5x8 grid to draw your glyph and copy the finished byte array. It is the quickest route for accented letters, battery indicators, arrows and wifi icons.

The eight slot limit is real. If you need more, you can reload slots at runtime with createChar(), but that changes any glyph currently on screen too.

Troubleshooting

Blank screen but the backlight is on

Contrast. Turn the blue trim pot on the back of the module slowly with a screwdriver. It often ships near zero, which shows nothing at all.

Only a row of solid white blocks

The classic symptom: the panel works but the Arduino is not talking to it. Usually the wrong I2C address, or lcd.init() was never called. Run the scanner sketch.

Not even the backlight comes on

A supply problem. Check VCC and GND, and on ESP boards make sure you are using 5V rather than 3.3V. If the module has a backlight jumper, confirm it is fitted.

Text appears but the characters are garbled

Usually an inadequate supply or overly long wires. The I2C bus degrades past roughly 20 to 30 cm. If you have hung many sensors off the same bus, the pull-up resistors may also be insufficient.

If you want to show your own custom characters (special symbols, accented letters, etc.) on the screen you wired up over I2C, our LCD Custom Character Generator builds the byte array you need in seconds.

Frequently Asked Questions

Which pins does an I2C LCD use on Arduino? On an Arduino Uno the I2C LCD uses A4 (SDA) and A5 (SCL), plus 5V and GND. On an Arduino Mega, SDA is pin 20 and SCL is pin 21.

My Arduino I2C LCD shows nothing. What should I check? First adjust the contrast trim pot on the back of the I2C module. Then verify the I2C address: most modules use 0x27 or 0x3F. If neither works, run an I2C scanner sketch to find the correct address.

What is the I2C address of a 16x2 LCD? It depends on the chip on the adapter: PCF8574 modules usually respond at 0x27, PCF8574A modules at 0x3F. The address goes into the constructor, for example LiquidCrystal_I2C lcd(0x27,16,2);.

Which library do I need for an Arduino I2C LCD? The LiquidCrystal_I2C library. Install it from the Arduino IDE Library Manager or add it as a ZIP as shown above.

Does the same code work with a 20x4 LCD? Yes. The only change is the constructor: LiquidCrystal_I2C lcd(0x27, 20, 4);. Every function stays the same, only the setCursor range grows to 0-19 for the column and 0-3 for the row.

Can I put other sensors on the same I2C bus? Yes, that is the whole point of I2C. Wire them in parallel on SDA and SCL, as long as their addresses differ. An LCD uses 0x27, a BME280 uses 0x76, a DS3231 uses 0x68, so they coexist happily. Two devices with the same address need a multiplexer such as the TCA9548A.

How do I scroll text on an I2C LCD? Two ways: lcd.scrollDisplayLeft() shifts the whole display, or you can slide a 16 character window over a longer string yourself with substring(). The second is more useful because it works per row; there is example code in the scrolling section above.

Should I use an I2C LCD or an OLED? If you are only showing text, an I2C LCD is cheaper, more readable and uses almost no RAM. If you want graphics, icons or animation you need an SSD1306 OLED, at the cost of 1 KB of RAM on an Arduino Uno. See the Arduino OLED SSD1306 tutorial for that side.

If you have had any problems, do not forget to check your links, or you can send your questions, comments, and suggestions from the comments! Happy coding! 😁