20x4 character LCD

The big brother of the 16x2: four rows of twenty characters on the same HD44780 controller. Aliases: LCD2004, 2004A, 20x4 LCD.

It comes two ways in the bin, as it does in the shops: lcd2004 with the sixteen bare pins, and lcd2004i2c with the PCF8574 backpack soldered on, which turns them into four.

Everything the 16x2 page says about the controller holds here word for word: the four-bit start-up, the busy time after every byte and the 1.52 ms after a clear, CGRAM for eight characters of your own, the contrast pin, the backlight. What is new is the shape of the window.

Pins

The bare module has the 16x2's sixteen pins, in the same order:

Pin What it does
VSS, VDD Ground and 5 V.
V0 Contrast. Near VSS is dark text; at VDD the glass is empty.
RS Low for a command, high for a character.
RW Tie it to ground: nothing here answers a read.
E The clock. The byte is taken on the falling edge.
D0 to D7 The data lines. Four-bit mode uses only D4 to D7.
A, K The backlight LED.

The backpack version has GND, VCC, SDA and SCL, at address 0x27.

Properties

The backpack version has address (0x27, or 0x3F for a PCF8574A board) and contrast, which stands in for the blue trimmer on its back.

Four rows are two lines

The controller only has two lines of memory, forty characters each: line 1 at 0x00 to 0x27, line 2 at 0x40 to 0x67. A 20x4 shows each of them twice over. Row 0 is the first twenty characters of line 1 and row 2 is the next twenty; rows 1 and 3 are the same of line 2. So the rows start at:

Row DDRAM address setCursor sends
0 0x00 0x80
1 0x40 0xC0
2 0x14 0x94
3 0x54 0xD4

And that is why text that runs off the end of row 0 carries on in row 2, not row 1: the address counter just keeps counting along line 1. LiquidCrystal_I2C and every other library carry that table, so setCursor(0, 2) does the right thing; printing a long string without it is where the surprise comes from. The part models it because it falls straight out of the memory map, not because it was written in specially. Scrolling the display (scrollDisplayLeft) moves both halves of a line together, so text leaving row 0 on the left comes back into row 2.

What it does not model

The same as the 16x2: nothing is read back (the busy flag, the address counter and DDRAM all read nothing), there is no 5x10 font, and bias, temperature and viewing angle do not exist. On the backpack, the PCF8574's input port and its interrupt pin are not there either.

Code

The backpack version, with the stock library:

#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, 3);
  lcd.print("and this is row 3");
}

void loop() {}

The bare module works with mokxi_lcd1602.h, the same driver the 16x2 uses: give setCursor the row start from the table above.

Common mistakes

Printing a line longer than twenty characters and finding the rest in row 2. Telling LiquidCrystal_I2C the module is 16, 2: setCursor(0, 2) then goes to the wrong address. On the bare module, V0 left at the supply: an empty glass.

See it in action

Twenty by four at /templates drives both versions from one Uno: the bare one prints a forty-character line to show it carrying on in row 2, and the backpack one places each row with setCursor.