Source: https://mokxi.com/learn/arduino-lcd-20x4
Updated: 2026-10-26

Learn

# A 20x4 LCD on an Arduino: four rows on I2C

Intermediate

Written by the Mokxi team, updated October 26, 2026

Start building free Open the editor

Or see every lesson.

- 455 parts on the bench
- 43 boards running now
- 1.00x real time, on every board

Click to open it in the editor

A bare 20x4 on six pins and an I2C 20x4 on A4 and A5, driven from one simulated ATmega328P.

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".

20x4 I2C LCD example sketch

## 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.

Related

## Keep going

Arduino LCD I2C 16x2: Wiring, Code, Scrolling Text Arduino LCD 16x2 Without I2C: Wiring and Code Arduino I2C LCD Not Working: Blank, Blocks, No Text I2C Scanner for Arduino and ESP32, and Fixes I2C Explained: Addresses, Acknowledges and Pull-Ups How the 20x4 LCD is modeled The 16x2 I2C LCD, pin by pin The Arduino Uno simulator The project page, with the full sketch Intermediate

Sources

## Where the facts on this page come from

- Arduino: LiquidCrystal I2C library reference (checked 2026-10-26)

## Build this for real

Open the editor, change a value and watch the number move with it. Nothing to install, and no account needed.

Start building free Open the editor
