Using Character LCDs in Arduino and Raspberry Pi Projects

LCDs (Liquid Crystal Displays) are a great tool for any Maker to have in their toolbox. They’re an easy component to insert into any project, and can be incredibly useful. In this article, I’m going to help you get started with using a character LCD in your project. I’ll discuss a little bit of history, describe how they work, and show you how to interact with the LCD to make it useful on both an Arduino and a Raspberry Pi.

Table of Contents

A Brief History and Overview of LCDs

LCD technology has been around for a long time. The existence of liquid crystals was actually discovered back in the late 1800s when an Austrian chemist was experimenting with carrots. It wasn’t until the 1960s, however, when George Heilmeier at RCA built the first prototype that used liquid crystals for an electronic display.

The core principles behind LCDs involve changing the polarity of light via an electrical charge. By manipulating a light wave’s polarity, you can control whether light passes through a polarized display or not, turning a segment of the display on or off.

There are many different types of LCDs available. In the simplest form, a seven-segment display on an old calculator or a digital alarm clock is an LCD. In the more complex form, you have LCD screens on TVs, computers, tablets, and phones.

For many Maker projects, one of the more commonly used LCDs is the character LCD. Character LCDs have controller chips onboard with built-in character sets, which makes them easy to interface with. The most common controller used in these LCDs is a controller built by Hitachi, called the HD44780. Although Hitachi no longer produces this controller, the instruction set has become a standard among character LCDs. It’s highly likely that any character LCD that you pick up today will be compatible with the HD44780 instruction set.

Character LCDs are identified using screen size – for example, 16×2. These two numbers refer to the number of characters (16) and the number of lines (2) on the display. Character LCDs come in various sizes, so you’ll sometimes see 16×4, 20×2, 20×1, and other configurations as well.

For the remainder of this article, I’m going to focus on the 16×2 LCD as an example to illustrate how they work. However, this information also applies to any other HD44780-based LCD as well.

Why We Need a Controller

If you look closely at a 16×2 display, you’ll notice that each of the 32 characters contains a grid of smaller pixels, typically 5×8 squares. To use the LCD display, you need to send the appropriate electrical charge to each pixel in order to turn it on or off. Now, there’s a lot of pixels in that display (40 per character x 32 characters = 1280 pixels), so there’s no way you’re going to be able to wire each pixel up to an individual pin of your micro controller to manage the signal at each one.

So to help with running the display, character LCDs have a controller built-in. As mentioned earlier, the most common controller used by character displays is the Hitachi HD44780. Your micro controller will talk to the HD44780 controller, which in turn enables or disables the charge at the appropriate pixels.

You don’t need to send each individual pixel to the controller, however – the HD44780 has built-in character sets. So, if you want to display a letter, you can just send the ASCII character code of the letter to the controller, and it will do the work of lighting up the right pixels for you. This saves you from having to program in the pixel map of each standard character that you want to display. If you do have custom characters that you want to display, however, the HD44780 can handle that, as well, which I’ll cover in more detail later in this article.

How Character LCDs Work

Now, I’m going to walk you through the high-level process for getting data to show on your LCD. There are 4 pieces to this:

  1. Initialize the LCD
  2. Set the Mode
  3. Load the Data Pins
  4. Execute the Instruction

Initialize the LCD

Once the LCD is powered up, the first step is to execute the initialization sequence. The HD44780 has an internal initialization routine that runs by default when the component is powered. This internal routine, however, doesn’t initialize the display for most configurations. Therefore, you’ll want to run your own initialization routine.

The initialization routine for the HD44780 is documented on pages 45 and 46 of the HD44780 datasheet. I won’t take the time to go through this in detail here, but at a high level, it consists of sending a sequence of commands to the LCD with minimum waiting periods between commands.

Set the Mode

The next step is to put the LCD in the appropriate mode. This is done using the RS, or “Register Select” pin. There are two modes for you to use – Command mode and Data mode.

When the LCD is in Command mode, you can instruct it to do things like clearing the display, moving the cursor, or shifting the entire display to the left or right. To enter Command mode, you set the RS pin to LOW. The HD44780 instruction set has several different commands that you can send it.

Data mode, on the other hand, is used to send the data to the LCD that you want displayed on the screen in the character block. You can set the RS pin to Data mode by putting it in HIGH. The HD44780 uses standard ASCII character codes to represent the data that you want it to display.

Load the Data Pins

To use the LCD, you first need to decide how many bits you’re going to use to send data to the display. Standard character LCDs have 8 data pins, labeled from D0 – D7. These 8 pins represent 8 bits of data that you will potentially send to the LCD to get it to do something. For example, if you were to send the character ‘A’ to the display, you would put 01000001 on the data lines (this is 65 in decimal, which is the ASCII character code for ‘A’).

8 pins is a lot to take up on smaller micro-controllers such as an Arduino. After all, those pins could be used for better things! Fortunately, there is an alternative – you can save some pins by putting the LCD into 4-bit mode. When in 4-bit mode, you would send the data to the LCD using only 4 data pins, but in two operations. If you were to send the character ‘A’ in 4-bit mode, you would first send the 4 lower bits (0001) in the 1st operation and then send the 4 upper bits (0100) in the 2nd operation.

The disadvantage of using 4-bit mode is that data takes twice as long to reach the LCD, so there is a bit of latency. In most projects, though, I think it’s worth the sacrifice to save 4 pins for other things. And it’s unlikely that you’ll even notice the latency.

Execute the Instruction

After you put the LCD in the correct mode, you can then send it data to instruct it to execute a command or display a character. The basic concept is pretty simple. You activate either a HIGH or LOW signal on the data pins (D0 – D7) that represent the command or character that you want to send. Once you’ve put the appropriate signal on the data pins, you then flip the Enable pin (EN) to HIGH for a few milliseconds, and then back down to LOW.

The HD44780 is watching that Enable pin, so when it sees that it’s HIGH, it knows that there’s data for it to grab on the data pins. Once it goes back to LOW, it executes the command or displays the character it received.

Example Command Sequence

To illustrate how these 4 steps come together to execute a command, let’s look at an example. Let’s say that you want to move the cursor on the display one square to the right. To execute that command, you would do the following:

  1. Set the RS pin to LOW (for Command mode)
  2. Send 00010100 (20 in decimal) to the data pins. In 8-bit mode, this would mean setting D3 and D5 to HIGH, while setting the other data pins to LOW.
  3. Set the EN pin to HIGH
  4. Wait a few milliseconds
  5. Set the EN pin to LOW

That’s pretty much all there is to it. Now that you understand how these LCDs work, let’s take a look at wiring them up to a project and using them.

How to Wire Up a 16×2 LCD

When dealing with a 16×2 LCD, you’ll find that the pin layout is the same as other sizes of character LCDs. Even if you don’t have the data sheet for your 16×2 LCD, it’s highly likely that you’ll be able to get it working with the following layout.

Character LCDs typically have 16 pins. Some will have 14 pins, but the only difference is that the 14-pin displays are missing the backlight. Otherwise, they function the same as a 16-pin display would. Here’s what all of those pins do.

Pins 1 and 2: Powering the LCD

The VSS and VDD pins (typically pins 1 and 2) are ground and +5V power, respectively. So first, you’ll want to wire these pins to ground and +5V on your micro controller in order to feed power to the LCD.

Pin 3: Contrast Adjustment

Pin #3 will be labelled V0. This pin controls the contrast of the display. You can either wire this up to a potentiometer to make the contrast adjustable, or you can wire a resistor in between this pin and ground to “hard code” a contrast setting.

For finished projects that you plan to keep around, you probably will want a potentiometer to allow the user to adjust the contrast. LCD contrast varies in different climates, so if it’s really cold or hot the display may not be readable with a hard-wired contrast setting. If you’re not sure of what to do and you’re just using it in a project in your lab, you can just use a 2K – 3K Ohm resistor and you should be able to see the screen well enough.

Pin 4: Setting Command vs Data Mode

As discussed earlier, the HD44780 operates in two different modes – command mode and data mode. The mode that the controller is in dictates what it does with the data that you send it on the data pins. The Register Select (RS) pin controls which mode is in use for the next operation.

During the normal operation of your LCD, your micro-controller will need to switch back and forth between these two modes by setting the RS pin to HIGH (Data mode) or LOW (Command mode). This pin, therefore, must be connected to one of the digital output pins on your microcontroller, as it will be controlled by your code.

Pin 5: Reading vs Writing

You may not have realized it – your LCD not only has the ability to show information on the screen, but you can also read what’s currently being displayed on the screen. Pin 5 is typically labeled RW for Read/Write. If you set this pin to HIGH, the LCD controller will function in Read mode and allow you to read the screen. If it’s set to LOW, then the LCD is in Write mode.

I’ll be honest; I’ve never found much use for Read mode in my own projects. I typically know what the LCD is displaying because I keep track of some form of state of the device in code. If you’re like me, then you can just wire this pin directly into ground, permanently setting Write mode for your project.

Pin 6: Signaling that There’s Data to Grab

After you’ve loaded your data pins with the information that you want to send to the LCD controller, you will then use the Enable pin to tell it there’s data there. The HD44780 looks for data in what’s referred to as a “falling edge” scenario. This occurs when the signal on this pin goes from HIGH to LOW.

So, this pin will normally be left in the LOW state. Then you would load up your data on the data pins, and flip this pin to HIGH and then LOW again.

Because this signal needs to be controlled via your microcontroller, you’ll need to wire up the Enable pin to one of the digital pins on your Arduino or Pi.

Pins 7 – 14: Sending Data

As mentioned earlier, data is sent to the LCD using the 8 data pins, labeled D0 – D7. Starting at D0, each pin represents a bit of data that you send to the LCD. You can send 8 bits at a time by putting a bit on each of the data pins. By putting a LOW signal on the pin, you’re indicating that the bit is 0. And conversely, by putting a HIGH signal on the pin, you’re telling the LCD that the bit is a 1.

If you’re operating the LCD in 4-bit mode, then you’ll only use pins D4 – D7 for sending data. Consequently, you’ll split the 8-bit commands in half and send only 4 bits at a time. When in 4-bit mode, the LCD won’t complete the command until all 8 bits are received.

Pins 15 and 16: Powering the Backlight

If your LCD has 16 pins, then it has a backlight. The backlight will be driven by running positive voltage and ground to the last 2 pins – 15 and 16, often labelled A and K, or Anode (+) and Cathode (-). You’ll want to look at the data sheet for your LCD to determine how much voltage you need for the backlight, but if your data sheet isn’t available, you can wire in +5V for maximum brightness, or use a resistor or potentiometer to adjust it to a lower brightness.

Using the backlight is as simple as it seems – just set the backlight power pin to HIGH to turn it on, and set it to LOW to turn it off.

Next Steps

Now that you have a basic understanding of what each pin in a character LCD does, it’s time to move on to using your LCD in either an Arduino or a Raspberry Pi project.

Using a 16×2 LCD in an Arduino Project

Now the fun begins! Let’s take a look at how you can incorporate your LCD into your Arduino project.

Wiring for Arduino

If you’ve been following along so far, then you’ll realize that the minimum amount of pins to use for your LCD is 8. That’s two for +5V and Ground, plus 2 for RS and EN, plus 4 data pins (assuming that you’ll operate in 4-bit mode). If you want to operate in 8-bit mode, then you’ll take up for 4 more pins for the remaining 4 bits. But as any Arduino user can attest, the fewer pins the better.

Wiring is pretty simple. If you’re using a breadboard, you can use the following layout:

The digital pins that you select for RS, EN, and D4 – D7 can vary. You’ll notice that I have +5V and Ground run into the rails of my breadboard.

The Code

Arduino makes it incredibly easy to use the LCD, by providing a library called LiquidCrystal.h. This library has everything you need in order to use the LCD in your Arduino project with only a couple lines of code. Let’s look at a simple LCD sketch, and then we’ll dig into the major pieces.

#include <LiquidCrystal.h>
const int RS = 12, EN = 11, D4 = 5, D5 = 4, D6 = 3, D7 = 2;
LiquidCrystal lcd(RS, EN, D4, D5, D6, D7);

void setup() {
  lcd.begin(16, 2);
  lcd.setCursor(0,0);
  lcd.print("Hello, World!");
}

void loop() { }

Before we can display anything on the LCD, it first must be initialized. This occurs in two steps, using the library. First, you need to create a new LiquidCrystal object, just as we’ve done in line #3 above. In doing so, notice that I passed in the pin assignments for the RS and EN pins, as well as the 4 data pins. If you want to use 8-bit mode instead, you can pass in the pin assignments for all 8 data pins, and the library will understand that you want 8-bit mode. For example, you might use the following code for 8-bits:

const int RS = 12, EN = 11, D0 = 9,D1 = 8, D2 = 7, D3 = 6, D4 = 5, D5 = 4, D6 = 3, D7 = 2;
LiquidCrystal lcd(RS, EN, D0, D1, D2, D3, D4, D5, D6, D7);

Next, you’ll need to call the begin method to initialize the display. In doing so, you’ll need to pass in the appropriate number of rows and columns for your LCD. In my example, since I’m using a 16×2 LCD, that command is lcd.begin(16, 2);. You only need to run this command once, so you’re best off putting it in the setup() function.

Now you’re almost ready to display text. First, however, you need tell the LCD where you want to put the text by running the setCursor method with the row and column number. For example, lcd.setCursor(0, 0); will write the text in the upper left corner of the display.

Finally, you can just call the print method with the data that you want to display. In my example, I’m displaying “Hello, World!” with lcd.print("Hello, World!");.

Additional Methods

What I’ve shown you above is the basics of what it takes to display some text. However, the LiquidCrystal library allows you to call more of the native functions of the HD44780 controller, which give you more control over what you’re displaying on your LCD.

In the following table, I’ve laid out a few other interesting things that you can do:

MethodDescriptionExample
clearClears the screen and moves the cursor to 0, 0.lcd.clear();
blinkEnables the blinking cursorlcd.blink();
noBlinkDisables the blinking cursorlcd.noBlink();
autoscrollTurns on automatic scrolling of the textlcd.autoscroll();

Custom Characters

You also have the ability to create custom characters with the LiquidCrystal library. It’s a little more involved than just displaying text, but along with it, you can display any type of information that you’d like, such as different shapes, images, progress bars, etc.

When defining a custom character, you create a map that corresponds to the individual pixels in each character of the LCD. As I mentioned earlier in this article, each character in the LCD is composed of 8 rows of 5 pixels each, for a total of 40 pixels. When creating a custom character, you need to tell the LiquidCrystal library which pixels in each row are turned on or off.

You’ll do this by building an 8-byte array. In this array, each byte represents a row, since there are 8 rows in each character. Each bit within the byte represents the column of the pixel you’re controlling. One thing to keep in mind is that bits in a byte are read from right to left. So the rightmost bit (called the Least Significant Bit) is the first bit (bit 0), followed by the bit to the left of it (bit 1), and so forth.

The 5 pixels in the character block of your LCD map to bits 0 through 4 in the byte.

You’ll notice, that there are 3 bits left over in each byte, since you only have 5 pixels in each row of the LCD. You’ll just keep those remaining 3 bits (bits 5, 6, and 7) set to 0. When you assign the byte value in Arduino, you’ll typically leave the first 3 bits out and the compiler will assume they are 0.

For example, these two lines are equivalent:

byte myByte1 = B11010;
byte myByte2 = B00011010;

By the way, the ‘B’ modifier in front of the binary value indicates to the Arduino compiler that this value is meant to be read as binary. If you leave it off, Arduino will treat it like an integer, which you don’t want in this case.

To create a Byte array with 8 bytes in Arduino, you would declare a variable like the following. This will set aside the memory for the array, but it doesn’t initialize it with any data.

byte myCustomChar[8];

If you want to initialize the byte array with some data, such as the 8 bytes that represent your custom character, you can put them in curly brackets in the variable declaration:

byte myCustomChar[8] = {
	B00100,
	B00100,
	B11111,
	B00100,
	B01110,
	B11111,
	B00100,
	B00100
};

Otherwise, you can set them one byte at a time by addressing the specific element in the array, such as myCustomChar[4] = B01110;.

It can be a bit tedious to create custom characters by hand. Fortunately, there are several tools that others have built, which can help. The one I like to use is a web-based tool that actually gives you the Arduino code that you can copy and paste into your project: https://omerk.github.io/lcdchargen/.

After you’ve created the byte array that contains your custom character, you then call the createChar(location, charmap[]) method in your setup() function. The createChar method stores your custom character in memory on the LCD. The HD44780 provides 8 memory slots for custom characters, so you can store up to 8 custom characters at a time. The slot that your custom character takes up is determined by the location parameter that you pass in (1 – 8). For example, to store your custom character in the first slot, you would use the following code:

lcd.createChar(1, myCustomChar);

Now that your custom character is in memory on the LCD, you just need to tell the controller to display it. The way to do this is to put the display into Data mode, and pass in the memory location of your custom character (1 – 8). The LiquidCrystal library has a handy method for writing something in Data mode – the write(value) method. The value that you pass in is the memory address of custom character, which is 1 in our case:

lcd.write(1);

If you store custom characters in the other memory locations, you just need to reference the appropriate location in the write command in order for the LCD to display your character.

Using a 16×2 LCD in a Raspberry Pi Python Project

The principles of how character LCDs work are the same regardless of which platform you use. The Raspberry Pi, however, doesn’t have an official library for controlling the HD44780 like the Arduino does. Instead, you’ll need to find a library that’s made for the language that you’re using in your Raspberry Pi project. Since Python is one of the more common languages used on the Pi, I’m going to show you how to interface with a character LCD through Python.

Step 1: Install Pip

The library we’re going to use here is actually a CircuitPython library developed by Adafruit, called CharLCD. The easiest way to install this library is through a Python package manager called pip. Depending on the version of Raspberry Pi OS you used, you may or may not already have pip installed. To install it, open the Terminal and run the following command:

sudo apt-get install python3-pip

Step 2: Install CharLCD

Next, install CharLCD by running the following command in your terminal:

sudo pip3 install adafruit-circuitpython-charlcd

Step 3: Wire up the LCD to the Pi

It’s time to actually wire up the LCD to the Raspberry Pi. You can use whichever GPIO pins you’d like on the Pi. For this example, however, I’m using the following wiring configuration on a 3rd generation Pi. You’ll need to reference your configuration in the Python code, so remember which GPIO pins you wire the LCD to.

Step 4: Use the LCD in Python

Once you have the libraries installed, using CharLCD in your Python project isn’t difficult. First, you’ll need to import the character LCD library, by placing the following import statement at the top of your code:

import adafruit_character_lcd.character_lcd

Then you’ll need to create and initialize an LCD object, passing in variables for each of the GPIO pins that the LCD is using, along with the number of columns and rows in your character LCD:

lcd = adafruit_character_lcd.character_lcd.Character_LCD_Mono(pinRS, pinEN, pinD4, pinD5, pinD6, pinD7, numCols, numRows)

Once it’s created, you can then display text by setting the lcd.message property to the text string you want, like this:

lcd.message = "What's Ken Making?"

To illustrate how it all comes together, here’s a simple (but complete) Python example for using the character LCD to display some simple text:

# import the CharLCD, board, and digital libraries
import board
import digitalio
import adafruit_character_lcd.character_lcd as charlcd

# define the number of rows and columns
numRows = 2
numCols = 16

# define the GPIO pins used
pinRS = digitalio.DigitalInOut(board.D7)
pinEN = digitalio.DigitalInOut(board.D8)
pinD4 = digitalio.DigitalInOut(board.D25)
pinD5 = digitalio.DigitalInOut(board.D24)
pinD4 = digitalio.DigitalInOut(board.D23)
pinD3 = digitalio.DigitalInOut(board.D18)

# Initialize an LCD object
lcd = charlcd.Character_LCD_Mono(pinRS, pinEN, pinD4, pinD5, pinD6, pinD7, numCols, numRows)

# Clear the LCD screen
lcd.clear()

# Display a message
lcd.message = "What's Ken\n Making?"

Additional Features

Adafruit’s CharLCD CircuitPython library also has functions and properties that let you do other things with the LCD. Here’s a few that you might find useful:

Function or PropertyDescriptionCode Example
.cursorSet it to True or False to show the cursor on the LCDlcd.cursor = True
.blinkSet to True or False to blink the cursorlcd.blink = True
.move_left()Call this function to move the message one space to the left. You can use this to scroll text by calling this function in a looplcd.move_left()
.move_right()Similar to the move_left() function, this will move the message one space to the rightlcd.move_right()
.backlightIf you’ve wired the backlight into a GPIO pin, you can set this property to True or False to turn the backlight on or offlcd.backlight = True

Wrap Up

Whew, this was quite a long journey down the world of character LCDs. We covered a lot of ground here, and I hope you found it helpful!  Here’s a recap of what we discussed:

  • LCD technology has been around for a while, and you learned a little bit about how it works.
  • You learned about the HD44780 controller and how that operates. The HD44780 is the most common character LCD controller that you’ll find today, so this knowledge should be universal to any character LCD that you pick up.
  • I went over the proper wiring of a standard character LCD and discussed what each pin does.
  • We took a deeper look at how to wire a 16×2 LCD with an Arduino, and how to use the LiquidCrystal library to control it.
  • We also dove into the wiring for a Raspberry Pi and how to use the Adafruit CharLCD library to control your LCD in Python.

Thanks for taking the time to read this article! If you enjoyed it, found it helpful, or have any questions about character LCDs, feel free to leave a comment below.

One comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s