Displays
16×2 LCD interfacing: wiring an HD44780 module, its timing, custom characters and I2C
By Bulan Sarkar, founder of ElectronicsInfoline (since 2000) · About a 13-minute read
The 16×2 character LCD is still the cheapest way to give a microcontroller project a readout, and nearly every one of them speaks the command set of Hitachi's HD44780 controller. Learn that controller once and the same code drives 16×2, 20×4 and 8×1 modules from an Arduino, an 8051, a PIC or a Raspberry Pi.
This page wires one to an Arduino UNO in 4-bit mode, then works through the parts that cause trouble: contrast, the address map, timing, the start-up sequence, custom characters, I2C backpacks and 3.3 V boards. Each number is either taken from a data sheet or library source or worked out here, and the page says which.
To drive a 16×2 HD44780 LCD, connect VSS to ground, VDD to 5 V, V0 to the wiper of a 10 kΩ pot, R/W to ground, and RS, E and D4 to D7 to six pins of the microcontroller. Send the 4-bit start-up sequence, then write characters: RS = 1 for text, RS = 0 for commands, each byte as two nibbles latched by a pulse on E.
Each command takes 37 µs at the nominal 270 kHz clock and up to 52.6 µs on a slow part. Line 1 starts at address 0x00 and line 2 at 0x40, so the cursor command for the start of the second line is 0xC0 (Hitachi).
The 16 pins
A standard 16×2 module has one row of 16 pins. Two power the logic, one sets the contrast, three control the bus, eight carry data and two feed the backlight LEDs. In 4-bit mode, which almost everyone uses, D0 to D3 stay unconnected.
| Pin | Name | Job |
|---|---|---|
| 1 | VSS | Ground |
| 2 | VDD | +5 V logic supply |
| 3 | V0 | Contrast voltage |
| 4 | RS | 0 = command, 1 = character |
| 5 | R/W | 0 = write, 1 = read |
| 6 | E | Enable: data latched on the falling edge |
| 7 | D0 | Unused in 4-bit mode |
| 8 | D1 | Unused in 4-bit mode |
| 9 | D2 | Unused in 4-bit mode |
| 10 | D3 | Unused in 4-bit mode |
| 11 | D4 | Data, low bit of each nibble |
| 12 | D5 | Data |
| 13 | D6 | Data |
| 14 | D7 | Data; also the busy flag when reading |
| 15 | A | Backlight LED anode |
| 16 | K | Backlight LED cathode |
Wiring it to an Arduino UNO
We use the pins from the Arduino LiquidCrystal library's own example: RS to D12, E to D11, D4 to D5, D5 to D4, D6 to D3, D7 to D2 (Arduino). R/W goes straight to ground, so the module is only ever written to. That costs nothing: the library waits a fixed time after each command instead of reading the busy flag.
Connect the backlight last, and only once you know its current (the backlight section below). Many modules have a resistor for it already on the board; some do not, and an LED array straight across 5 V without one will burn out.
Contrast: the voltage across the glass
The liquid crystal is driven by the difference between VDD and V0, not by V0 itself. Newhaven's NHD-0216K1Z, a typical 5 V module, wants 3.5 to 3.9 V across the glass, 3.7 V typical (Newhaven Display). From a 5 V supply that puts V0 at 1.3 V, anywhere from 1.1 V to 1.5 V. Too high and the screen is blank; too low and every dot shows as a dark block.
A 10 kΩ pot covers the whole range and copes with the spread between modules and with temperature. Once you have found the setting, you can replace it with two fixed resistors. For 1.3 V, the closest E12 pair we found is 15 kΩ from 5 V to V0 and 5.6 kΩ from V0 to ground, which gives 1.36 V and draws 0.24 mA. Measure your own pot setting first; it may differ from the typical figure.
Where characters go: the DDRAM address map
Every character on the screen is one byte in the controller's display RAM (DDRAM). On a two-line module, line 1 occupies addresses 0x00 to 0x27 and line 2 occupies 0x40 to 0x67, 40 positions each (Hitachi). A 16×2 module shows only the first 16 of each, so the visible addresses jump from 0F to 40 between the lines.
To move the cursor, send the Set DDRAM address command, 80H plus the address. Column 0 of line 1 is 0x80 and column 0 of line 2 is 0xC0; column 5 of line 2 is 0xC5. On a 20×4 module the rows start at 0x00, 0x40, 0x14, 0x54, which is why the LiquidCrystal library stores four row offsets and adds the column to them (Arduino).
Our take: always position with a Set DDRAM address command at the start of each line, rather than printing 16 characters and expecting the 17th to wrap. It doesn't: it goes to address 10, which is off the screen.
Timing: the write cycle and the execution time
A write is simple. Set RS (0 for a command, 1 for a character), keep R/W low, put the data on the bus, then pulse E high and low; the controller latches the data on the falling edge. At 4.5 to 5.5 V the HD44780U needs RS stable 40 ns before E rises, E high for at least 230 ns, the data stable 80 ns before E falls and held 10 ns after, and 500 ns from one E pulse to the next (Hitachi).
Those nanoseconds are not the slow part. After the falling edge the controller is busy carrying the instruction out, and the data sheet gives 37 µs for almost every one at a 270 kHz oscillator, 1.52 ms for Return home, and no figure at all for Clear display. The times scale with the clock, and the clock of a part with the standard resistor can be as slow as 190 kHz. At that end, 37 µs becomes 52.6 µs and 1.52 ms becomes 2.16 ms (Hitachi).
The LiquidCrystal library waits 100 µs after every enable pulse, which covers 52.6 µs with room to spare. For clear() and home() it waits 2.0 ms (Arduino), which is a little short of 2.16 ms on the slowest permitted part. Most modules run near 270 kHz and never notice. If the first character after a clear() sometimes goes missing, that is the likely reason; a delay(1) after clear() fixes it.
The 4-bit start-up sequence
The controller has a power-on reset, but it only works if the supply rises within the limits the data sheet sets, which a USB port or a slow regulator may not meet. The data sheet therefore gives a start-up sequence by instruction that always works. In 4-bit mode it begins with three "8-bit" function sets sent as single nibbles, because the controller may be in either mode, or halfway through a byte, when your code starts (Hitachi).
The waits are minimums. The LiquidCrystal library waits 50 ms at power-up, then 4.5 ms, 4.5 ms and 150 µs between the three nibbles, all a little longer than the data sheet asks (Arduino). The last byte, 0x0C, is not part of the data sheet's sequence: it turns the display back on after step 7 turned it off.
- Instruction time
- 37 µs (up to 53 µs)
- Line 2 starts at
- 0x40 (command 0xC0)
- Contrast V0 at 5 V
- about 1.3 V
- Logic current
- 1.0 mA typ
Custom characters
The controller keeps eight user-defined 5×8 characters in its character RAM (CGRAM), printed as character codes 0 to 7 (Hitachi). Each character is eight bytes, one per row from the top, with the five dots in the lowest five bits; the eighth row is the cursor line, so leave it blank. Send Set CGRAM address (40H plus the slot number × 8), then the eight bytes.
Here is the complete UNO sketch for the wiring in Figure 2. It loads the three characters and prints a temperature line and a battery line. The comments give the command byte behind each library call, worked out from the address map above.
#include <LiquidCrystal.h>
// RS, E, D4, D5, D6, D7: the wiring in Figure 2
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
byte degree[8] = {0x0C, 0x12, 0x12, 0x0C, 0x00, 0x00, 0x00, 0x00};
byte battery[8] = {0x0E, 0x1B, 0x11, 0x11, 0x1F, 0x1F, 0x1F, 0x00};
byte thermo[8] = {0x04, 0x0A, 0x0A, 0x0A, 0x0E, 0x1F, 0x0E, 0x00};
void setup() {
lcd.begin(16, 2); // the 4-bit start-up of Figure 5
lcd.createChar(0, degree); // command 0x40, then 8 bytes
lcd.createChar(1, battery); // command 0x48
lcd.createChar(2, thermo); // command 0x50
lcd.setCursor(0, 0); // command 0x80: back to the screen
lcd.write(byte(2));
lcd.print(" Temp 27.5");
lcd.write(byte(0));
lcd.print("C");
lcd.setCursor(0, 1); // command 0xC0
lcd.write(byte(1));
lcd.print(" Battery 52%");
}
void loop() {}The I2C backpack
A backpack is a small board soldered onto the 16 pins that carries a PCF8574 I/O expander, so the module needs only two signal wires, SDA and SCL. The expander's eight outputs drive the module in 4-bit mode. On the common boards, and in the LiquidCrystal_I2C library written for them, P0 is RS, P1 is R/W, P2 is E, P3 switches the backlight through a transistor, and P4 to P7 are D4 to D7 (LiquidCrystal_I2C library (johnrickman/LiquidCrystal_I2C on GitHub)).
Which address? A PCF8574 answers at 0x20 to 0x27 and a PCF8574A at 0x38 to 0x3F, set by its A0 to A2 pins (Texas Instruments) (Texas Instruments). Backpacks usually pull those pins high and leave solder links to ground them, so an untouched board is most often at 0x27 or 0x3F. Rather than guess, run an I2C scanner sketch once and use the address it prints.
With that bit order, sending the nibble 4 as a character with E high and the backlight on is the expander byte 0x4D: data 0100 in the top four bits, then backlight 1, E 1, R/W 0, RS 1.
How fast can you update the screen?
Whichever way you connect it, the bus is faster than the glass can follow. We counted only the waits and the bus time, not the processor's own overhead, for rewriting a whole 16×2 screen: 34 bytes, the 32 characters plus one cursor command per line.
- Parallel, 4-bit, with LiquidCrystal: two nibbles per byte, each followed by 100 µs, about 204 µs a byte and 6.9 ms a screen. In 8-bit mode, half that.
- I2C with LiquidCrystal_I2C: each nibble goes out as three separate I2C transmissions (data, E high, E low) (LiquidCrystal_I2C library (johnrickman/LiquidCrystal_I2C on GitHub)). At 100 kHz each transmission is at least 20 clock periods, so a byte takes about 1.30 ms and a screen 44.3 ms.
- I2C with all six expander writes for a byte packed into one transmission: 0.65 ms a byte, 22.1 ms a screen.
The NHD-0216K1Z's crystal takes 150 ms to turn a dot on or off, 200 ms at worst (Newhaven Display). Updating more than about 7 times a second only smears the digits. Update a reading two to five times a second, and rewrite only the characters that change.
3.3 V boards: ESP32, ESP8266, Raspberry Pi
Two separate things go wrong when a 5 V module meets a 3.3 V board, and only one of them is about the signals.
The first is contrast. If you run the module itself from 3.3 V, the most you can put across the glass is the whole 3.3 V supply, with V0 at ground. The NHD-0216K1Z needs at least 3.5 V, so it comes up blank or very faint, 0.2 V short however you turn the pot. Power a 5 V module from 5 V, or buy a module sold for 3.3 V operation.
The second is the logic levels. The original HD44780U accepts anything above 2.2 V as a high at a 5 V supply (Hitachi), so a 3.3 V output drives it comfortably. Many modules now carry compatible controllers with different limits: Newhaven's module, with an ST7066U, wants 3.5 V or more (Newhaven Display), which a 3.3 V pin never reaches. The PCF8574 on a backpack powered from 5 V also wants 3.5 V on SDA and SCL (Texas Instruments). It may work anyway, but it is outside the guarantee, and it is the first thing to suspect when a display shows random characters.
Keep R/W tied to ground on a 3.3 V board. If the module ever drives the bus to read the busy flag, its outputs swing up to its 5 V supply (the data sheet guarantees at least 2.4 V for a high), and an ESP32 or Raspberry Pi pin is not 5 V tolerant. For a clean fix in the other direction, run the six signal lines through a 74AHCT125, which takes 3.3 V in and gives 5 V out; our 3.3 V to 5 V level-shifting guide works through that part and the I2C options.
Backlight current, and dimming it
The backlight, not the controller, decides your power budget. The NHD-0216K1Z takes 1.0 mA for its logic (3.0 mA at most) and 130 mA for its yellow-green backlight (156 mA at most) through a resistor already on the board (Newhaven Display). That is 99 % of the module's current. Other modules differ a lot, so measure yours: put a multimeter on its mA range in series with pin 15.
A backlight like that is too much for a pin to switch: the ATmega328P's absolute maximum is 40 mA per pin (Microchip). Switch the cathode with a transistor and you can also dim it with PWM. For 156 mA through a 2N2222A forced to a gain of 10, the base needs 15.6 mA. With VBE(sat) up to 1.2 V (onsemi), that allows at most 244 Ω from a 5 V pin, so use 220 Ω. The pin then supplies 17.3 mA to 20.0 mA, and the transistor dissipates at most 71 mW, a rise of about 14 °C.
With the transistor in place, analogWrite(10, 64) gives about a quarter brightness. D10 is a PWM pin on the UNO and is free with our wiring.
Tips and checks
- Power up with only VSS, VDD and V0 connected and turn the pot. If a row of blocks appears on line 1, the module and contrast are fine, and any later fault is in the wiring or code.
- Garbled characters that change on every reset usually mean one data wire is off by one pin, often D4 and D5 swapped. Check them against Figure 2 with the power off.
- lcd.begin() must run after the module has power. If the module is fed from a supply that comes up later than the board's, the start-up sequence is lost and the screen stays at blocks; power both from the same rail.
- Long ribbon cables pick up noise on E, the one line that acts on an edge. Keep the wires short and put a 100 nF capacitor across VDD and VSS at the module.
- For a reading that changes, print it at a fixed position with trailing spaces, such as "27.5 ", rather than calling clear() every time. clear() is the slowest command and makes the screen flicker.
- An I2C backpack showing nothing at all is often only the contrast pot on the backpack. Turn it through its whole travel before you debug the code.
What we'd do: on an UNO or any 5 V board, wire the module in 4-bit mode with R/W to ground, as in Figure 2; it costs six pins and has no address to find. If pins are short, use a PCF8574 backpack and run an I2C scanner first. On an ESP32 or a Pi, power the module from 5 V and put a 74AHCT125 in the signal lines, or buy a module rated for 3.3 V.
Questions people ask
Why does my 16×2 LCD show only black boxes on the first line?
The glass and contrast are working, but the controller has not been initialised. Check the RS, E and D4 to D7 wiring and that lcd.begin(16, 2) runs. If the boxes look too dark, turn the contrast pot until V0 is nearer 1.3 V.
What is the address of the second line of a 16×2 LCD?
Line 2 starts at DDRAM address 0x40, so the command to move there is 0xC0. Line 1 starts at 0x00, command 0x80.
Can I connect R/W to ground?
Yes, and on a 3.3 V board you should. You lose the busy flag, so the code waits a fixed time after each command instead. The Arduino LiquidCrystal library already works that way.
What I2C address does an LCD backpack use?
0x20 to 0x27 with a PCF8574 and 0x38 to 0x3F with a PCF8574A, most often the top of the range on an unmodified board. An I2C scanner sketch tells you for certain.
Will a 16×2 LCD work on 3.3 V?
A 5 V module usually will not: the glass needs about 3.5 V or more and a 3.3 V supply cannot reach it. Power it from 5 V and shift the signal levels, or buy a module made for 3.3 V.
How many custom characters can an HD44780 hold?
Eight of 5×8 dots, as character codes 0 to 7, or four of the taller 5×10 font. They live in RAM, so load them again after every power-up.
Lessons behind the numbers
- Logic levelsWhy 3.3 V clears a 2.2 V threshold and misses a 3.5 V one.
- Logic familiesThe AHCT parts that turn 3.3 V signals into 5 V ones.
- PotentiometersThe contrast pot as an adjustable divider.
- Voltage dividerThe fixed pair that replaces the pot.
- Pull-ups and pull-downsWhat holds SDA and SCL high on an I2C bus.
- The BJT as a switchThe forced gain of 10 behind the backlight base resistor.
- Duty cycle and PWMHow analogWrite() dims the backlight.
- Seven-segment displaysThe other cheap readout, and when it beats an LCD.
- 2N2222 pinoutThe backlight switch transistor, pin by pin.
- Voltage divider calculatorCheck the contrast pair for your own measured V0.
Sources and assumptions
- Hitachi, HD44780U (LCD-II) Dot Matrix Liquid Crystal Display Controller/Driver, ADE-207-272(Z), '99.9 Rev. 0.0 (p. 11 and p. 29: two-line DDRAM addresses 00H–27H and 40H–67H; p. 13: eight 5×8 CGRAM characters; pp. 24–25 Table 6: 37 µs per instruction and 1.52 ms for Return home at 270 kHz, no time printed for Clear display, times scale as 270/fOSC; p. 46 Figure 24: 4-bit initialisation, waits of more than 40 ms (from 2.7 V), 4.1 ms and 100 µs; p. 51: VIH1 2.2 V min, VOH1 2.4 V min, ICC 350 µA typ at 5 V; p. 49: write timing at 2.7–4.5 V, tcycE 1000 ns, PWEH 450 ns, tDSW 195 ns; p. 52: fOSC 190–350 kHz (Rf = 91 kΩ, 5 V), write timing at 4.5–5.5 V tcycE 500 ns, PWEH 230 ns, tAS 40 ns, tDSW 80 ns, tH 10 ns)
- Newhaven Display, NHD-0216K1Z-FL-YBW 2×16 character LCD, part Rev1A (document revision 13, 9 November 2022), pp. 4–5 (ST7066U controller; VDD 4.5–5.5 V; IDD 1.0 mA typ, 3.0 mA max; VLCD 3.5–3.9 V, 3.7 V typ (V0 approx. 1.3 V); VIH 0.7 × VDD min; backlight 5.0 V through an on-board resistor, 130 mA typ, 156 mA max; response time 150 ms typ, 200 ms max)
- Texas Instruments, PCF8574 Remote 8-Bit I/O Expander for I2C Bus, SCPS068K (revised September 2024), §5.3, §5.6 and §7.3.3 (VIH 0.7 × VCC min; I2C clock 100 kHz max; 8-bit write addresses 40H–4EH, so 7-bit addresses 20H–27H)
- Texas Instruments, PCF8574A Remote 8-Bit I/O Expander for I2C Bus, SCPS069H (revised September 2024), §7.3.3 (8-bit write addresses 70H–7EH, so 7-bit addresses 38H–3FH)
- Arduino, LiquidCrystal library 1.0.7, src/LiquidCrystal.cpp (clear() and home() wait 2000 µs; each enable pulse is followed by a 100 µs wait; row offsets 00H, 40H, 00H + columns, 40H + columns)
- LiquidCrystal_I2C library (johnrickman/LiquidCrystal_I2C on GitHub), LiquidCrystal_I2C.cpp and .h (expander bits: P0 = RS, P1 = R/W, P2 = E, P3 = backlight, P4–P7 = D4–D7; each nibble is three separate I2C transmissions (data, E high, E low) followed by a 50 µs wait)
- onsemi, P2N2222A/D, Rev. 7 (January 2013), pp. 1–2 (VBE(sat) 0.6–1.2 V and VCE(sat) 0.3 V max at IC = 150 mA, IB = 15 mA)
- Microchip, ATmega48A/PA/88A/PA/168A/PA/328/P data sheet, DS40002061B, p. 322 (40 mA DC per I/O pin, absolute maximum)
Read on 9 October 2026. Our own choices, not taken from any source: the UNO pins (those of the LiquidCrystal example), tying R/W to ground, the 15 kΩ / 5.6 kΩ contrast pair (the nearest E12 divider to the module's typical V0), the 2N2222A backlight switch on D10 with a forced gain of 10, the three custom characters, and the speed figures. Those count only the library waits and the I2C bus time at 100 kHz (9 clocks per byte plus 2 for start, stop and bus-free time), not the processor's own overhead, so real updates are somewhat slower. Module figures are for the NHD-0216K1Z; check the data sheet of the module you buy.