If you’ve ever attempted to connect an LCD display to an Arduino, you’ve probably noticed that it uses a lot of Arduino pins. Even in 4-bit mode, the Arduino requires seven connections – half of the Arduino’s available digital I/O pins.
The solution is to use an I2C LCD display. It only uses two I/O pins that are not even part of the digital I/O pin set and can be shared with other I2C devices.
Hardware Overview
A typical I2C LCD display consists of an HD44780-based character LCD display and an I2C LCD adapter. Let’s learn more about them.
Character LCD Display
As the name suggests, these LCDs are ideal for displaying only characters. A 16×2 character LCD, for example, can display 32 ASCII characters across two rows.

If you look closely, you can see tiny rectangles for each character on the screen as well as the pixels that make up a character. Each of these rectangles is a grid of 5×8 pixels.
Please refer to our in-depth guide for more information about character LCD displays.
I2C LCD Adapter
At the heart of the adapter is an 8-bit I/O expander chip – PCF8574. This chip converts the I2C data from an Arduino into the parallel data required for an LCD display.

The board also includes a tiny trimpot for making precise adjustments to the display’s contrast.

There is a jumper on the board that provides power to the backlight. To control the intensity of the backlight, you can remove the jumper and apply external voltage to the header pin labeled ‘LED’.
I2C Address of LCD
If you have multiple devices on the same I2C bus, you may need to set a different I2C address for the LCD adapter to avoid conflicting with another I2C device.
For this purpose, the adapter comes with three solder jumpers/pads (A0, A1, and A2). The address is set when a jumper is shorted with a blob of solder.

An important point to note here is that several companies, including Texas Instruments and NXP Semiconductors, manufacture the same PCF8574 chip. And the I2C address of your LCD depends on the chip manufacturer.
If your LCD has Texas Instruments’ PCF8574 chip:
According to the Texas Instruments’ datasheet, the three address selection bits (A0, A1, and A2) are located at the end of the 7-bit I2C address register.

Because there are three address inputs that can take on two states, either HIGH or LOW, eight (2^3) different combinations (addresses) are possible.
All three address inputs are pulled HIGH using onboard pullups. This gives the PCF8574 a default I2C address of 0x27.
When you short a solder jumper, you pull that address input LOW. If you were to short all three jumpers, the address would be 0x20. So the range of all possible addresses spans from 0x20 to 0x27.
You can set a different I2C address, according to the table below.

If your LCD has NXP’s PCF8574 chip:
According to the NXP Semiconductors’ datasheet, the three address selection bits (A0, A1, and A2) are located at the end of the 7-bit I2C address register. However, the remaining bits in the address register are different.

Because there are three address inputs that can take on two states, either HIGH or LOW, eight (2^3) different combinations (addresses) are possible.
All three address inputs are pulled HIGH using onboard pullups. This gives the PCF8574 a default I2C address of 0x3F.
When you short a solder jumper, you pull that address input LOW. If you were to short all three jumpers, the address would be 0x38. So the range of all possible addresses spans from 0x38 to 0x3F.
You can set a different I2C address, according to the table below.

So the I2C address of your LCD is most likely 0x27 or 0x3F. If you’re not sure what your LCD’s I2C address is, there’s an easy way to figure it out. You’ll learn about that later in this tutorial.
I2C LCD Display Pinout
The I2C LCD Display has only four pins. The following is the pinout:

GND is a ground pin.
VCC is the power supply pin. Connect it to the 5V output of the Arduino or an external 5V power supply.
SDA is the I2C data pin.
SCL is the I2C clock pin.
Wiring an I2C LCD Display to an Arduino
Connecting an I2C LCD is much simpler than connecting a standard LCD. You only need to connect four pins.
Begin by connecting the VCC pin to the Arduino’s 5V output and the GND pin to ground.
Now we are left with the pins that are used for I2C communication. Note that each Arduino board has different I2C pins that must be connected correctly. On Arduino boards with the R3 layout, the SDA (data line) and SCL (clock line) are on the pin headers close to the AREF pin. They are also referred to as A5 (SCL) and A4 (SDA).
Check out the table below for quick reference.
SCL | SDA | |
Arduino Uno | A5 | A4 |
Arduino Nano | A5 | A4 |
Arduino Mega | 21 | 20 |
Leonardo/Micro | 3 | 2 |
The diagram below shows how to connect everything.

Adjusting The LCD Contrast
After wiring the LCD, you will need to adjust the contrast of the LCD. On the I2C module, there is a potentiometer that can be rotated with a small screwdriver.
Now, turn on the Arduino. You will see the backlight light up. As you turn the potentiometer knob, the first row of rectangles will appear. If you have made it this far, Congratulations! Your LCD is functioning properly.

Library Installation
Before you can proceed, you must install the LiquidCrystal_I2C library. This library allows you to control I2C displays using functions that are very similar to the LiquidCrystal library.
To install the library, navigate to Sketch > Include Library > Manage Libraries… Wait for the Library Manager to download the library index and update the list of installed libraries.

Filter your search by entering ‘liquidcrystal‘. Look for the LiquidCrystal I2C library by Marco Schwartz. Click on that entry and then choose Install.

Determining the I2C Address
As previously stated, the I2C address of your LCD depends on the manufacturer. If your LCD has a PCF8574 chip from Texas Instruments, its I2C address is 0x27; if it has a PCF8574 chip from NXP Semiconductors, its I2C address is 0x3F.
If you’re not sure what your LCD’s I2C address is, you can run a simple I2C scanner sketch that scans your I2C bus and returns the address of each I2C device it finds.
You can find this sketch under File > Examples > Wire > i2c_scanner.

Load the i2c_scanner sketch into your Arduino IDE.
#include <Wire.h>
void setup() {
Wire.begin();
Serial.begin(9600);
while (!Serial); // Leonardo: wait for serial monitor
Serial.println("\nI2C Scanner");
}
void loop() {
int nDevices = 0;
Serial.println("Scanning...");
for (byte address = 1; address < 127; ++address) {
// The i2c_scanner uses the return value of
// the Write.endTransmisstion to see if
// a device did acknowledge to the address.
Wire.beginTransmission(address);
byte error = Wire.endTransmission();
if (error == 0) {
Serial.print("I2C device found at address 0x");
if (address < 16) {
Serial.print("0");
}
Serial.print(address, HEX);
Serial.println(" !");
++nDevices;
} else if (error == 4) {
Serial.print("Unknown error at address 0x");
if (address < 16) {
Serial.print("0");
}
Serial.println(address, HEX);
}
}
if (nDevices == 0) {
Serial.println("No I2C devices found\n");
} else {
Serial.println("done\n");
}
delay(5000); // Wait 5 seconds for next scan
}
After you’ve uploaded the sketch, launch the serial monitor at 9600 baud. You should see the I2C address of your I2C LCD display.

Please make a note of this address. You’ll need it in later examples.
Basic Arduino Sketch – Hello World
The test sketch below will print ‘Hello World!’ on the first line of the LCD and ‘LCD Tutorial’ on the second.
However, before you upload the sketch, you must make a minor change to make it work for you. You must pass the I2C address of your LCD as well as the display dimensions to the LiquidCrystal_I2C constructor. If you’re using a 16×2 character LCD, pass 16 and 2; if you’re using a 20×4 character LCD, pass 20 and 4.
// enter the I2C address and the dimensions of your LCD here
LiquidCrystal_I2C lcd(0x3F, 16, 2);
Once you are done, go ahead and try the sketch.
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x3F,16,2); // set the LCD address to 0x3F for a 16 chars and 2 line display
void setup() {
lcd.init();
lcd.clear();
lcd.backlight(); // Make sure backlight is on
// Print a message on both lines of the LCD.
lcd.setCursor(2,0); //Set cursor to character 2 on line 0
lcd.print("Hello world!");
lcd.setCursor(2,1); //Move cursor to character 2 on line 1
lcd.print("LCD Tutorial");
}
void loop() {
}
This is what you should see on the screen.

Code Explanation:
The sketch begins by including the LiquidCrystal_I2C library.
#include <LiquidCrystal_I2C.h>
The next step is to create an object of LiquidCrystal_I2C class. The LiquidCrystal_I2C constructor accepts three inputs: I2C address, number of columns, and number of rows of the display.
LiquidCrystal_I2C lcd(0x3F,16,2);
In the setup, three functions are called. The first function is init()
. It initializes the interface to the LCD. The second function is clear()
. This function clears the LCD screen and positions the cursor in the upper-left corner. The third function, backlight()
, turns on the LCD backlight.
lcd.init();
lcd.clear();
lcd.backlight();
The function setCursor(2, 0)
is then called to move the cursor to the third column of the first row. The cursor position specifies where you want the new text to appear on the LCD. It is assumed that the upper left corner is col=0
and row=0
.
lcd.setCursor(2,0);
Next, the print()
function is used to print “Hello world!” to the LCD.
lcd.print("Hello world!");
Similarly, the next two lines of code move the cursor to the third column of the second row and print ‘LCD Tutorial’ to the LCD.
lcd.setCursor(2,1);
lcd.print("LCD Tutorial");
Other useful functions of the LiquidCrystal_I2C Library
There are many useful functions you can use with LiquidCrystal_I2C Object. Some of them are listed below:
lcd.home()
function positions the cursor in the upper-left of the LCD without clearing the display.lcd.blink()
function displays a blinking block of 5×8 pixels at the position to which the next character will be written.lcd.noBlink()
function turns off the blinking LCD cursor.lcd.cursor()
function displays an underscore (line) at the position to which the next character will be written.lcd.noCursor()
function hides the LCD cursor.lcd.scrollDisplayRight()
function scrolls the contents of the display one space to the right. If you want the text to scroll continuously, you have to use this function inside afor
loop.lcd.scrollDisplayLeft()
function scrolls the contents of the display one space to the left. Similar to the above function, use this inside afor
loop for continuous scrolling.lcd.noDisplay()
function turns off the LCD display, without losing the text currently shown on it.lcd.display()
function turns on the LCD display, after it’s been turned off withnoDisplay()
. This will restore the text (and cursor) that was on the display.
Create and Display Custom Characters
If you find the default font uninteresting, you can create your own custom characters (glyphs) and symbols. They come in handy when you need to display a character that isn’t in the standard ASCII character set.
As previously discussed in this tutorial, a character is made up of a 5×8 pixel matrix; therefore, you must define your custom character within this matrix. You can define a character by using the createChar()
function.
To use createChar()
, you must first create an 8-byte array. Each byte in the array corresponds to a row in a 5×8 matrix. In a byte, the digits 0 and 1 indicate which pixels in a row should be OFF and which should be ON.
All of these user-defined characters are stored in the LCD’s CGRAM.
CGROM and CGRAM
All Hitachi HD44780 driver-based LCDs have two types of memory: CGROM and CGRAM (Character Generator ROM and RAM).
CGROM is non-volatile memory that retains data even when the power is removed, whereas CGRAM is volatile memory that loses data when the power is removed.
The CGROM stores the font that appears on a character LCD. When you instruct a character LCD to display the letter ‘A’, it needs to know which pixels to turn on so that we see an ‘A’. This data is stored in the CGROM.
CGRAM is an additional memory for storing user-defined characters. This RAM is limited to 64 bytes. Therefore, for a 5×8 pixel LCD, only 8 user-defined characters can be stored in CGRAM, whereas for a 5×10 pixel LCD, only 4 can be stored.
Custom Character Generator
Creating custom characters has never been easier! We’ve developed a small application called Custom Character Generator. Can you see the blue grid below? You can click on any pixel to set or clear that pixel. And as you click, the code for the character is generated next to the grid. This code can be used directly in your Arduino sketch.
There’s no limit to what you can create. The only limitation is that the LiquidCrystal_I2C library only supports eight custom characters. But don’t be sad, look at the bright side; at least we have eight characters.
Arduino Example Code
The sketch below demonstrates how to display custom characters on the LCD.
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x3F, 16, 2); // set the LCD address to 0x3F for a 16 chars and 2 line display
// make some custom characters:
byte Heart[8] = {
0b00000,
0b01010,
0b11111,
0b11111,
0b01110,
0b00100,
0b00000,
0b00000
};
byte Bell[8] = {
0b00100,
0b01110,
0b01110,
0b01110,
0b11111,
0b00000,
0b00100,
0b00000
};
byte Alien[8] = {
0b11111,
0b10101,
0b11111,
0b11111,
0b01110,
0b01010,
0b11011,
0b00000
};
byte Check[8] = {
0b00000,
0b00001,
0b00011,
0b10110,
0b11100,
0b01000,
0b00000,
0b00000
};
byte Speaker[8] = {
0b00001,
0b00011,
0b01111,
0b01111,
0b01111,
0b00011,
0b00001,
0b00000
};
byte Sound[8] = {
0b00001,
0b00011,
0b00101,
0b01001,
0b01001,
0b01011,
0b11011,
0b11000
};
byte Skull[8] = {
0b00000,
0b01110,
0b10101,
0b11011,
0b01110,
0b01110,
0b00000,
0b00000
};
byte Lock[8] = {
0b01110,
0b10001,
0b10001,
0b11111,
0b11011,
0b11011,
0b11111,
0b00000
};
void setup()
{
lcd.init();
// Make sure backlight is on
lcd.backlight();
// create a new characters
lcd.createChar(0, Heart);
lcd.createChar(1, Bell);
lcd.createChar(2, Alien);
lcd.createChar(3, Check);
lcd.createChar(4, Speaker);
lcd.createChar(5, Sound);
lcd.createChar(6, Skull);
lcd.createChar(7, Lock);
// Clears the LCD screen
lcd.clear();
// Print a message to the lcd.
lcd.print("Custom Character");
}
// Print All the custom characters
void loop()
{
lcd.setCursor(0, 1);
lcd.write(0);
lcd.setCursor(2, 1);
lcd.write(1);
lcd.setCursor(4, 1);
lcd.write(2);
lcd.setCursor(6, 1);
lcd.write(3);
lcd.setCursor(8, 1);
lcd.write(4);
lcd.setCursor(10, 1);
lcd.write(5);
lcd.setCursor(12, 1);
lcd.write(6);
lcd.setCursor(14, 1);
lcd.write(7);
}
The output appears as shown.

Code Explanation:
After including the library and creating the LCD object, custom character arrays are defined. The array consists of 8 bytes, with each byte representing a row in a 5×8 matrix.
This sketch contains eight custom-characters. Take, for example, the Heart[8]
array. You can see that the bits (0s and 1s) are forming the shape of a heart. 0 turns the pixel off, and 1 turns it on.
byte Heart[8] = {
0b00000,
0b01010,
0b11111,
0b11111,
0b01110,
0b00100,
0b00000,
0b00000
};
In the setup, we use the createChar()
function to create a custom character. This function accepts two parameters: a number between 0 and 7 to reserve one of the eight supported custom characters, and the name of the array.
lcd.createChar(0, Heart);
In the loop, to display the custom character, we simply call the write()
function and pass it the number of the character we reserved earlier.
lcd.setCursor(0, 1);
lcd.write(0);