A 20x4 LCD on an Arduino: four rows on I2C
Intermediate
Or see every lesson.
- 455parts on the bench
- 43boards running now
- 1.00xreal time, on every board
A 20x4 character LCD, sold as the LCD2004, shows four rows of twenty characters. It is the big brother of the 16x2, on the same HD44780 controller, and it comes the same two ways: with sixteen bare pins, or with an I2C backpack that cuts them down to four wires.
The circuit above is an Uno driving one of each. The bare panel prints a forty-character line in one go, to show the surprise every 20x4 owner meets: the text runs off row 0 and carries on in row 2, not row 1. The I2C panel places each row with setCursor() and counts the seconds. If you have a 16x2 instead, the 16x2 I2C page covers it.
What you need
- An Arduino Uno
- A 20x4 LCD with an I2C backpack (LCD2004 with PCF8574)
- For the second panel: a bare 20x4 LCD
- A breadboard and jumper wires
Wiring
| Part and pin | Goes to | Note |
|---|---|---|
| I2C LCD GND | GND | |
| I2C LCD VCC | 5 V | |
| I2C LCD SDA | A4 | The Uno’s I2C data pin |
| I2C LCD SCL | A5 | The Uno’s I2C clock pin |
| Bare LCD RS, E | Pins 2, 3 | |
| Bare LCD D4 to D7 | Pins 4, 5, 6, 7 | Four-bit mode |
| Bare LCD VSS, V0, RW | GND | V0 to ground is full contrast |
| Bare LCD VDD | 5 V |
Four rows are really two lines
The HD44780 controller only has two lines of memory, forty characters each. A 16x2 shows the first sixteen of each. A 20x4 shows each line twice over: row 0 is the first twenty characters of line 1 and row 2 is the next twenty, and rows 1 and 3 do the same with line 2.
So the four rows start at memory addresses 0x00, 0x40, 0x14 and 0x54. LiquidCrystal_I2C knows that table, which is why setCursor(0, 2) lands at the start of row 2. Print a string longer than twenty characters without setCursor() and it keeps going along line 1, into row 2.
Run the circuit and the bare panel shows it. Its sketch prints "Row 0 runs off here " and then "and on into row 2..." with no setCursor() between them, and the second half appears on row 2. Row 1 holds "Row 1 is line 2", placed there with setCursor(0, 1).
The I2C example code
The backpack is a PCF8574 port expander soldered to the back of the panel. It turns the LCD’s pins into an I2C device at address 0x27, or 0x3F on boards with the PCF8574A chip. In the Arduino IDE install "LiquidCrystal I2C" from the Library Manager; in Mokxi it is built in.
The one line that matters for a 20x4 is the constructor: LiquidCrystal_I2C lcd(0x27, 20, 4). The 20 and the 4 tell the library the shape, so it uses the right row addresses. Then init() starts the controller, backlight() turns the light on, and setCursor(column, row) puts each line where you want it, with rows numbered 0 to 3.
Run this sketch on a 20x4 I2C panel and it shows four rows: "Four rows of twenty", then "Row 1 starts at 0x40", "Row 2 starts at 0x14" and "Row 3 starts at 0x54".
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 20, 4);
void setup() {
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Four rows of twenty");
lcd.setCursor(0, 1);
lcd.print("Row 1 starts at 0x40");
lcd.setCursor(0, 2);
lcd.print("Row 2 starts at 0x14");
lcd.setCursor(0, 3);
lcd.print("Row 3 starts at 0x54");
}
void loop() {
}Telling the library it is 16x2 by mistake
Copy a 16x2 sketch to a 20x4 and the constructor still says LiquidCrystal_I2C lcd(0x27, 16, 2). Rows 0 and 1 work, so it looks fine at first. But the library now thinks there are only two rows, so setCursor(0, 2) and setCursor(0, 3) both go to row 1.
Run the sketch above with 16, 2 in the constructor and the panel shows "Four rows of twenty" on row 0 and "Row 3 starts at 0x54" on row 1, written over the row 1 and row 2 text. Rows 2 and 3 stay empty. Change it back to 20, 4 and all four rows appear.
Updating a number without flicker
The I2C panel in the circuit counts seconds on row 2. Its loop does not clear the screen. It moves the cursor to column 9 of row 2 and prints the new number over the old one, four times a second. clear() is slow, about 1.5 milliseconds on the controller and much more over I2C, and it blanks the whole screen, which shows as flicker.
If a number can get shorter, from 10 to 9 say, print a space after it so the old digit does not stay behind.
The bare panel, and your own characters
Without a backpack, a 20x4 has the same sixteen pins as a 16x2 and wires the same way: RS, E and four data lines in four-bit mode, with RW and V0 to ground. The bare panel in the circuit uses pins 2 to 7. Its sketch uses Mokxi’s own small LCD driver, but the stock LiquidCrystal library does the same job with lcd.begin(20, 4).
Both kinds keep room for eight characters of your own, five dots wide and eight tall, which is how people draw battery icons, arrows and big digits across two rows. lcd.createChar() stores one, and lcd.write() with its number, 0 to 7, shows it.
Common mistakes
The wrong address. With 0x3F in the sketch and a backpack at 0x27, the controller is never set up, and the panel shows its power-on look: rows 0 and 2 full of solid blocks and rows 1 and 3 empty. Run an I2C scanner to find the real address.
16, 2 in the constructor of a 20x4, so rows 2 and 3 end up in row 1.
Lines longer than twenty characters, which carry on into the row two below.
The contrast trimmer on the backpack turned all the way, so the glass looks empty with the backlight on. Turn it slowly until the text appears.
Forgetting backlight(). The text is there, but you cannot read it.
Questions
What library do I use for a 20x4 I2C LCD?
LiquidCrystal I2C (LiquidCrystal_I2C.h), the same one the 16x2 uses. Declare it as LiquidCrystal_I2C lcd(0x27, 20, 4).
What is the I2C address of a 20x4 LCD?
Usually 0x27. Backpacks with the PCF8574A chip use 0x3F. An I2C scanner sketch tells you which.
Why does my text jump from row 0 to row 2?
The controller stores rows 0 and 2 as one long line. Text that runs past column 19 of row 0 carries on in row 2. Use setCursor() for each row.
Can I use the 16x2 code on a 20x4 LCD?
Yes, once you change the constructor to 20, 4. With 16, 2 the library sends rows 2 and 3 to row 1.
Keep going
Where the facts on this page come from
Build this for real
Open the editor, change a value and watch the number move with it. Nothing to install, and no account needed.