Twenty by four on an Arduino Uno
Two 20x4 character LCDs, one on six wires and one on its I2C backpack. A long line runs off row 0 and carries on in row 2. The whole build, an Arduino Uno and 2 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Intermediate About 20 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $45. On Mokxi they're free.
What you'll learn
- How a 20x4 LCD maps rows to controller memory
- Driving an LCD on six wires versus two with I2C
- Placing text with a cursor column and row
- Why one long print wraps in an odd order
- Difficulty: Intermediate
- Time: About 20 minutes
- Board: Arduino Uno R3
- Parts: 4
How it works
An Uno drives two 20x4 character LCDs. The first is wired directly: RS on pin 2, E on pin 3 and D4 to D7 on pins 4 to 7. The second has an I2C backpack and needs only SDA on A4 and SCL on A5.
A 20x4 panel uses the same controller as a 16x2, which only has two lines of memory. The rows start at addresses 0x00, 0x40, 0x14 and 0x54. So when the sketch prints forty characters in a row on the first panel, the second twenty land in row 2, not row 1.
The second panel places each row by hand with setCursor and shows a seconds counter that updates in row 2.
Parts list
4 parts, plus the jumper wires. They retail for about $45; on Mokxi they're free.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Full-size breadboard
- 1 × Character LCD, 20x4
- 1 × Character LCD, 20x4, with I2C backpackIn Spaceship Panel Kit, Escape Box Kit.
Wiring table
Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.
| Part and pin | Connects to |
|---|---|
| Character LCD, 20x4 pin D7 | Arduino Uno R3 pin 7 |
| Character LCD, 20x4 pin D6 | Arduino Uno R3 pin 6 |
| Character LCD, 20x4 pin D5 | Arduino Uno R3 pin 5 |
| Character LCD, 20x4 pin D4 | Arduino Uno R3 pin 4 |
| Character LCD, 20x4 pin E | Arduino Uno R3 pin 3 |
| Character LCD, 20x4 pin RS | Arduino Uno R3 pin 2 |
| Character LCD, 20x4 pin VDD | Arduino Uno R3 pin 5V |
| Character LCD, 20x4, with I2C backpack pin VCC | Arduino Uno R3 pin 5V |
| Character LCD, 20x4 pin VSS | Arduino Uno R3 pin GND |
| Character LCD, 20x4 pin V0 | Arduino Uno R3 pin GND |
| Character LCD, 20x4 pin RW | Arduino Uno R3 pin GND |
| Character LCD, 20x4, with I2C backpack pin GND | Arduino Uno R3 pin GND |
| Character LCD, 20x4, with I2C backpack pin SDA | Arduino Uno R3 pin A4 |
| Character LCD, 20x4, with I2C backpack pin SCL | Arduino Uno R3 pin A5 |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Twenty by four: two 20x4 character LCDs, one on six wires and one on two.
//
// bare 20x4 RS -> 2 E -> 3 D4..D7 -> 4, 5, 6, 7
// VSS, V0, RW -> GND VDD -> 5 V
// I2C 20x4 SDA -> A4 SCL -> A5 VCC -> 5 V GND -> GND
//
// A 20x4 is the same HD44780 controller as a 16x2, and the controller only has
// two lines of memory, forty characters each. The panel shows each line twice
// over: row 0 is the first twenty characters of line 1 and row 2 the next
// twenty, rows 1 and 3 the same of line 2. So the rows start at 0x00, 0x40,
// 0x14 and 0x54, and setCursor() knows that.
//
// The bare panel shows what happens without it: one long print runs off the
// end of row 0 and carries on in row 2, not row 1. The I2C panel counts, with
// each row placed by setCursor().
#include "mokxi_lcd1602.h"
#include "mokxi_lcd1602_i2c.h"
Lcd1602 bare(2, 3, 4, 5, 6, 7);
Lcd1602I2c backpack(A5, A4);
void setup() {
bare.begin();
// Forty characters in one go: the second twenty land in row 2.
bare.print("Row 0 runs off here ");
bare.print("and on into row 2...");
bare.setCursor(0, 1);
bare.print("Row 1 is line 2");
backpack.begin();
backpack.setCursor(0, 0);
backpack.print("Twenty by four");
backpack.setCursor(0, 1);
backpack.print("rows at 00 40 14 54");
backpack.setCursor(0, 2);
backpack.print("seconds:");
}
void loop() {
backpack.setCursor(9, 2);
backpack.print((long)(millis() / 1000));
backpack.setCursor(0, 3);
backpack.print("row 3 is 0x54");
delay(250);
}
Code walkthrough
The key functions of the sketch, and what each one does.
setup sketch.ino
Starts both screens with begin. On the bare panel it prints two 20-character strings in a row, which fill row 0 and then run on into row 2, then moves to row 1 for a separate line. On the I2C panel it uses setCursor to put a title, an address note and the word seconds on rows 0, 1 and 2.
void setup() {
bare.begin();
// Forty characters in one go: the second twenty land in row 2.
bare.print("Row 0 runs off here ");
bare.print("and on into row 2...");
bare.setCursor(0, 1);
bare.print("Row 1 is line 2");
backpack.begin();
backpack.setCursor(0, 0);
backpack.print("Twenty by four");
backpack.setCursor(0, 1);
backpack.print("rows at 00 40 14 54");
backpack.setCursor(0, 2);
backpack.print("seconds:");
}loop sketch.ino
Every 250 milliseconds it moves the I2C cursor to column 9 of row 2 and prints millis() / 1000, so the seconds count updates in place. It also prints a label on row 3.
void loop() {
backpack.setCursor(9, 2);
backpack.print((long)(millis() / 1000));
backpack.setCursor(0, 3);
backpack.print("row 3 is 0x54");
delay(250);
}Troubleshooting
The usual mistakes with this circuit, and the fix for each.
- Row 1 stays empty after a long print on the bare panel This project
That is expected. The text continues in row 2. UsesetCursorto place text on each row yourself. - The I2C panel stays blank This project
Check SDA on A4 and SCL on A5, plus 5 V and ground on the backpack. - The LCD shows blocks or nothing. Character LCD
Adjust the contrast with the potentiometer on V0. Check the wiring, power at 5 V, and that you set 20 columns and 4 rows in the LiquidCrystal begin call. - Text appears on the wrong line or breaks across rows. Character LCD
Row 2 continues row 0 and row 3 continues row 1. Use setCursor(col, row) for each line, and clear old text. For an I2C backpack, check the address, often 0x27 or 0x3F. - The screen lights up but shows nothing, or only blocks. Character LCD
Turn the small contrast trimmer on the backpack. Make sure the backlight jumper is on. The backpack needs 5 V power, not 3.3 V. - The sketch hangs or the display stays blank. Character LCD
The PCF8574 backpack is at 0x27, but PCF8574A versions are at 0x3F. Run an I2C scanner. SDA goes to A4 and SCL to A5 on an Uno. Use LiquidCrystal_I2C with 20 columns and 4 rows. - Upload fails or nothing runs after wiring pins 0 and 1 to a module. Arduino Uno R3
Pins 0 and 1 are the USB serial port. Move the module to other pins, or unplug it while uploading. Also check the right board is selected. - Pins misbehave or the board resets when you power motors or servos from it. Arduino Uno R3
Each I/O pin gives about 20 mA, 40 mA max. Power motors and servos from their own supply and join its ground to the Uno's GND.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.