Connecting an LCD to your Raspberry Pi will spice up almost any project, but what if your pins are tied up with connections to other modules? No problem, just connect your LCD with I2C, it only uses two pins (well, four if you count the ground and power). I’ll show you everything you need to know to set up an LCD using I2C here, but if you aren’t familiar with I2C and the details about how it works, you might want to check out our article Basics of the I2C Communication Protocol.

The only downside to using I2C to connect your LCD is that you’re going to need some additional hardware. The standard inputs on a HD44780 16×2 LCD are parallel, so the serial I2C signal from the Raspberry Pi needs to be converted into the parallel signal the LCD uses. There are a couple ways to do this. The simplest is to get an LCD with an I2C serial interface, but you can also do it with an IC called the PCF8574. In fact, most I2C enabled LCDs use the PCF8574 to convert the I2C signal into a parallel signal anyway.

First I’ll show you how to connect the LCD and enable I2C with Raspbian Jessie. Then, I’ll show you how to program it using Python. I’ll provide examples for how to print and position the text, clear the screen, scroll text, print data from a sensor, print the date and time, and print the IP address of your Pi.

If you don’t have an I2C enabled LCD or a PCF8574, you can connect the LCD another way that uses 6 to 10 GPIO pins. To connect it with the GPIO pins and program it with C, check out Raspberry Pi LCD Set Up and Programming in C With WiringPi. To connect it with the GPIO pins and program it with Python, see Raspberry Pi LCD Set Up and Programming in Python.

You can also watch the video version of this tutorial, where I go through the set up and walk through all of the programming examples below:

Connecting the LCD

I2C (inter-integrated circuit) is also known as the two-wire interface since it only uses two wires to send and receive data. Actually it takes four if you count the Vcc and ground wires, but the power could always come from another source.

Connecting an I2C Enabled LCD

Raspberry Pi I2C LCD - I2C Backpack LCD

Connecting an LCD with an I2C serial interface is pretty self-explanatory. Connect the SDA pin on the Pi to the SDA pin on the LCD, and the SCL pin on the Pi to the SCL pin on the LCD. The ground and Vcc pins will also need to be connected. The LCD can operate with 3.3V, but it’s meant to be run on 5V, so connect it to the 5V pin of the Pi if possible.

Connecting the LCD With a PCF8574

Raspberry Pi I2C LCD - PCF8574

If you have an LCD without I2C and have a PCF8574 IC laying around, you can use it to connect your LCD with a little extra wiring. The PCF8574 is an 8 bit I/O expander which converts a parallel signal into I2C and vice-versa. The Raspberry Pi sends data to the PCF8574 via I2C. The PCF8574 then converts the I2C signal into a 4 bit parallel signal, which is relayed to the LCD.

Use the following diagram to connect the LCD to the Raspberry Pi via the PCF8574:

Raspberry Pi LCD - I2C Connection Diagram

In the diagram above, the blue wire connecting to the Raspberry Pi is the I2C SDA line. The yellow wire connecting to the Pi is the I2C SCL line.

R1: 10K Ohm resistor

Potentiometers: 10K, but can be substituted with 1K to 3K Ohm resistors

Enabling I2C on the Pi

Before we get into the programming, we need to make sure the I2C module is enabled on the Pi and install a couple tools that will make it easier to use I2C.

Enable I2C in raspi-config

First, log in to your Pi and enter sudo raspi-config to access the configuration menu. Then arrow down and select “Advanced Settings”:

Raspberry Pi LCD - I2C Connections - sudo raspi-config

Now arrow down and select “I2C Enable/Disable automatic loading”:

Raspberry Pi LCD - I2C Connections - sudo raspi-config enable i2c

Choose “Yes” at the following prompts, exit the configuration menu, and reboot to activate the settings.

Install I2C-tools and smbus

Now we need to install a program called I2C-tools, which will tell us the I2C address of the LCD when it’s connected to the Pi. So at the command prompt, enter sudo apt-get install i2c-tools.

Next we need to install SMBUS, which gives the Python library we’re going to use access to the I2C bus on the Pi. At the command prompt, enter sudo apt-get install python-smbus.

Now reboot the Pi and log in again. With your LCD connected, enter i2cdetect -y 1 at the command prompt. This will show you a table of addresses for each I2C device connected to your Pi:

Raspberry Pi LCD - I2C Connections - I2C detect

The I2C address of the LCD connected to my Pi is 21. Take note of the I2C address of your LCD, we will need it later.

Programming the LCD

We’ll be using Python to program the LCD. If this is your first time writing/running a Python program, you may want to check out How to Write and Run a Python Program on the Raspberry Pi.

Installing the Library

I found a Python I2C library that has a good set of functions and works pretty well. This library was originally posted here, then expanded and improved by GitHub user DenisFromHR.

Copy this code for the library, then save it in a file named I2C_LCD_driver.py:

# -*- coding: utf-8 -*-
# Original code found at:
# https://gist.github.com/DenisFromHR/cc863375a6e19dce359d

"""
Compiled, mashed and generally mutilated 2014-2015 by Denis Pleic
Made available under GNU GENERAL PUBLIC LICENSE

# Modified Python I2C library for Raspberry Pi
# as found on http://www.recantha.co.uk/blog/?p=4849
# Joined existing 'i2c_lib.py' and 'lcddriver.py' into a single library
# added bits and pieces from various sources
# By DenisFromHR (Denis Pleic)
# 2015-02-10, ver 0.1

"""

# i2c bus (0 -- original Pi, 1 -- Rev 2 Pi)
I2CBUS = 0

# LCD Address
ADDRESS = 0x27

import smbus
from time import sleep

class i2c_device:
   def __init__(self, addr, port=I2CBUS):
      self.addr = addr
      self.bus = smbus.SMBus(port)

# Write a single command
   def write_cmd(self, cmd):
      self.bus.write_byte(self.addr, cmd)
      sleep(0.0001)

# Write a command and argument
   def write_cmd_arg(self, cmd, data):
      self.bus.write_byte_data(self.addr, cmd, data)
      sleep(0.0001)

# Write a block of data
   def write_block_data(self, cmd, data):
      self.bus.write_block_data(self.addr, cmd, data)
      sleep(0.0001)

# Read a single byte
   def read(self):
      return self.bus.read_byte(self.addr)

# Read
   def read_data(self, cmd):
      return self.bus.read_byte_data(self.addr, cmd)

# Read a block of data
   def read_block_data(self, cmd):
      return self.bus.read_block_data(self.addr, cmd)


# commands
LCD_CLEARDISPLAY = 0x01
LCD_RETURNHOME = 0x02
LCD_ENTRYMODESET = 0x04
LCD_DISPLAYCONTROL = 0x08
LCD_CURSORSHIFT = 0x10
LCD_FUNCTIONSET = 0x20
LCD_SETCGRAMADDR = 0x40
LCD_SETDDRAMADDR = 0x80

# flags for display entry mode
LCD_ENTRYRIGHT = 0x00
LCD_ENTRYLEFT = 0x02
LCD_ENTRYSHIFTINCREMENT = 0x01
LCD_ENTRYSHIFTDECREMENT = 0x00

# flags for display on/off control
LCD_DISPLAYON = 0x04
LCD_DISPLAYOFF = 0x00
LCD_CURSORON = 0x02
LCD_CURSOROFF = 0x00
LCD_BLINKON = 0x01
LCD_BLINKOFF = 0x00

# flags for display/cursor shift
LCD_DISPLAYMOVE = 0x08
LCD_CURSORMOVE = 0x00
LCD_MOVERIGHT = 0x04
LCD_MOVELEFT = 0x00

# flags for function set
LCD_8BITMODE = 0x10
LCD_4BITMODE = 0x00
LCD_2LINE = 0x08
LCD_1LINE = 0x00
LCD_5x10DOTS = 0x04
LCD_5x8DOTS = 0x00

# flags for backlight control
LCD_BACKLIGHT = 0x08
LCD_NOBACKLIGHT = 0x00

En = 0b00000100 # Enable bit
Rw = 0b00000010 # Read/Write bit
Rs = 0b00000001 # Register select bit

class lcd:
   #initializes objects and lcd
   def __init__(self):
      self.lcd_device = i2c_device(ADDRESS)

      self.lcd_write(0x03)
      self.lcd_write(0x03)
      self.lcd_write(0x03)
      self.lcd_write(0x02)

      self.lcd_write(LCD_FUNCTIONSET | LCD_2LINE | LCD_5x8DOTS | LCD_4BITMODE)
      self.lcd_write(LCD_DISPLAYCONTROL | LCD_DISPLAYON)
      self.lcd_write(LCD_CLEARDISPLAY)
      self.lcd_write(LCD_ENTRYMODESET | LCD_ENTRYLEFT)
      sleep(0.2)


   # clocks EN to latch command
   def lcd_strobe(self, data):
      self.lcd_device.write_cmd(data | En | LCD_BACKLIGHT)
      sleep(.0005)
      self.lcd_device.write_cmd(((data & ~En) | LCD_BACKLIGHT))
      sleep(.0001)

   def lcd_write_four_bits(self, data):
      self.lcd_device.write_cmd(data | LCD_BACKLIGHT)
      self.lcd_strobe(data)

   # write a command to lcd
   def lcd_write(self, cmd, mode=0):
      self.lcd_write_four_bits(mode | (cmd & 0xF0))
      self.lcd_write_four_bits(mode | ((cmd << 4) & 0xF0))

   # write a character to lcd (or character rom) 0x09: backlight | RS=DR<
   # works!
   def lcd_write_char(self, charvalue, mode=1):
      self.lcd_write_four_bits(mode | (charvalue & 0xF0))
      self.lcd_write_four_bits(mode | ((charvalue << 4) & 0xF0))
  
   # put string function with optional char positioning
   def lcd_display_string(self, string, line=1, pos=0):
    if line == 1:
      pos_new = pos
    elif line == 2:
      pos_new = 0x40 + pos
    elif line == 3:
      pos_new = 0x14 + pos
    elif line == 4:
      pos_new = 0x54 + pos

    self.lcd_write(0x80 + pos_new)

    for char in string:
      self.lcd_write(ord(char), Rs)

   # clear lcd and set to home
   def lcd_clear(self):
      self.lcd_write(LCD_CLEARDISPLAY)
      self.lcd_write(LCD_RETURNHOME)

   # define backlight on/off (lcd.backlight(1); off= lcd.backlight(0)
   def backlight(self, state): # for state, 1 = on, 0 = off
      if state == 1:
         self.lcd_device.write_cmd(LCD_BACKLIGHT)
      elif state == 0:
         self.lcd_device.write_cmd(LCD_NOBACKLIGHT)

   # add custom characters (0 - 7)
   def lcd_load_custom_chars(self, fontdata):
      self.lcd_write(0x40);
      for char in fontdata:
         for line in char:
            self.lcd_write_char(line)         
         

There are a couple things you may need to change depending on your set up. In line 19 there is a function that defines the port for the I2C bus (I2CBUS = 0). Older Raspberry Pi’s used port 0, but newer models use port 1. So depending on which RPi model you have, you might need to change this from 0 to 1.

Next, put the I2C address of your LCD in line 22 of the library code. For example, my I2C address is 21, so I will change line 22 to ADDRESS = 0x21.

Write to Display

The following is a bare minimum “Hello World!” program to demonstrate how to initialize the LCD:

import I2C_LCD_driver
from time import *

mylcd = I2C_LCD_driver.lcd()

mylcd.lcd_display_string("Hello World!", 1)

Position the Text

The function mylcd.lcd_display_string() allows you to print text to the screen and also chose where to position it. The function is used as mylcd.lcd_display_string(“TEXT TO PRINT”, ROW, COLUMN). The following code prints “Hello World!” to row 2, column 3:

import I2C_LCD_driver
from time import *

mylcd = I2C_LCD_driver.lcd()

mylcd.lcd_display_string("Hello World!", 2, 3)

The rows are numbered 1 – 2, while the columns are numbered 0 – 15 on a 16×2 LCD. So for example, to print “Hello World!” at the first column of the top row, you would use: mylcd.lcd_display_string(“Hello World!”, 1, 0).

Clear the Screen

The function mylcd.lcd_clear() clears the screen:

import I2C_LCD_driver
from time import *

mylcd = I2C_LCD_driver.lcd()

mylcd.lcd_display_string("This is how you", 1)
sleep(1)

mylcd.lcd_clear()

mylcd.lcd_display_string("clear the screen", 1)
sleep(1)

mylcd.lcd_clear()

Blinking Text

We can use a simple while loop with the mylcd.lcd_display_string() and mylcd.lcd_clear() functions to create a continuous blinking text effect:

import time
import I2C_LCD_driver
mylcd = I2C_LCD_driver.lcd()

while True:
    mylcd.lcd_display_string(u"Hello world!")
    time.sleep(1)
    mylcd.lcd_clear()
    time.sleep(1)
    

You can use the time.sleep() function (line 7), to change the time (in seconds) the text stays on. The time the text stays off can be changed in the time.sleep() function in line 9. To end the program, press Ctrl-C.

Print the Date and Time

The following program prints the current date and time to the LCD:

import I2C_LCD_driver
import time
mylcd = I2C_LCD_driver.lcd()


while True:
    mylcd.lcd_display_string("Time: %s" %time.strftime("%H:%M:%S"), 1)
    
    mylcd.lcd_display_string("Date: %s" %time.strftime("%m/%d/%Y"), 2)
    

Print Your IP Address

This code prints the IP address of your ethernet connection (eth0). To print the IP of your WiFi connection, change eth0 to wlan0 in line 18:

import I2C_LCD_driver
import socket
import fcntl
import struct

mylcd = I2C_LCD_driver.lcd()

def get_ip_address(ifname):
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    return socket.inet_ntoa(fcntl.ioctl(
        s.fileno(),
        0x8915, 
        struct.pack('256s', ifname[:15])
    )[20:24])

mylcd.lcd_display_string("IP Address:", 1) 

mylcd.lcd_display_string(get_ip_address('eth0'), 2)

Infinite Scroll Text Right to Left

This program will scroll a text string from the right side of the LCD to the left side in an infinite loop:

import I2C_LCD_driver
from time import *

mylcd = I2C_LCD_driver.lcd()

str_pad = " " * 16
my_long_string = "This is a string that needs to scroll"
my_long_string = str_pad + my_long_string

while True:
    for i in range (0, len(my_long_string)):
        lcd_text = my_long_string[i:(i+16)]
        mylcd.lcd_display_string(lcd_text,1)
        sleep(0.4)
        mylcd.lcd_display_string(str_pad,1)
        

Scroll Text Right to Left Once

The following code slides text onto the screen from right to left one time, then stops and leaves a cleared screen.

import I2C_LCD_driver
from time import *

mylcd = I2C_LCD_driver.lcd()

str_pad = " " * 16
my_long_string = "This is a string that needs to scroll"
my_long_string = str_pad + my_long_string

for i in range (0, len(my_long_string)):
 lcd_text = my_long_string[i:(i+16)]
 mylcd.lcd_display_string(lcd_text,1)
 sleep(0.4)
 mylcd.lcd_display_string(str_pad,1)
 

Scroll Text Left to Right Once

This program slides text onto the screen from left to right one time, then stops and leaves the first 16 characters of the text string on the screen.

import I2C_LCD_driver
from time import *

mylcd = I2C_LCD_driver.lcd()

padding = " " * 16
my_long_string = "This is a string that needs to scroll"
padded_string = my_long_string + padding

for i in range (0, len(my_long_string)):
 lcd_text = padded_string[((len(my_long_string)-1)-i):-i]
 mylcd.lcd_display_string(lcd_text,1)
 sleep(0.4)
 mylcd.lcd_display_string(padding[(15+i):i], 1)
 

Custom Characters

Each character on a Hitachi HD44780 LCD is an array of 5 x 8 pixels. You can create any pattern you want and print it to the display as a custom character. Up to 8 custom characters can be defined and stored in the LCD’s character memory. This custom character generator will help create the bit array we need to define the characters in the LCD memory.

Printing a Single Custom Character

The following code generates a “<” character:

import I2C_LCD_driver
from time import *

mylcd = I2C_LCD_driver.lcd()

fontdata1 = [      
        [ 0b00010, 
          0b00100, 
          0b01000, 
          0b10000, 
          0b01000, 
          0b00100, 
          0b00010, 
          0b00000 ],
]

mylcd.lcd_load_custom_chars(fontdata1)
mylcd.lcd_write(0x80)
mylcd.lcd_write_char(0)

Printing Multiple Custom Characters

This program prints a large right pointing arrow (→) to the screen:

import I2C_LCD_driver
from time import *

mylcd = I2C_LCD_driver.lcd()

fontdata1 = [
        # char(0) - Upper-left character
        [ 0b00000, 
          0b00000, 
          0b00000, 
          0b00000, 
          0b00000, 
          0b00000, 
          0b11111, 
          0b11111 ],

        # char(1) - Upper-middle character
        [ 0b00000, 
          0b00000, 
          0b00100, 
          0b00110, 
          0b00111, 
          0b00111, 
          0b11111, 
          0b11111 ],
        
        # char(2) - Upper-right character
        [ 0b00000, 
          0b00000, 
          0b00000, 
          0b00000, 
          0b00000, 
          0b00000, 
          0b10000, 
          0b11000 ],
        
        # char(3) - Lower-left character
        [ 0b11111, 
          0b11111, 
          0b00000, 
          0b00000, 
          0b00000, 
          0b00000, 
          0b00000, 
          0b00000 ],
       
        # char(4) - Lower-middle character
        [ 0b11111, 
          0b11111, 
          0b00111, 
          0b00111, 
          0b00110, 
          0b00100, 
          0b00000, 
          0b00000 ],
        
        # char(5) - Lower-right character
        [ 0b11000, 
          0b10000, 
          0b00000, 
          0b00000, 
          0b00000, 
          0b00000, 
          0b00000, 
          0b00000 ],
]

mylcd.lcd_load_custom_chars(fontdata1)

mylcd.lcd_write(0x80)
mylcd.lcd_write_char(0)
mylcd.lcd_write_char(1)
mylcd.lcd_write_char(2)

mylcd.lcd_write(0xC0)
mylcd.lcd_write_char(3)
mylcd.lcd_write_char(4)
mylcd.lcd_write_char(5)

Print Data from a Sensor

The code below will display data from the DHT11 temperature and humidity sensor. Follow this tutorial for instructions on how to set up the DHT11 on the Raspberry Pi. The DHT11 signal pin is connected to BCM pin 4 (physical pin 7 of the RPi).

Temperature is displayed on line 1, and humidity on line 2:

import RPi.GPIO as GPIO
import dht11
import I2C_LCD_driver

from time import *

mylcd = I2C_LCD_driver.lcd()

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()

while True:
  
  instance = dht11.DHT11(pin = 4)
  result = instance.read()

# Uncomment for Fahrenheit:
# result.temperature = (result.temperature * 1.8) + 32 

  if result.is_valid():
    mylcd.lcd_display_string("Temp: %d%s C" % (result.temperature, chr(223)), 1)
    mylcd.lcd_display_string("Humidity: %d %%" % result.humidity, 2)
    

For Fahrenheit, un-comment lines 18 and 19, and change the C to an F in line 22.

You can also change the signal pin of the DHT11 input in line 15. The pin numbers in this program refer to the BCM pin numbers.

By inserting the variable from your sensor into the mylcd.lcd_display_string() function (line 22 in the code above) you can print the sensor data just like any other text string.

These programs are just basic examples of ways you can control text on your LCD. Try changing things around and combining the code to get some cool effects. For example, you can make some interesting animations by scrolling with custom characters. Don’t have enough screen space to output all of your sensor data? Just print and clear each reading for a couple seconds in a loop.

Let us know in the comments if you have any questions or trouble setting this up. Also leave a comment if you have any other ideas on how to get some cool effects, or just to share your project!


Need an easy-to-use way to design circuits and layout PCBs?

Try EasyEDA, a free circuit design software that also offers low cost, high quality PCB manufacturing.