Interface an I2C LCD with Arduino

If you’ve ever tried to connect an LCD display to an Arduino, you might have noticed that it consumes a lot of pins on the Arduino. Even in 4-bit mode, the Arduino still requires a total of seven connections – which is half of the Arduino’s available digital I/O pins.

The solution is to use an I2C LCD display. It consumes only two I/O pins that are not even part of the set of digital I/O pins and can be shared with other I2C devices as well.

Hardware Overview

A typical I2C LCD display consists of an HD44780 based character LCD display and an I2C LCD adapter. Let’s get to know them one by one.

Character LCD Display

True to their name, these LCDs are ideal for displaying only text/characters. A 16×2 character LCD, for example, has an LED backlight and can display 32 ASCII characters in two rows of 16 characters each.

character lcd internal pixel grid structure

If you look closely you can see tiny rectangles for each character on the display and the pixels that make up a character. Each of these rectangles is a grid of 5×8 pixels.

Check out our comprehensive tutorial for more information on 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.

pcf8574 chip on i2c lcd

The board also comes with a small trimpot to make fine adjustments to the display’s contrast.

i2c lcd adapter hardware overview

In addition, there is a jumper on the board that supplies power to the backlight. To control the intensity of the backlight, you can remove the jumper and apply external voltage to the header pin that is marked ‘LED’.

I2C Address of LCD

If you are using multiple devices on the same I2C bus, you may need to set a different I2C address for the LCD adapter so that it does not conflict with another I2C device.

To do this, the adapter has three solder jumpers (A0, A1 and A2) or solder pads.

i2c address selection jumpers on i2c lcd

Each of these is used to hardcode the address. If a jumper is shorted with a blob of solder, it sets the address.

An important point here is that several companies manufacture the same PCF8574 chip, Texas Instruments and NXP Semiconductors, to name a few. 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 placed at the end of the 7-bit I2C address register.

texas instruments pcf8574 i2c address register

Since there are 3 address inputs, which can take 2 states, either HIGH/LOW, we can therefore create 8 (23) different combinations (addresses).

By default, all 3 address inputs are pulled HIGH using onboard pullups, giving the PCF8574 a default I2C address of 0100111Binary or 0x27Hex.

By shorting the solder jumpers, the address inputs are puled LOW. If you were to short all three jumpers, the address would be 0x20. The range of all possible addresses spans from 0x20 to 0x27. Please see the illustration below.

i2c lcd address selection jumper table for ti

If your LCD has NXP’s PCF8574 chip:

According to the NXP Semiconductors’ datasheet, the three address selection bits (A0, A1 and A2) are also placed at the end of the 7-bit I2C address register. But the other bits in the address register are different.

nxp semiconductors pcf8574 i2c address register

Since there are 3 address inputs, which can take 2 states, either HIGH/LOW, we can therefore create 8 (23) different combinations (addresses).

By default, all 3 address inputs are pulled HIGH using onboard pullups, giving the PCF8574 a default I2C address of 0111111Binary or 0x3FHex.

By shorting the solder jumpers, the address inputs are puled LOW. If you were to short all three jumpers, the address would be 0x38. The range of all possible addresses spans from 0x38 to 0x3F. Please see the illustration below.

i2c lcd address selection jumper table for nxp

So your LCD probably has a default I2C address 0x27Hex or 0x3FHex. However it is recommended that you find out the actual I2C address of the LCD before using it.

Luckily there’s an easy way to do this. We will see that later in the tutorial.

I2C LCD display Pinout

An I2C LCD has only 4 pins that connect it to the outside world. The connections are as follows:

i2c lcd display pinout

GND is a ground pin. Connect it to the ground of the Arduino.

VCC supplies power to the module and LCD. Connect it to the Arduino’s 5V output or an external 5V power supply.

SDA is the I2C data pin. Connect it to the Arduino’s I2C data pin.

SCL is the I2C clock pin. Connect it to the Arduino’s I2C clock pin.

Hooking up an Arduino Uno to an I2C LCD display

Connecting an I2C LCD is much easier than connecting a standard LCD. You only need to connect 4 pins instead of 12. Start by connecting the VCC pin to the 5V output on the Arduino and GND to ground.

Now we are left with the pins which are used for I2C communication. Note that each Arduino board has different I2C pins that must be connected accordingly. 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 known as A5 (SCL) and A4 (SDA).

If you are using a different Arduino board, please refer to the table below.

SCLSDA
Arduino UnoA5A4
Arduino NanoA5A4
Arduino Mega2120
Leonardo/Micro32

The following diagram shows you how to wire everything up.

wiring i2c lcd display with arduino

Adjusting The LCD Contrast

After wiring up the LCD you’ll need to adjust the contrast of the display. On the I2C module you will find a potentiometer that you can rotate with a small screwdriver.

Plug in the Arduino’s USB connector to power the LCD. You will see the backlight lit up. Now as you turn the knob on the potentiometer, you will start to see the first row of rectangles. If that happens, Congratulations! Your LCD is working fine.

testing i2c character lcd contrast by turning potentiometer

Once this is done, we can start programming the LCD.

Library Installation

To drive an I2C LCD you must first install a library called LiquidCrystal_I2C. This library is an enhanced version of the LiquidCrystal library that comes with your Arduino IDE.

To install the library navigate to Sketch > Include Libraries > Manage Libraries… Wait for Library Manager to download the library index and update the list of installed libraries.

Arduino Library Installation - Selecting Manage Libraries in Arduino IDE

Filter your search by typing ‘liquidcrystal‘. There should be some entries. Look for the LiquidCrystal I2C library by Frank de Brabander. Click on that entry, and then select Install.

liquidcrystal_i2c library installation

Determining the I2C Address

The I2C address of your LCD depends on the manufacturer, as mentioned earlier. If your LCD has a Texas Instruments’ PCF8574 chip, its default I2C address is 0x27Hex. If your LCD has NXP Semiconductors’ PCF8574 chip, its default I2C address is 0x3FHex.

So your LCD probably has I2C address 0x27Hex or 0x3FHex. However it is recommended that you find out the actual I2C address of the LCD before using it. Luckily there’s an easy way to do this, thanks to the Nick Gammon.

Nick wrote a simple I2C scanner sketch that scans your I2C bus and returns the address of each I2C device it finds.

#include <Wire.h>

void setup() {
  Serial.begin (9600);

  // Leonardo: wait for serial port to connect
  while (!Serial) 
    {
    }

  Serial.println ();
  Serial.println ("I2C scanner. Scanning ...");
  byte count = 0;
  
  Wire.begin();
  for (byte i = 8; i < 120; i++)
  {
    Wire.beginTransmission (i);
    if (Wire.endTransmission () == 0)
      {
      Serial.print ("Found address: ");
      Serial.print (i, DEC);
      Serial.print (" (0x");
      Serial.print (i, HEX);
      Serial.println (")");
      count++;
      delay (1);  // maybe unneeded?
      } // end of good response
  } // end of for loop
  Serial.println ("Done.");
  Serial.print ("Found ");
  Serial.print (count, DEC);
  Serial.println (" device(s).");
}  // end of setup

void loop() {}

Load this sketch into your Arduino and then open your Serial Monitor. You will see the I2C address of your I2C LCD display.

i2c address scanner output

Please note this address. You will need it in later examples.

Basic Arduino Sketch – Hello World

The following test sketch will print ‘Hello World!’ on the first line of the LCD and ‘LCD Tutorial’ on the second line.

But, before you proceed to upload the sketch, you need to make a small change to make it work for you. You must pass the I2C address of your LCD and the dimensions of the display to the constructor of the LiquidCrystal_I2C class. If you are using a 16×2 character LCD, pass the 16 and 2; If you’re using a 20×4 LCD, pass 20 and 4. You got the point!

// enter the I2C address and the dimensions of your LCD here
LiquidCrystal_I2C lcd(0x3F, 16, 2);

Once you are done go ahead and give the sketch a try.

#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() {
}

If all goes well, you should see something like this on the display.

Interfacing 16x2 character LCD with Arduino Hello world Program output

Code Explanation:

The sketch begins by including the LiquidCrystal_I2C library.

#include <LiquidCrystal_I2C.h>

First of all an object of LiquidCrystal_I2C class is created. This object takes three parameters LiquidCrystal_I2C(address, columns, rows). This is where you need to enter the address you found earlier, and the dimensions of the display.

LiquidCrystal_I2C lcd(0x3F,16,2);

Once you have declared a LiquidCrystal_I2C object, you can access the object methods specific to the LCD.

In ‘setup’ we call three functions. The first function is init(). It initializes the LCD object. The second function is clear(). This clears the LCD screen and moves the cursor to the top left corner. And third, the backlight() function turns on the LCD backlight.

lcd.init();
lcd.clear();         
lcd.backlight();

After that we set the cursor position to the third column of the first row by calling the function lcd.setCursor(2, 0). The cursor position specifies the location where you want the new text to be displayed on the LCD. The upper left corner is assumed to be col=0, row=0.

lcd.setCursor(2,0);

Next, the string ‘Hello World!’ is printed by calling the print() function.

lcd.print("Hello world!");

Similarly the next two lines of code set the cursor position to the third column of the second row, and print ‘LCD Tutorial’ on the LCD.

lcd.setCursor(2,1);
lcd.print("LCD Tutorial");

Other useful functions of the Library

There are some useful functions you can use with LiquidCrystal_I2C objects. Some of them are listed below:

  • lcd.home() function is used to position 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 at which the next character is to be written.
  • lcd.cursor() displays an underscore (line) at the position at which the next character is to be written.
  • lcd.noBlink() function turns off the blinking LCD cursor.
  • lcd.noCursor() 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 a for loop.
  • lcd.scrollDisplayLeft() function scrolls the contents of the display one space to the left. Similar to above function, use this inside a for loop for continuous scrolling.

Create and Display Custom Characters

If you find the characters on the display dull and boring, you can create your own custom characters (glyphs) and symbols for your LCD. They are extremely useful when you want to display a character that is not part of the standard ASCII character set.

As discussed earlier in this tutorial a character is made up of a 5×8 pixel matrix, so you need to define your custom character within that matrix. You can use the createChar() function to define a character.

To use createChar() you first set up an array of 8 bytes. Each byte in the array represents a row of characters in a 5×8 matrix. Whereas, 0 and 1 in a byte indicate which pixel in the row should be ON and which should be OFF.

All these user defined characters are stored in the CGRAM of the LCD.

CGROM and CGRAM

All Hitachi HD44780 controller based LCDs have two types of memory – CGROM and CGRAM (Character Generator ROM and RAM).

CGROM is non-volatile memory and cannot be modified whereas CGRAM is volatile memory and can be modified at any time.

CGROM is used to store all permanent fonts that are displayed using their ASCII codes. For example, if we send 0x41 to the LCD, the letter ‘A’ will be printed on the display.

CGRAM is another memory used to store user defined characters. This RAM is limited to 64 bytes. For a 5×8 pixel based LCD, only 8 user-defined characters can be stored in CGRAM. And for 5×10 pixel based LCD only 4 user-defined characters can be stored.

Custom Character Generator

Creating custom characters has never been easier! We have created a small application called Custom Character Generator. Can you see the blue grid below? You can click on any 5×8 pixel to set/clear that particular 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.

byte Character[8] =
{
0b00000, 0b00000, 0b01010, 0b11111, 0b11111, 0b01110, 0b00100, 0b00000
};

Your imagination is limitless. The only limitation is that the LiquidCrystal library only supports eight custom characters. But don’t be discouraged, look at the bright side, at least we have eight characters.

Arduino Example Code

The following sketch shows how you can create custom characters and print them on an 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);
}

You will see the following output on the LCD:

Interfacing 16x2 LCD with Arduino Custom Character Generation Program output

Code Explanation:

After the library is included and the LCD object is created, custom character arrays are defined. The array consists of 8 bytes, each byte representing a row of a 5×8 LED matrix. In this sketch, eight custom characters have been created.

Let’s examine the Heart[8] array as an example. You can see how the bits (0s and 1s) are forming a heart shape. 0 turns the pixel off and 1 turns the pixel on.

byte Heart[8] = {
0b00000,
0b01010,
0b11111,
0b11111,
0b01110,
0b00100,
0b00000,
0b00000
};

In setup, a custom character is created using the createChar() function. This function takes two parameters. The first parameter is a number between 0 and 7 to reserve one of the 8 supported custom characters. The second is the name of the array.

lcd.createChar(0, Heart);

Next in the loop, to display the custom character we simply use the write() function and pass it the number of the character we reserved earlier.

lcd.setCursor(0, 1);
lcd.write(0);