Documentation

LCD2004 20x4 Character LCD Display with Blue Backlight for Arduino, Raspberry Pi & ESP32 | ShillehTek Product Manual
Documentation / LCD2004 20x4 Character LCD Display with Blue Backlight for Arduino, Raspberry Pi & ESP32 | ShillehTek Product Manual

LCD2004 20x4 Character LCD Display with Blue Backlight for Arduino, Raspberry Pi & ESP32 | ShillehTek Product Manual

manualshillehtek

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

Display
20 columns x 4 rows
Controller
HD44780-compatible
Interface
Parallel, 4-bit or 8-bit
Supply Voltage
5V (logic + backlight)
Backlight
Blue, white characters
Pins Used (4-bit)
6 GPIO + power

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.

LCD2004 20x4 character LCD pinout diagram showing VSS, VDD, V0 contrast, RS, RW, E, D0-D7 data bus, anode and cathode pins

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 -
Tip: Blank screen or a row of solid blocks almost always means contrast — turn the V0 pot slowly until characters appear. If your module has no pot, try V0 through a ~1-2k resistor to GND as a starting point.

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
Warning: Never wire RW to the ESP32 and issue reads — the LCD would answer at 5V levels. Keep RW grounded and the interface is write-only and 3.3V-safe.

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
Tip: Install the library with pip3 install RPLCD — it supports 20x4 out of the box and handles all the nibble timing for you.

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
Note: With RW grounded the LCD never outputs voltage toward the Pico, so driving its inputs from 3.3V GPIO is safe and works reliably.

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_arduino.ino
// 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_esp32.ino
// 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)

lcd2004_rpi.py
#!/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_pico.py
# 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)

Frequently Asked Questions

The screen powers on but shows nothing (or one row of solid blocks). What's wrong?
Ninety percent of the time it's contrast: adjust the 10k pot on V0 until characters appear. A single row of blocks specifically means the controller hasn't been initialized — check RS, E, and the four data lines, and make sure your code declares the right pins. The remaining cases are usually VSS/VDD swapped or a loose breadboard jumper on E.
Can I use it with 3.3V boards like the ESP32 and Pico?
Yes, with the standard trick: power the LCD from 5V, tie RW permanently to GND, and drive RS/E/D4-D7 from 3.3V GPIO. Because the display never transmits back, no 5V ever reaches your microcontroller, and HD44780 clones register 3.3V highs dependably in this direction. The wiring tabs above follow exactly this arrangement.
Why does everyone use 4-bit mode instead of 8-bit?
Eight-bit mode saves microseconds per character but costs four extra GPIO pins — and a character LCD is slow enough that you will never notice the difference. Four-bit mode sends each byte as two nibbles on D4-D7 and leaves D0-D3 unconnected, which is why every library defaults to it.
Can I cut this down to two wires?
Yes — a PCF8574 I2C backpack (ShillehTek sells one made for 1602/2004 displays) solders onto the 16-pin header and exposes the whole display over SDA/SCL at address 0x27 or 0x3F. You then use the LiquidCrystal_I2C or RPLCD i2c driver instead. It's the cleanest option on pin-starved boards, and it includes its own contrast pot.
How do I make custom characters like a degree symbol or bar graph blocks?
The HD44780 has eight CGRAM slots for user-defined 5x8 glyphs. In Arduino, define a byte[8] bitmap and call lcd.createChar(0, bitmap), then lcd.write(byte(0)). RPLCD offers create_char() the same way. Degree symbols, arrows, and battery/bar-graph glyphs are the classic uses — you can redefine slots on the fly for simple animations.
How much current does it draw?
Logic is negligible (~2 mA); the backlight dominates at roughly 25-60 mA depending on the module and series resistor. That's fine from USB or an Arduino's 5V pin. If you need to save power, switch the backlight through a transistor on A — the display itself remains readable in good light with the backlight off.
Text appears in the wrong order on lines 3 and 4. Why?
The 20x4 DDRAM map is non-contiguous: rows start at addresses 0x00, 0x40, 0x14, and 0x54, so naive "keep printing and let it wrap" code jumps from row 1 to row 3. Always position the cursor per row (setCursor / cursor_pos / the goto() helper in the Pico example) instead of relying on wraparound.

Related Tutorials