20×4 LCD Module Control Using Python


This article is based on my previous article 16×2 LCD Module Control Using Python and 16×2 LCD Module Control With Backlight Switch. 20×4 LCD modules are relatively easy and cheap to obtain. They have the same 16 pin interface as the 16×2 modules but still only require 6 GPIO pins on your Pi (an extra pin is required for the backlight switch).

These modules are compatible with the Hitachi HD44780 LCD controller. There are plenty available on eBay with a variety of backlight colours to choose from.

LCD Module Hardware

The pinout of the module is :

  1. Ground
  2. VCC (Usually +5V)
  3. Contrast adjustment (VO)
  4. Register Select (RS).
    RS=0: Command, RS=1: Data
  5. Read/Write (R/W).
    R/W=0: Write, R/W=1: Read
  6. Enable
  7. Bit 0 (Not required in 4-bit operation)
  8. Bit 1 (Not required in 4-bit operation)
  9. Bit 2 (Not required in 4-bit operation)
  10. Bit 3 (Not required in 4-bit operation)
  11. Bit 4
  12. Bit 5
  13. Bit 6
  14. Bit 7
  15. LED Backlight Anode (+)
  16. LED Backlight Cathode (-)

Usually the device requires 8 data lines to provide data to Bits 0-7. However this LCD can be configured to use a “4 bit” mode which allows you to send data in two chunks (or nibbles) of 4 bits. This reduces the number of GPIO connections you need when interfacing with your Pi.

Here is how I wired up my LCD :

LCD Pin Function Pi Function Pi Pin
01 GND GND P1-06
02 +5V +5V P1-02
03 Contrast
04 RS GPIO7 P1-26
05 RW GND P1-06
06 E GPIO8 P1-24
07 Data 0
08 Data 1
09 Data 2
10 Data 3
11 Data 4 GPIO25 P1-22
12 Data 5 GPIO24 P1-18
13 Data 6 GPIO23 P1-16
14 Data 7 GPIO18 P1-12
15 +5V via 560 ohm
16 GND P1-06

NOTE : The RW pin allows the device to be be put into read or write mode. I tied this pin to ground to prevent the module attempting to send data to the Pi as the Pi can not tolerate 5V inputs on its GPIO header.

Pin 3 : In order to control the contrast you can adjust the voltage presented to Pin 3. I used a 10K ohm trimmer to provide a variable voltage of 0-5V to Pin 3 which allows the contrast to be tweaked.

Pin 15 : This provides power to the backlight LED. In order to allow the backlight to be turned on and off I used a transistor (BC547, BC548 or equivalent) to switch this pin. This required an additional GPIO pin to switch the transistor but allowed my Python script to control the backlight.

Wiring Checks

Here are some sanity checks before you power up your circuit for the first time :

  • Pin 1 (GND), 3 (Contrast), 5 (RW) and 16 (LED -) ( should be tied to ground.
  • Pin 2 should be tied to 5V. Pin 15 should have a resistor inline to 5V to protect the backlight.
  • Pin 7-10 are unconnected
  • Pin 11-14 are connected to GPIO pins on the Pi


The script below is based heavily on the code presented in my 16×2 LCD Module Control With Backlight Switch article. This allows the backlight to be switched on and off as well as some basic text justification.

As usual I am using the excellent RPi.GPIO library to provide access to the GPIO within Python.

The features of this setup include :

  • 10k variable resistor to adjust the contrast
  • 2k variable resistor to adjust the backlight brightness
  • A transistor to allow the backlight to be switched on and off
  • Left, centred and right justified text

The breadboard circuit looks like this :

Contrast Adjustment

Pin 3 is routed to Ground via a 10K ohm trimming pot so that the display contrast can be adjusted.

Backlight Brightness and Switching

Pin 15/16 are in series with a 560 ohm and 2K ohm trimming pot via an NPN transitor which is activated by an additional GPIO connection. The LCD backlight is treated in exactly the same way I switch standard LEDs in my previous Control LED Using GPIO Output Pin article. Using a fixed resistance ensures the resistance can never be adjusted below 560 ohm which protects the backlight if you set the trimming pot to zero ohms. The base of the transistor is wired to an additional GPIO pin via a 27K ohm resistor.

Text Justification

The function “lcd_string” accepts a second parameter (1, 2 or 3) which determines how the text is displayed on the screen.


Here is the code :

#    ___  ___  _ ____
#   / _ \/ _ \(_) __/__  __ __
#  / , _/ ___/ /\ \/ _ \/ // /
# /_/|_/_/  /_/___/ .__/\_, /
#                /_/   /___/
#  lcd_16x2.py
#  20x4 LCD Test Script with
#  backlight control and text justification
# Author : Matt Hawkins
# Date   : 06/04/2015
# http://www.raspberrypi-spy.co.uk/

# The wiring for the LCD is as follows:
# 1 : GND
# 2 : 5V
# 3 : Contrast (0-5V)*
# 4 : RS (Register Select)
# 5 : R/W (Read Write)       - GROUND THIS PIN
# 6 : Enable or Strobe
# 7 : Data Bit 0             - NOT USED
# 8 : Data Bit 1             - NOT USED
# 9 : Data Bit 2             - NOT USED
# 10: Data Bit 3             - NOT USED
# 11: Data Bit 4
# 12: Data Bit 5
# 13: Data Bit 6
# 14: Data Bit 7
# 15: LCD Backlight +5V**
# 16: LCD Backlight GND

import RPi.GPIO as GPIO
import time

# Define GPIO to LCD mapping
LCD_RS = 7
LCD_E  = 8
LCD_D4 = 25
LCD_D5 = 24
LCD_D6 = 23
LCD_D7 = 18
LED_ON = 15

# Define some device constants
LCD_WIDTH = 20    # Maximum characters per line
LCD_CHR = True
LCD_CMD = False

LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line
LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line
LCD_LINE_3 = 0x94 # LCD RAM address for the 3rd line
LCD_LINE_4 = 0xD4 # LCD RAM address for the 4th line

# Timing constants
E_PULSE = 0.0005
E_DELAY = 0.0005

def main():
  # Main program block

  GPIO.setmode(GPIO.BCM)       # Use BCM GPIO numbers
  GPIO.setup(LCD_E, GPIO.OUT)  # E
  GPIO.setup(LCD_D4, GPIO.OUT) # DB4
  GPIO.setup(LCD_D5, GPIO.OUT) # DB5
  GPIO.setup(LCD_D6, GPIO.OUT) # DB6
  GPIO.setup(LCD_D7, GPIO.OUT) # DB7
  GPIO.setup(LED_ON, GPIO.OUT) # Backlight enable

  # Initialise display

  # Toggle backlight on-off-on

  while True:

    # Send some centred test
    lcd_string("Rasbperry Pi",LCD_LINE_2,2)
    lcd_string("Model B",LCD_LINE_3,2)

    time.sleep(3) # 3 second delay

    lcd_string("20x4 LCD Module Test",LCD_LINE_4,2)

    time.sleep(3) # 20 second delay

    # Blank display
    lcd_byte(0x01, LCD_CMD)

    time.sleep(3) # 3 second delay

def lcd_init():
  # Initialise display
  lcd_byte(0x33,LCD_CMD) # 110011 Initialise
  lcd_byte(0x32,LCD_CMD) # 110010 Initialise
  lcd_byte(0x06,LCD_CMD) # 000110 Cursor move direction
  lcd_byte(0x0C,LCD_CMD) # 001100 Display On,Cursor Off, Blink Off
  lcd_byte(0x28,LCD_CMD) # 101000 Data length, number of lines, font size
  lcd_byte(0x01,LCD_CMD) # 000001 Clear display

def lcd_byte(bits, mode):
  # Send byte to data pins
  # bits = data
  # mode = True  for character
  #        False for command

  GPIO.output(LCD_RS, mode) # RS

  # High bits
  GPIO.output(LCD_D4, False)
  GPIO.output(LCD_D5, False)
  GPIO.output(LCD_D6, False)
  GPIO.output(LCD_D7, False)
  if bits&0x10==0x10:
    GPIO.output(LCD_D4, True)
  if bits&0x20==0x20:
    GPIO.output(LCD_D5, True)
  if bits&0x40==0x40:
    GPIO.output(LCD_D6, True)
  if bits&0x80==0x80:
    GPIO.output(LCD_D7, True)

  # Toggle 'Enable' pin

  # Low bits
  GPIO.output(LCD_D4, False)
  GPIO.output(LCD_D5, False)
  GPIO.output(LCD_D6, False)
  GPIO.output(LCD_D7, False)
  if bits&0x01==0x01:
    GPIO.output(LCD_D4, True)
  if bits&0x02==0x02:
    GPIO.output(LCD_D5, True)
  if bits&0x04==0x04:
    GPIO.output(LCD_D6, True)
  if bits&0x08==0x08:
    GPIO.output(LCD_D7, True)

  # Toggle 'Enable' pin

def lcd_toggle_enable():
  # Toggle enable
  GPIO.output(LCD_E, True)
  GPIO.output(LCD_E, False)

def lcd_string(message,line,style):
  # Send string to display
  # style=1 Left justified
  # style=2 Centred
  # style=3 Right justified

  if style==1:
    message = message.ljust(LCD_WIDTH," ")
  elif style==2:
    message = message.center(LCD_WIDTH," ")
  elif style==3:
    message = message.rjust(LCD_WIDTH," ")

  lcd_byte(line, LCD_CMD)

  for i in range(LCD_WIDTH):

def lcd_backlight(flag):
  # Toggle backlight on-off-on
  GPIO.output(LED_ON, flag)

if __name__ == '__main__':

  except KeyboardInterrupt:
    lcd_byte(0x01, LCD_CMD)

Remember to update the constants at the top of the script to match the GPIO signals you are using on your Pi. As always these GPIO references are the Broadcom signal names as described in my GPIO header article.

Take a look at my other LCD Screen related posts which include details of the 16×2 version of the screen used in this post.



  1. This is excellent work, and I think I will get one of these as a external link for my RasPi!

    I do have one question though, what is the model/part number for the 20×4 LCD module, I am having some trouble finding the one pictured and am a little reluctant to get one that isn’t exactly the same ‘just in case’ the pinouts etc are different.

    It looks like HD44780 isn’t what I need to be searching on :)

  2. an ignorant, but eager, noob on

    Hey there — so I have the breakout board, some wires and the LCD screen. What I don’t have are the various resistors. I am totally eager to get going, but clearly, I have no idea what I am doing. I don’t want to fry the Pi or the LCD screen. But there are folks who are wiring directly from the GPIO to the LCD screens out there, so how neccesary are the resistors?

    I have to find a site to go over the basics of how the breakout board even works. I can’t say I understand anything, but am definitely up to learn. :)

    Thanks for the site, I’ll come back here when I have a little more knowledge of what I am doing.

    • The resistors are there to limit the current to the backlight input and to divide the voltage to the contrast input.

      The contrast resistors are not required they just allow you to tweak the contrast. The backlight resistor is required otherwise you are in danger of damaging the backlight. Some LCD modules have built in backlight resistors but it isn’t always clear if these are there … so I play it safe.

      I got all my resistors from eBay. It is worth spending a few pounds/dollars getting a collection of the more common values. In my circuits I tend to use 560, 1K, 2K, 5K, 10K and 27K values so I just bought 100 of each.

    • Depending on where you’re based Maplins (in the UK) do a reasonable bundle (of mixed values). Get the usual flesh coloured one’s not the blue ones! They all look similar but once you get used to the colours you’ll be fine.

  3. Great job, very nice project, I’m beginner and I’ll try the project with good result! But I’ve a little question, someone can help me to create a special character for degrees symbols (°) in python? I found a lot of guide but nothing in python!

    • Hi,
      searching some documentation on the HD44780 you can find the ° character under the value 223 (0xDF). You can then for example build a string you can send like this:
      line = str(temperature) + chr(223) + ‘C’

      That would make a string with the value of the variable “temperature”,the ° symbol and then a C for Celsius (… 34°C).

      Hope that helps

  4. Hello everyone.
    first of all thanks for this tutorial it helped me alot getting a 4×20 JHD 204A lcd to work with my Pi.
    i used the same wiring the first time ,with no resistor on PIN 15 (the lcd have one onboard) and i’ve wired the CONTRAST PIN to the ground.
    the LCD worked just fine for Two days.but now with the same exact wiring it doesn’t work any more!! any ideas why is this?
    ps: i had many problem with same LCD in the past when i tried to get it to work with a PIC MCU , i’ve tried every thing but it just won’t work.
    i’m begining to think that the problem is with LCD it self,could this be the case??

    thanks in advance for any help

  5. Would it be possible to use another pin as RS pin? This is now configured on P1-26 GPIO7, but I allready have another function configured on this pin.

    • Yes you could use a different GPIO pin. As long as you tweak the reference in the code it would work fine.

  6. Pedro Rodrigues on


    I have 20×4 lcd module lcm1602 with hd44780. I’m using I2c.

    Do you have sample code for using that with I2c on Raspberry Pi B+ ?
    I want do scrolling (up, down, left , right) and custom character.

  7. Hello,
    I have modified your program to use the pyRock.gpio library on the Radxa Rock Pro SBC. Can I share the modified program with the other users on the Radxa forum? I have left the first ten comment lines intact, giving you full credit and can post a link to this web page. If yes then I can email the article to you before sending it to Radxa.

    Thanks for sharing your program, it has been an inspiration to this python newbie.

  8. hi matt,

    firstly thanks for producing a great blog, i am looking to get one of these 20×4 LCD screens as an xmas project, is there any chance you can post up the exact resistors, transistors and potentiometer I need to get this working without killing my pi?
    many thanks,

  9. I made it exactly the same like you, used the same code like you, but it won’t work. My display just shows black bars in the first and third row. One thing I’ve noticed it that my display works fine with Arduino, and the characters are darker on the Arduino than on the Pi. How can I fix this? Thanks!

  10. Hi Matt,

    I’m beginner as well and I would like to have 2 questions regards to the wiring diagram:
    – what is the second variable resistors value (2k or 5k) connected to PIN15
    – on the breadboard diagram the second variable resistor has only 2 leg connected, first one is to PIN15, the second one is to positive rail via 560 ohm. What about the third pin of the variable resistor?

    • It’s 2K. When I wrote the article I intended to use a 5K but changed it to 2K. It doesn’t really matter. It only needs to allow you to increase the value from the minimum of 560.

      If you are using a variable resistor to split a voltage you use 3 legs. 1 for the maximum voltage, 1 for ground and the other which “sweeps” in-between. I do this for the contrast as it needs a variable voltage between 0 and 5V.

      If you simply want it to act as a resistor with a varying value you only need to use one of the legs and the and the leg that “sweeps” between the other two.

  11. Not working for you? I might have found your solution (i’ve researched many websites and many people have this issue). I’m a beginner and I struggled with this for hours with the same problem. White boxes, right? Here’s the solution: GPIO pin 26 (RS) is board pin 37 (raspi 2). GPIO pin 24 is actually board pin 35. You might be doing it right for the data GPIO pins, but relating the board pins with LCD pin 4 and LCD pin 6. Check those pins and make sure you’re relating them to the GPIO pins 26 and 24 (board pins 37 and 35). I was using board pins 26 and 24. Good luck. It finally worked for me (4am here! and the funs just started)

  12. Phil Rogers on

    An excellent article. I’m using a Raspberry Pi to make a controller for my home-build solar water heater. It reads temperature sensors and depending on their values, switches a pump on or off.
    Adding a display will allow me to display those temperatures and the pump status, allowing me to refine and optimise the controller software.
    Your article has saved me trying to work it all out for myself.

  13. Wolfram Esser on

    Hi, first: thanks for the tut.

    One observation/hint from my side:
    When using your Python script, my display was quite “unstable”. It was displaying the right text, the backlight worked. Seemed fine! But when I unplugged Ethernet from my Raspberry or touched the silver shielding of the Ethernet jack, my text was erased by black squares. I could even reproduce this with only moving my hand close to to the wires that connected the Raspi with the LCD. Looking at the data sheet the “black square” is equal to character 255 = 0xFF = 11111111. So I measured the GPIO pins after your python script terminated and text was displayed – they all had +5V! So obviously in this state only a smal “athmospheric disturbance” was needed for the display to fill everything witch black squares.

    I then found out that a GPIO.cleanup() – which you do at the end of your script – does switch all used GPIOs into Input mode which means they have a current attached.

    My solution for becoming my LCD stable was to:
    => remove the GPIO.cleanup() line from your skript.
    Now after your script runs, all my GPIOs have +0V and no black squares erase my text anymore when I touch or come close to the wires.

    Thanks again for your great tut!

    Wolfram from Germany

Leave A Reply