Interface an I2C LCD with Arduino

If you have ever tried to connect an LCD display with an Arduino, you may 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 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 a set of digital I/O pins and can also be shared with other I2C devices.

I2C LCD Hardware Overview

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

Character LCD Display

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

character lcd internal pixel grid structure

If you look closely, you can actually see the little 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.

For more information about Character LCD Display check out this comprehensive tutorial of ours.

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 by the LCD display.

pcf8574 chip on i2c lcd

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

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 an external voltage to the header pin that is marked as ‘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 board, so that it does not conflict with another I2C device.

To do so, the board 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 many 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 a PCF8574 chip from Texas Instruments:

According to the Texas Instruments’ datasheet, 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

As 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 the 3 address inputs are pulled HIGH using onboard pullups, giving 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 refer the below illustration.

i2c lcd address selection jumper table for ti

If your LCD has a PCF8574 chip from NXP Semiconductors:

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

nxp semiconductors pcf8574 i2c address register

Again! The 3 address input can have 2 states, either HIGH / LOW, giving us 8 (23) different combinations.

By default, all the 3 address inputs are pulled HIGH using onboard pullups, giving 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 refer the below illustration.

i2c lcd address selection jumper table for nxp

I2C LCD display Pinout

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

i2c lcd display pinout

GND is a ground pin and should be connected to the ground of Arduino.

VCC supplies power to the module and the LCD. Connect it to the 5V output of the Arduino or a separate power supply.

SDA is a Serial Data pin. This line is used for both transmit and receive. Connect to the SDA pin on the Arduino.

SCL is a Serial Clock pin. This is a timing signal supplied by the Bus Master device. Connect to the SCL pin on the Arduino.

Hooking up an Arduino Uno to an I2C LCD display

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

Now we are remaining with the pins that are used for I2C communication. Note that each Arduino Board has different I2C pins which should be connected accordingly. On the 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 below table.

SCLSDA
Arduino UnoA5A4
Arduino NanoA5A4
Arduino Mega2120
Leonardo/Micro32

The following diagram shows you how to wire everything.

wiring i2c lcd display with arduino

Adjusting The LCD Contrast

After you wire the LCD, you will need to adjust the contrast of the display. On the I2C module, you will find a potentiometer that you can turn with a small screwdriver.

Plug in the USB connector of the Arduino to power the LCD. You should see the backlight light up. Now rotate the potentiometer until the first line of the rectangle appears.

testing i2c character lcd contrast by turning potentiometer

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

Library Installation

In order to run the subsequent sketches you’ll need to install a library called LiquidCrystal_I2C. This library is an improved version of the LiquidCrystal library that comes packaged with your Arduino IDE.

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

Arduino Library Installation - Selecting Manage Libraries in Arduino IDE

Filter your search by typing ‘liquidcrystal’. There should be a couple entries. Look for 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 PCF8574 chip from Texas Instruments, its default I2C address is 0x27Hex. If your LCD has a PCF8574 chip from NXP semiconductors, its default I2C address is 0x3FHex.

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

Nick has written a simple I2C scanner sketch that scans your I2C bus and gives you back 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 then open your serial monitor. You’ll see the I2C address of your I2C LCD display.

i2c address scanner output

Please make note of this address. You’ll need it in the subsequent sketches.

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 head for uploading the sketch, you need to make some changes to make it work for you. You need to enter the I2C address of your LCD and the dimensions of the display (columns and rows the display). If you are using 16×2 character LCD, pass the parameters 16 & 2; If you are using 20×4 LCD, pass the parameters 20 & 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 out.

#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 everything goes right, you should see following output on the display.

Interfacing 16x2 character LCD with Arduino Hello world Program output

Code Explanation:

The sketch starts by including LiquidCrystal_I2C library.

#include <LiquidCrystal_I2C.h>

Next an object of LiquidCrystal_I2C class is created. This object uses 3 parameters LiquidCrystal_I2C(address,columns,rows). This is where you will need to change the default address to the address you found earlier if it happens to be different, and dimensions of the display.

LiquidCrystal_I2C lcd(0x3F,16,2);

Once the LiquidCrystal_I2C object is declared, you can access special methods that are specific to the LCD.

In the ‘setup’ function: the init() function is called to initialize the lcd object. Next, the clear() function is called. This function clears the LCD screen and moves the cursor to the top-­left corner. The backlight() function is used to turn on the LCD backlight.

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

Next, the cursor position is set to third column and the first row of the LCD, by calling function lcd.setCursor(2,0). The cursor position specifies the location where you need the new text to be displayed on the LCD. The top left corner is considered 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 will set the cursor position at the third column and 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 a few useful functions you can use with LiquidCrystal_I2C object. Few of them are listed below:

  • home() – positions the cursor in the top-left corner of the LCD without clearing the display.
  • cursor() – displays the LCD cursor, an underscore (line) at the position of the next character to be printed.
  • noCursor() – hides the LCD cursor.
  • blink() – creates a blinking block style LCD cursor: a blinking rectangle of 5×8 pixels at the position of the next character to be printed.
  • noBlink() – disables the blinking block style LCD cursor.
  • display() – turns on the LCD screen and displays the characters that were previously printed on the display.
  • noDisplay() – turns off the LCD screen. Simply turning off the LCD screen does not clear data from the LCD memory. This means that it will be shown again when the display() function is called.
  • scrollDisplayLeft() – scrolls the contents of the display one space to the left. If you want to scroll the text continuously, you need to use this function inside a loop.
  • scrollDisplayRight() – scrolls the contents of the display one space to the right.
  • autoscroll() – turns on automatic scrolling of the LCD. If the current text direction is left-to-right (default), the display scrolls to the left, if the current direction is right-to-left, the display scrolls to the right.
  • noAutoscroll() – turns off automatic scrolling.

Create and Display Custom Characters

If you are finding characters on the display dull and unexciting, you can create your own custom characters (glyph) 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.

CGROM and CGRAM

All LCD displays based on Hitachi HD44780 controller have two types of memories that store defined characters called CGROM and CGRAM (Character Generator ROM & RAM). CGROM memory is non-volatile and can’t be modified whereas; CGRAM memory is volatile and can be modified any time.

CGROM is used for storing all permanent fonts that can be displayed by using their ASCII code. For example, if we write 0x41 then on the LCD we get character ‘A’. CGRAM is another memory that can be used for storing user defined characters.

This RAM is limited to 64 bytes. Meaning, for 5×8 pixel based LCD; up to 8 user-defined characters can be stored in the CGRAM. And for 5×10 pixel based LCD, only 4 user-defined characters are can be stored.

To define a custom character the createChar() function is used. This function accepts an array of 8 bytes. Each byte (only 5 bits are considered) in the array defines one row of the character in the 5×8 matrix. Whereas, 0s and 1s in the byte indicate which pixels in the row should be off and which should be turned on.

Custom Character Generator

Creating custom character was not easy until now! We have created a small application to help you create your custom characters. You can click on any of the 5×8 pixels below to set/clear a particular pixel. As you click on pixels, the code for the character is generated next to the grid. This code can directly be used in your Arduino sketch.

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

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

You should see the following output on the LCD:

Interfacing 16x2 LCD with Arduino Custom Character Generation Program output

Code Explanation:

After including the library and creating the LCD object, the custom character arrays are defined. Each array consists of 8 bytes, 1 byte for each row of the 5×8 led matrix. In this sketch, 8 custom characters are created.

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

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

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

lcd.createChar(0, Heart);

Next in the loop, to display the custom character we use write() function and as a parameter we use the number of the character that we want to display.

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