Interfacing a 16×2 LCD with Raspberry Pi

Hitachi HD44780 based 16×2 character LCD are very cheap and widely available, and is a essential part for any  projects that displays information. Using the I2C bus on Raspberry Pi ,PCF8574 IC, and Python characters/strings can be displayed on the LCD. The PCF8574 is an general purpose bidirectional 8 bit I/O port expander that uses the I2C protocol.
The LCD(HD44780) is connected in 4 bit mode as follows to the PCF8574:-

  1. import smbus
  2. from time import *
  3. # General i2c device class so that other devices can be added easily
  4. class i2c_device:
  5.  def __init__(self, addr, port):
  6.   self.addr = addr
  7.   self.bus = smbus.SMBus(port)
  8.  def write(self, byte):
  9.   self.bus.write_byte(self.addr, byte)
  10.  def read(self):
  11.   return self.bus.read_byte(self.addr)
  12.  def read_nbytes_data(self, data, n): # For sequential reads > 1 byte
  13.   return self.bus.read_i2c_block_data(self.addr, data, n)
  14. Interfacing a 16x2 LCD with Raspberry Pi
  15. class lcd:
  16.  #initializes objects and lcd
  17.  ”'
  18.  Reverse Codes:
  19.  0: lower 4 bits of expander are commands bits
  20.  1: top 4 bits of expander are commands bits AND P0-4 P1-5 P2-6
  21.  2: top 4 bits of expander are commands bits AND P0-6 P1-5 P2-4
  22.  ”'
  23.  def __init__(self, addr, port, reverse=0):
  24.   self.reverse = reverse
  25.   self.lcd_device = i2c_device(addr, port)
  26.   if self.reverse:
  27.    self.lcd_device.write(0x30)
  28.    self.lcd_strobe()
  29.    sleep(0.0005)
  30.    self.lcd_strobe()
  31.    sleep(0.0005)
  32.    self.lcd_strobe()
  33.    sleep(0.0005)
  34.    self.lcd_device.write(0x20)
  35.    self.lcd_strobe()
  36.    sleep(0.0005)
  37.   else:
  38.    self.lcd_device.write(0x03)
  39.    self.lcd_strobe()
  40.    sleep(0.0005)
  41.    self.lcd_strobe()
  42.    sleep(0.0005)
  43.    self.lcd_strobe()
  44.    sleep(0.0005)
  45.    self.lcd_device.write(0x02)
  46.    self.lcd_strobe()
  47.    sleep(0.0005)
  48.   self.lcd_write(0x28)
  49.   self.lcd_write(0x08)
  50.   self.lcd_write(0x01)
  51.   self.lcd_write(0x06)
  52.   self.lcd_write(0x0C)
  53.   self.lcd_write(0x0F)
  54.  # clocks EN to latch command
  55.  def lcd_strobe(self):
  56.   if self.reverse == 1:
  57.    self.lcd_device.write((self.lcd_device.read() | 0x04))
  58.    self.lcd_device.write((self.lcd_device.read() & 0xFB))
  59.   if self.reverse == 2:
  60.    self.lcd_device.write((self.lcd_device.read() | 0x01))
  61.    self.lcd_device.write((self.lcd_device.read() & 0xFE))
  62.   else:
  63.    self.lcd_device.write((self.lcd_device.read() | 0x10))
  64.    self.lcd_device.write((self.lcd_device.read() & 0xEF))
  65.  # write a command to lcd
  66.  def lcd_write(self, cmd):
  67.   if self.reverse:
  68.    self.lcd_device.write((cmd >> 4)<<4)
  69.    self.lcd_strobe()
  70.    self.lcd_device.write((cmd & 0x0F)<<4)
  71.    self.lcd_strobe()
  72.    self.lcd_device.write(0x0)
  73.   else:
  74.    self.lcd_device.write((cmd >> 4))
  75.    self.lcd_strobe()
  76.    self.lcd_device.write((cmd & 0x0F))
  77.    self.lcd_strobe()
  78.    self.lcd_device.write(0x0)
  79.  # write a character to lcd (or character rom)
  80.  def lcd_write_char(self, charvalue):
  81.   if self.reverse == 1:
  82.    self.lcd_device.write((0x01 | (charvalue >> 4)<<4))
  83.    self.lcd_strobe()
  84.    self.lcd_device.write((0x01 | (charvalue & 0x0F)<<4))
  85.    self.lcd_strobe()
  86.    self.lcd_device.write(0x0)
  87.   if self.reverse == 2:
  88.    self.lcd_device.write((0x04 | (charvalue >> 4)<<4))
  89.    self.lcd_strobe()
  90.    self.lcd_device.write((0x04 | (charvalue & 0x0F)<<4))
  91.    self.lcd_strobe()
  92.    self.lcd_device.write(0x0)
  93.   else:
  94.    self.lcd_device.write((0x40 | (charvalue >> 4)))
  95.    self.lcd_strobe()
  96.    self.lcd_device.write((0x40 | (charvalue & 0x0F)))
  97.    self.lcd_strobe()
  98.    self.lcd_device.write(0x0)
  99.  # put char function
  100.  def lcd_putc(self, char):
  101.   self.lcd_write_char(ord(char))
  102.  # put string function
  103.  def lcd_puts(self, string, line):
  104.   if line == 1:
  105.    self.lcd_write(0x80)
  106.   if line == 2:
  107.    self.lcd_write(0xC0)
  108.   if line == 3:
  109.    self.lcd_write(0x94)
  110.   if line == 4:
  111.    self.lcd_write(0xD4)
  112.   for char in string:
  113.    self.lcd_putc(char)
  114.  # clear lcd and set to home
  115.  def lcd_clear(self):
  116.   self.lcd_write(0x1)
  117.   self.lcd_write(0x2)
  118.  # add custom characters (0 – 7)
  119.  def lcd_load_custon_chars(self, fontdata):
  120.   self.lcd_device.bus.write(0x40);
  121.   for char in fontdata:
  122.    for line in char:
  123.     self.lcd_write_char(line)

Interfacing a 16x2 LCD with Raspberry Pi schematic

Main Program:-

  1. import pylcdlib
  2. lcd = pylcdlib.lcd(0x21,0)
  3. lcd.lcd_puts(“Raspberry Pi”,1)  #display “Raspberry Pi” on line 1
  4. lcd.lcd_puts(”  Take a byte!”,2)  #display “Take a byte!” on line 2

Save the above code as test_lcd.py and enter sudo python test_lcd.py
My code assumes that the first 4 bits of the LCD(11,12,13,14) are connected to P0,P1,P2,P3 ports on PCF8574. The next 3 ports on PCF8574(P4,P5,P6) should be connected to 4-RS, 5-R/W, 6-E.However there are other serial backpack lcd's with different pinouts. According to the wiring of your serial backpack LCD you can override the default mapping during initialization.There are 3 modes available-
lcd = pylcdlib.lcd(0x21,0)   lower 4 bits of expander are commands bits
lcd = pylcdlib.lcd(0x21,0,1)   top 4 bits of expander are commands bits AND   P0-4   P1-5   P2-6
lcd = pylcdlib.lcd(0x21,0,2)   top 4 bits of expander are commands bits AND   P0-6   P1-5   P2-4

(Update):- If you have a Raspberry Pi with a revision 2.0 board, you need to use I²C bus 1, not bus 0, so you will need to change the bus number used. In this case, the line lcd = pylcdlib.lcd(0x21,0) would become lcd = pylcdlib.lcd(0x21,1).

You can check that the device is present on the bus by using the i2cdetect program from the i2ctools package-
i2cdetect 0 -y  or i2cdetect 1 -y

Source: Interfacing a 16×2 LCD with Raspberry Pi


About The Author

Ibrar Ayyub

I am an experienced technical writer holding a Master's degree in computer science from BZU Multan, Pakistan University. With a background spanning various industries, particularly in home automation and engineering, I have honed my skills in crafting clear and concise content. Proficient in leveraging infographics and diagrams, I strive to simplify complex concepts for readers. My strength lies in thorough research and presenting information in a structured and logical format.

Follow Us:
LinkedinTwitter

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top