Introduction
The HD44780 is a chip that drives simple 16x2 LCD character displays.
They are extremely inexpensive, and a fully integrated HD44780 and LCD display can be purchased for under $4. They provide an 8-bit parallel interface to the outside world which is used to both issue configuration commands and program the display registers, and there appears to be pretty good drivers for Arduino out there.
However there is much less information available on how to program these displays with Raspberry Pi, and although Adafruit provides an HD44780 driver for Raspberry Pi, it is not thoroughly documented. The datasheet is also a bit opaque, and there are a couple of subtleties that are not clearly documented anywhere; as a result, I struggled a bit in figuring out exactly how to make this display work.
I did ultimately sort through all the issues, and what follows are some of the notes I took while getting there.
Basic physical interface
The HD44780 display that I bought and used has sixteen interface pins; five provide power, ground, and contrast control, and eleven are used to program the device:
There are plenty of wiring guides around (e.g., the Adafruit Character LCD guide) which are mostly complete. However, there are a few additional points to make:
The E
(or EN
) pin is extremely sensitive to outside interference. I
had to install a bypass capacitor between the EN
pin and ground to suppress
spurious command signals that were triggered as I waved my hand near some of
my breadboard wires. I used a 10 μF capacitor out of convenience, but
a smaller capacitance is probably better since it will allow you to issue
commands to the HD44780 controller at a higher frequency. My bypass capacitor
is visible in the above photo.
If you want to use the 8-bit command interface (using eight GPIO pins
instead of four), just wire D0
through D3
to additional GPIO ports. The
8-bit interface is conceptually simpler and is very convenient if you attach
an 8-bit shift register to the display.
Basic command structure
The HD44780 provides a parallel I/O interface where pins D0
- D7
, RW
, and
RS
are set either high or low, and a clock pulse is set to the EN
pin to
tell the controller to read the state of all pins and act on them. The most
important pins are
RS
, which indicates if you are sending a command (0) or updating the display (1)RW
, which indicates if you want to useD0
-D7
to write (0) or read (1). Most guides just keep this pin grounded (always write, never read) but you can use the memory on the display controller to write and read arbitrary data if you want.D0
-D7
are used to encode the command you wish to issue to the controllerE
(orEN
) is used to tell the controller to either read the state of the aforementioned pins and act (whenRS
is low), or populate them with the state saved on the controller (whenRS
is high)
The controller takes time to actually process commands whenever EN
is pulsed,
so pulsing EN
too quickly can cause major problems. It follows that
commands should not be issued less than a millisecond apart--or longer, if
you are using a high-capacitance bypass capacitor.
The HD44780 controller supports both an 8-bit interface, where all eight D
pins are used simultaneously to issue an 8-bit command, as well as a 4-bit
interface, where only pins D4
-D7
are used, and an additional pulse of EN
is used to send the four most significant bits of the 8-bit command separately
from the four least significant bits.
When operating in 4-bit mode, the process to issue a command is
- Set
RS
to high (which means we want to display the data we are sending over theD
pins) or low (we are sending a command to the chip) - Set
D4
,D5
,D6
,D7
to the 4th, 3rd, 2nd, and 1st most significant bits - Pulse the
EN
pin by setting it low, high, then low - Set the
D4
,D5
,D6
, andD7
to the least significant bit, 2nd least, 3rd, and 4th - Pulse the
EN
pin
Programmatically, a function that issues a command might look like
def write8_4bitmode(command, rs_value):
GPIO.output(PIN_RS, rs_value)
time.sleep(1e-3)
GPIO.output(PIN_D[4], GPIO.HIGH if ((command >> 4) & 1) > 0 else GPIO.LOW)
GPIO.output(PIN_D[5], GPIO.HIGH if ((command >> 5) & 1) > 0 else GPIO.LOW)
GPIO.output(PIN_D[6], GPIO.HIGH if ((command >> 6) & 1) > 0 else GPIO.LOW)
GPIO.output(PIN_D[7], GPIO.HIGH if ((command >> 7) & 1) > 0 else GPIO.LOW)
clock_pulse()
GPIO.output(PIN_D[4], GPIO.HIGH if ((command >> 0) & 1) > 0 else GPIO.LOW)
GPIO.output(PIN_D[5], GPIO.HIGH if ((command >> 1) & 1) > 0 else GPIO.LOW)
GPIO.output(PIN_D[6], GPIO.HIGH if ((command >> 2) & 1) > 0 else GPIO.LOW)
GPIO.output(PIN_D[7], GPIO.HIGH if ((command >> 3) & 1) > 0 else GPIO.LOW)
clock_pulse()
Pulsing the clock (EN
) is not entirely straightforward, because the EN
pin
is triggered by a rising edge, but cannot be pulsed too quickly since the
command it triggers takes dozens of microseconds to complete. The process
looks like
- Pull
EN
low - Waiting a microsecond (> 450 ns); may need to be longer depending on your bypass capacitor's capacitance
- Pull
EN
high - Waiting a microsecond (> 450 ns)
- Pull
EN
low - Wait 37 microsecond; commands can take 37 μs to execute, per Table 6 in the datasheet
The clock_pulse()
function may look something like
def pulse_clock(delay=1e-3):
time.sleep(delay)
GPIO.output(PIN_EN, GPIO.HIGH)
time.sleep(delay)
GPIO.output(PIN_EN, GPIO.LOW)
time.sleep(delay)
where the delay
is critical to ensure that successive commands are not lost
due to excessively high frequency.
When using 8-bit mode and pins D0
-D7
are all wired, the command sequence
is a little simpler. The corresponding code would look like
def write8_8bitmode(command, rs_value):
GPIO.output(PIN_RS, rs_value)
time.sleep(1e-3)
GPIO.output(PIN_D[0], GPIO.HIGH if ((command >> 0) & 1) > 0 else GPIO.LOW)
GPIO.output(PIN_D[1], GPIO.HIGH if ((command >> 1) & 1) > 0 else GPIO.LOW)
GPIO.output(PIN_D[2], GPIO.HIGH if ((command >> 2) & 1) > 0 else GPIO.LOW)
GPIO.output(PIN_D[3], GPIO.HIGH if ((command >> 3) & 1) > 0 else GPIO.LOW)
GPIO.output(PIN_D[4], GPIO.HIGH if ((command >> 4) & 1) > 0 else GPIO.LOW)
GPIO.output(PIN_D[5], GPIO.HIGH if ((command >> 5) & 1) > 0 else GPIO.LOW)
GPIO.output(PIN_D[6], GPIO.HIGH if ((command >> 6) & 1) > 0 else GPIO.LOW)
GPIO.output(PIN_D[7], GPIO.HIGH if ((command >> 7) & 1) > 0 else GPIO.LOW)
pulse_clock()
Initialization
The chip must be initialized into either 4-bit or 8-bit mode before it will receive any commands. The chip will auto-initialize into 4-bit mode when it is powered by up to 5 V, but if you are using 3.3 V or if you need to switch from 4-bit to 8-bit modes after the initial power-up, you will have to (re-)initialize the chip manually.
At a high level, initializing the chip is described in Figures 23 and 24 in the datasheet and involves five steps:
- Issuing the magical reset sequence, which includes declaring whether you will use either the 4-bit or 8-bit command interface
- Configuring the "function set" options
- Configuring the "display on/off control" options
- Clearing the display
- Configuring the "entry mode set" options
Although not documented, steps #3, #4, and #5 are not strictly required, although they do explicitly define behavior that may otherwise cause your chip and display to behave unpredictably. In addition, steps #3 to #5 can be executed in any order.
An example of the code that would initialize the chip in 4-bit mode would look like this:
def init_4bitmode():
### initialization magic sequence
write4(int("0011", 2))
write4(int("0011", 2))
write4(int("0011", 2))
write4(int("0010", 2))
### send the "function set" command to configure display dimensions
write8_4bitmode(int("00101100",2), rs_value=GPIO.LOW)
### send the "display on/off control" command (1000) to power on the
### display (0100), enable cursor (0010), and enable cursor blink (0001)
write8_4bitmode(int("00001111",2), rs_value=GPIO.LOW)
### clear the display
write8_4bitmode(int("00000001",2), rs_value=GPIO.LOW)
### send the "entry mode set" command to set left-to-right printing (110)
write8_4bitmode(int("00000110",2), rs_value=GPIO.LOW)
The following sections provide more detail about the steps in this process.
The magical reset sequence
To reset or initialize the chip:
- Enter
0011----
three times in a row to reset into 8-bit mode - Enter
0011----
three times in a row followed by0010----
once to reset into 4-bit mode
Specifically, this involves
- Pulling
RS
low to indicate that we are sending a command, not a character to display - Pulling
D7
andD6
low,D5
high, andD4
either low (4-bit mode) or high (8-bit mode) - Ensuring
EN
is low, then raising it high (to trigger a clock signal, which is triggered on the rising edge), then lowering it again - Repeating #2 and #3 the requisite number of additional times
Note that this sequence simply leaves the low-order bits floating:
:::python def write4(value): """ special function to send only the four highest-order bits; low-order bits remain floating """ GPIO.output(PIN_RS, GPIO.LOW)
time.sleep(1e-3)
GPIO.output(PIN_D[4], GPIO.HIGH if ((value >> 0) & 1) > 0 else GPIO.LOW)
GPIO.output(PIN_D[5], GPIO.HIGH if ((value >> 1) & 1) > 0 else GPIO.LOW)
GPIO.output(PIN_D[6], GPIO.HIGH if ((value >> 2) & 1) > 0 else GPIO.LOW)
GPIO.output(PIN_D[7], GPIO.HIGH if ((value >> 3) & 1) > 0 else GPIO.LOW)
pulse_clock()
This magic reset sequence is the only time this 4-bit write is ever used. And in fact, it is not strictly necessary to use a special 4-bit write function; the magic reset sequence to initialize 4-bit mode is actually designed in a way that allows you to issue two 8-bit commands in 4-bit mode to emulate the same sequence. That is,
def init_4bitmode():
"""
initialize chip into 4-bit interface mode using a special 4-bit write
function
"""
write4(int("0011", 2))
write4(int("0011", 2))
write4(int("0011", 2))
write4(int("0010", 2))
is functionally identical to
def init_4bitmode():
"""
initialize chip into 4-bit interface mode using 8-bit sequences written
in 4-bit mode
"""
write8_4bitmode(int("00110011", 2))
write8_4bitmode(int("00110010", 2))
When initializing into 8-bit mode, you can simply issue regular 8-bit writes
using D0
through D7
:
def init_8bitmode():
"""initialize chip into 8-bit interface mode"""
write8_8bitmode(int("00110000", 2))
write8_8bitmode(int("00110000", 2))
write8_8bitmode(int("00110000", 2))
In this case, the low-order bits (D0
- D3
) are "don't care" bits and can
have any value.
Configuring the "function set" options
The "function set" command has the following form:
D7 |
D6 |
D5 |
D4 |
D3 |
D2 |
D1 |
D0 |
---|---|---|---|---|---|---|---|
0 | 0 | 1 | DL | N | F | - | - |
where
DL
is 1 for 8-bit interface, 0 for 4-bit. You must use the same setting here as you used for the magical reset sequence; if you don't, you will get scrambled commands.N
= 1 for a two-line display; = 0 for a one-line displayF
= 1 to use the 5x10 dot character set; = 0 for the 5x8 dot character set
A reasonable command may be
### send the "function set" command to configure display dimensions
write8_4bitmode(int("00101100",2), rs_value=GPIO.LOW)
Configuring "display on/off control" options
The "display on/off control" command has the following form:
D7 |
D6 |
D5 |
D4 |
D3 |
D2 |
D1 |
D0 |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 1 | D | C | B |
where
D
= 1 turns on the display; = 0 turns it offC
= 1 enables the display of the cursor; = 0 hides the cursor. Having the cursor enabled is great for debugging display issues, but is probably annoying for production use.B
= 1 makes the cursor blink; = 0 makes the cursor not blink
A reasonable command may be
### send the "display on/off control" command (1000) to power on the
### display (100), enable cursor (010), and enable cursor blink (001)
write8_4bitmode(int("00001111",2), rs_value=GPIO.LOW)
Although not strictly required to get the chip to a usable state, the chip does
default to a state where the display is off (00001000
). Thus, the display
won't actually show anything until you explicitly issue this command and set the
D bit to 1.
Clearing the display
Clearing the display is a matter of sending a single least-significant bit with all others set to zero:
D7 |
D6 |
D5 |
D4 |
D3 |
D2 |
D1 |
D0 |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 |
This also resets the cursor position to the first register so that subsequent characters are printed to the freshly cleared screen. It would appear as
### clear the display
write8_4bitmode(int("00000001",2), rs_value=GPIO.LOW)
This is not strictly necessary to get the chip to a usable state, but it does make life easier.
Configuring the "entry mode set" options
The "entry mode set" command has the following form:
D7 |
D6 |
D5 |
D4 |
D3 |
D2 |
D1 |
D0 |
---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 1 | I/D | S |
where
I/D
= 1 sets the mode where the cursor moves to the right by one after a new character is displayed; = 0 moves the cursor to the leftS
= 1 shifts the display along with the above cursor movement; = 0 does not shift the display
A reasonable command would be
### send the "entry mode set" command to set left-to-right printing (110)
write8_4bitmode(int("00000110",2), rs_value=GPIO.LOW)
Issuing this command is not strictly required to initialize the chip though.
Writing a message
Write one character at a time by pulling RS
high and then sending the 8-bit
ASCII representation of the character via D0
through D7
. Pulsing EN
then
displays this character on the LCD display:
def printmsg(msg):
"""write the message one character at a time"""
for c in msg:
write8_4bitmode(ord(c), rs_value=GPIO.HIGH)
Printing a character on the display also moves the cursor (position where the next character will appear) over.
It is worth pointing out that the HD44780 chip is capable of driving an LCD display that is 40 characters (columns) long. Most actual LCD displays appear to be only 16 characters long though, so the characters for columns 17-40 never appear anywhere despite being settable. This has two interesting implications:
- You can use the registers for characters #17 through #40 as memory to store
whatever values you want. They aren't displayed, and if you wire the
RW
pin of the chip, you can read the contents of these registers back out. - To print characters to the second row of the LCD display, you must either fill up columns 17-40 with nonsense to get the cursor to wrap around, or explicitly set the cursor position using the "Set DDRAM address" command, which is detailed in the following section.
Setting the cursor position
As described in the above section, the HD44780 addresses its characters in two rows, each with 40 columns. To set the position of the input cursor, issue the "Set DDRAM address" command, which has the form
D7 |
D6 |
D5 |
D4 |
D3 |
D2 |
D1 |
D0 |
---|---|---|---|---|---|---|---|
1 | ADD | ADD | ADD | ADD | ADD | ADD | ADD |
where ADD
bits encode the position (1-40 for the first row, 41-80 for the
second). Enabling the cursor makes it a lot easier to experiment with this
command, and recall that on a 2x16 LCD display, characters 17-40 are not
visible.
Other commands
Table 6 in the datasheet describes all of the available commands. The command being sent is a function of the highest-order bit:
00000001
= clear display0000001-
= reset the cursor position to 0000001a1
= set cursor move direction (a)00001abc
= display on/off (a), cursor on/off (b), cursor blinking on/off (c)0001ab--
= when new characters are displayed, move cursor (a=0) or shift the whole screen (a=1), and move/shift to the right (b=1) or left (b=0)001abc--
= set interface length (a), number of lines (b), font (c)- a = 1 for 8-bit, 0 for 4-bit
- b = 1 for 2 lines, 0 for 1 line
- c = 1 for 5x10 dots, 0 for 5x8 dots
01000000
= set CGRAM address (6 bits)10000000
= set display position (DDRAM address; 7 bits)
Further experimenting
I've written a test script that demonstrates how to interface with the HD44780 chip and perform basic commands using just the Raspberry Pi GPIO library. It either prints a message if passed no options, or allows you to manually pass 8-bit commands using both the 4-bit and 8-bit interfaces.