Overview
The LCD2004 is a 20-character by 4-line character LCD built around the industry-standard HD44780-compatible controller, with a blue backlight and crisp white-on-blue text. Eighty characters on screen at once is enough for a full sensor dashboard — four labeled readings, a menu with a cursor, or a status page for a 3D printer or data logger — with no graphics libraries, no framebuffers, and barely any RAM used on your microcontroller.
The 16-pin header along the top edge carries power (VSS/VDD), contrast (V0), three control lines (RS, RW, E), the eight-bit data bus (D0-D7), and the backlight anode/cathode (A/K). In practice almost everyone runs it in 4-bit mode — six GPIO pins total — and ties RW to ground since reading from the display is rarely needed. Character LCDs are forgiving: slow timing, long wires, and breadboard wiring all work fine.
It pairs naturally with Arduino's built-in LiquidCrystal library, works with ESP32, Raspberry Pi, and Pico, and if you would rather spend two pins instead of six, ShillehTek's PCF8574 I2C backpack solders straight onto the header and turns it into an I2C display. Contrast is set with a simple 10k potentiometer on V0 — if you ever see a blank or all-blocks screen, that pot is almost always the answer.
At a Glance
Specifications
| Parameter | Value |
| Display Format | 20 characters x 4 lines (80 characters) |
| Controller | HD44780-compatible (SPLC780 / ST7066 class) |
| Character Size | 5 x 8 dot matrix per character |
| Logic Supply | 5V DC (logic current ~2 mA) |
| Backlight | Blue LED, ~25-60 mA at 5V |
| Contrast | Set by voltage on V0 (10k potentiometer recommended) |
| Interface | 6800-style parallel bus: RS, RW, E, D0-D7 |
| Recommended Mode | 4-bit (D4-D7 only), RW tied to GND |
| Module Size | ~98 x 60 x 13.5 mm |
| Viewing Area | ~76 x 25.2 mm |
| Pinout (1-16) | VSS, VDD, V0, RS, RW, E, D0-D7, A, K |
Pinout Diagram
Pin 1 (VSS) is on the left end of the header. VSS and VDD power the logic, V0 sets contrast, RS selects command or data, RW selects write or read (tie it to GND), and E is the strobe that latches each transfer. D0-D7 form the data bus — in 4-bit mode only D4-D7 are wired and each byte is sent as two nibbles. A and K at the right end power the backlight LED.
Wiring Guide
Arduino Wiring (4-bit mode)
| LCD Pin | Arduino Pin | Details |
|---|---|---|
| 1 (VSS) | GND | |
| 2 (VDD) | 5V | |
| 3 (V0) | 10k pot wiper | Pot ends to 5V and GND |
| 4 (RS) | D12 | |
| 5 (RW) | GND | Write-only |
| 6 (E) | D11 | |
| 11-14 (D4-D7) | D5, D4, D3, D2 | 4-bit data bus |
| 15 (A) | 5V via 220 ohm | Backlight + |
| 16 (K) | GND | Backlight - |
ESP32 Wiring
Power the LCD from 5V (VIN/VBUS) but drive the pins from the ESP32 as usual. Because RW is tied to GND the display never drives its bus back at 5V, so the ESP32 pins stay safe; the LCD reads the ESP32's 3.3V highs reliably in practice.
| LCD Pin | ESP32 Pin | Details |
|---|---|---|
| VSS / RW / K | GND | All three grounded |
| VDD | VIN (5V) | Logic + backlight power |
| V0 | 10k pot wiper | Pot ends to 5V and GND |
| RS | GPIO 13 | |
| E | GPIO 12 | |
| D4 / D5 / D6 / D7 | GPIO 14 / 27 / 26 / 25 | |
| A | 5V via 220 ohm |
Raspberry Pi Wiring
Same rule as the ESP32: LCD powered at 5V, RW hard-wired to GND so nothing 5V ever reaches a Pi GPIO. The RPLCD Python library handles 4-bit timing.
| LCD Pin | Pi Pin (BCM) | Details |
|---|---|---|
| VSS / RW / K | Pin 6 (GND) | |
| VDD | Pin 2 (5V) | |
| V0 | 10k pot wiper | Pot ends to 5V and GND |
| RS | GPIO 26 | |
| E | GPIO 19 | |
| D4 / D5 / D6 / D7 | GPIO 13 / 6 / 5 / 11 | |
| A | 5V via 220 ohm |
Raspberry Pi Pico Wiring
| LCD Pin | Pico Pin | Details |
|---|---|---|
| VSS / RW / K | GND (pin 38) | |
| VDD | VBUS (pin 40, 5V) | USB 5V rail |
| V0 | 10k pot wiper | Pot ends to 5V and GND |
| RS | GP16 | |
| E | GP17 | |
| D4 / D5 / D6 / D7 | GP18 / GP19 / GP20 / GP21 | |
| A | VBUS via 220 ohm |
Code Examples
Each example prints a four-line status screen and updates a counter on the bottom row — the "hello world" that proves all 20 columns and 4 rows are addressed correctly.
Arduino
// LCD2004 20x4 Character LCD - Arduino Example (4-bit mode)
// RS->12, E->11, D4->5, D5->4, D6->3, D7->2, RW->GND
#include <LiquidCrystal.h>
LiquidCrystal lcd(12, 11, 5, 4, 3, 2); // RS, E, D4, D5, D6, D7
unsigned long count = 0;
void setup() {
lcd.begin(20, 4); // 20 columns, 4 rows
lcd.setCursor(0, 0);
lcd.print("ShillehTek LCD2004");
lcd.setCursor(0, 1);
lcd.print("20 x 4 characters");
lcd.setCursor(0, 2);
lcd.print("HD44780 4-bit mode");
}
void loop() {
lcd.setCursor(0, 3);
lcd.print("Uptime: ");
lcd.print(count++);
lcd.print(" s ");
delay(1000);
}
ESP32 (Arduino IDE)
// LCD2004 20x4 Character LCD - ESP32 Example (4-bit mode)
// RS->13, E->12, D4->14, D5->27, D6->26, D7->25, RW->GND, VDD->5V
#include <LiquidCrystal.h>
LiquidCrystal lcd(13, 12, 14, 27, 26, 25);
unsigned long count = 0;
void setup() {
lcd.begin(20, 4);
lcd.setCursor(0, 0);
lcd.print("ESP32 + LCD2004");
lcd.setCursor(0, 1);
lcd.print("Write-only is safe:");
lcd.setCursor(0, 2);
lcd.print("RW tied to GND");
}
void loop() {
lcd.setCursor(0, 3);
lcd.print("Count: ");
lcd.print(count++);
lcd.print(" ");
delay(1000);
}
Raspberry Pi (Python)
#!/usr/bin/env python3
# LCD2004 20x4 Character LCD - Raspberry Pi Example (RPLCD, 4-bit GPIO)
# RS->GPIO26, E->GPIO19, D4-D7->GPIO13/6/5/11, RW->GND, VDD->5V
# Install: pip3 install RPLCD RPi.GPIO
import time
from RPLCD.gpio import CharLCD
import RPi.GPIO as GPIO
lcd = CharLCD(numbering_mode=GPIO.BCM,
cols=20, rows=4,
pin_rs=26, pin_e=19,
pins_data=[13, 6, 5, 11],
auto_linebreaks=True)
lcd.clear()
lcd.write_string("ShillehTek LCD2004")
lcd.cursor_pos = (1, 0)
lcd.write_string("Raspberry Pi + RPLCD")
lcd.cursor_pos = (2, 0)
lcd.write_string("20 columns x 4 rows")
count = 0
try:
while True:
lcd.cursor_pos = (3, 0)
lcd.write_string("Uptime: {} s ".format(count))
count += 1
time.sleep(1)
except KeyboardInterrupt:
lcd.clear()
GPIO.cleanup()
print("Stopped by user")
Raspberry Pi Pico (MicroPython)
# LCD2004 20x4 Character LCD - Pico MicroPython Example (4-bit mode)
# RS->GP16, E->GP17, D4-D7->GP18/19/20/21, RW->GND, VDD->VBUS(5V)
from machine import Pin
import time
RS = Pin(16, Pin.OUT)
E = Pin(17, Pin.OUT)
DATA = [Pin(p, Pin.OUT) for p in (18, 19, 20, 21)] # D4..D7
ROW_ADDR = (0x00, 0x40, 0x14, 0x54) # 20x4 row offsets
def pulse():
E.value(1); time.sleep_us(2)
E.value(0); time.sleep_us(50)
def write4(nib):
for i in range(4):
DATA[i].value((nib >> i) & 1)
pulse()
def write_byte(b, rs):
RS.value(rs)
write4(b >> 4)
write4(b & 0x0F)
def cmd(b): write_byte(b, 0); time.sleep_ms(2)
def char(c): write_byte(ord(c), 1)
def lcd_init():
time.sleep_ms(40)
RS.value(0)
for _ in range(3):
write4(0x03); time.sleep_ms(5)
write4(0x02) # 4-bit mode
cmd(0x28) # 2-line logical, 5x8 font
cmd(0x0C) # display on, cursor off
cmd(0x06) # entry mode: increment
cmd(0x01) # clear
time.sleep_ms(2)
def goto(row, col):
cmd(0x80 | (ROW_ADDR[row] + col))
def text(row, s):
goto(row, 0)
for c in s[:20]:
char(c)
lcd_init()
text(0, "ShillehTek LCD2004")
text(1, "Pico MicroPython")
text(2, "4-bit, RW to GND")
count = 0
while True:
text(3, "Uptime: {} s".format(count))
count += 1
time.sleep(1)