Hello friends, I hope you all are doing great. Today, I am going to share the new I2C LCD 20x4 Library for Proteus V1.0. The I2C LCD 20x4 is the big brother of the 16x2: four rows of 20 characters, 80 characters in all, on a 98 x 60 mm board, with the same small black I2C backpack on its back. It still needs only four wires, GND, VCC, SDA and SCL, so it is the LCD to pick when a 16x2 is too small for your menu, your sensor readings or your status screen. With this I2C LCD 20x4 Library for Proteus, your sketch talks to an LCD that behaves like the real module, including the strange row order of every 20x4.

This is the third library of our LCD series, after the LCD 16x2 Library for Proteus V3.0 and the I2C LCD 16x2 Library for Proteus V1.0, and it is our own model of the whole module: the PCF8574 I2C port expander of the backpack and the HD44780 controller of the LCD, wired to a 20x4 glass the way the real one is. You get four devices: a blue and a yellow-green LCD, each as I2C LCD 20x4 Simple, with a live STATUS panel on the schematic, and I2C LCD 20x4 Advance, with a pop-up window, a bus log and four LCD test tools. The demo runs a small dashboard with the popular LiquidCrystal_I2C library on two Arduino UNOs, one LCD at 0x27 and one at 0x3F, with the Simple interface of our TEP Serial Monitor.

NOTICE: This library is very special to our team. A 20x4 is not just a 16x2 with more rows: its controller has two lines, and each line runs over two rows of the glass. We modelled that exactly, together with our own PCF8574 backpack with a clickable trimmer, jumper and address pads. It took our team a lot of hard work and many test runs. Your feedback is the fuel that keeps us going, so please tell us what you think in the comments below or in the LCD Library board of our forum. And if our free libraries help you, you can buy us a coffee. So, let's get started with the I2C LCD 20x4 Library for Proteus V1.0:

Figure: The I2C LCD 20x4 demo in Proteus 8.5: NODE 1 (blue, Simple, 0x27) and NODE 2 (yellow-green, Advance, 0x3F), LCD2's Display page with "Advance at 0x3F", the clock, "LCD 20x4 over I2C" and a 75 % bar, and U2 answering STATUS.

What is an I2C LCD 20x4?

An I2C LCD 20x4 is a 20x4 character LCD, often sold as the "2004A" (4 rows of 20 characters, each 5x8 dots, driven by the HD44780 controller), plus an I2C backpack soldered to its 16-pin header. On the backpack sit a PCF8574T chip, a blue contrast trimmer, a LED jumper for the backlight, three address pads A0, A1 and A2, and a 4-pin header: GND, VCC, SDA, SCL. It is the very same backpack as on the I2C LCD 16x2. For the LCD itself, read our Introduction to 20x4 LCD Module.

The PCF8574 is an I2C port expander: it has 8 output pins, and the Arduino sets all 8 at once by sending one byte over I2C. The backpack wires those 8 pins to the LCD's RS, RW, E, the backlight and D4 to D7. So the LCD still runs on its 4-bit bus, but the bus now comes from the PCF8574 instead of six Arduino pins.

The I2C LCD 20x4 at a Glance

The I2C LCD 20x4 in numbers
FeatureValue
Display4 rows x 20 characters, each 5x8 dots (HD44780 controller)
Board98 x 60 mm, the backpack on the back
InterfaceI2C: SDA and SCL (A4 and A5 on an Arduino UNO), plus GND and VCC
Chip on the backpackPCF8574T (addresses 0x20 - 0x27) or PCF8574AT (0x38 - 0x3F)
Default address0x27 (PCF8574T) or 0x3F (PCF8574AT): all three pads open
Supply5 V on VCC
ContrastThe blue trimmer on the backpack
BacklightSwitched by the PCF8574 (P3) through a transistor; the LED jumper must be fitted
SpeedAbout 1.5 ms per character at 100 kHz: 31 ms for a row, 125 ms for the whole screen
Arduino libraryLiquidCrystal_I2C (Library Manager) and Wire: LiquidCrystal_I2C lcd(0x27, 20, 4)

Terms You Will See in This Article

I2C LCD terms
TermMeaning
I2CA two-wire bus: SDA carries the data, SCL the clock; many devices share it, each with its own address
AddressThe 7-bit number a device answers to, written in hex (0x27)
ACK / NACKAfter each byte the receiver pulls SDA low (ACK) or not (NACK); nobody at an address means NACK
PCF8574The I2C chip on the backpack: one byte in, 8 output pins (P0 - P7)
Line / rowA line is the controller's 40-character memory row; a row is what you see on the glass
DDRAM / CGRAMThe LCD's character memory / its 8 own characters
TrimmerThe small blue pot on the backpack that sets the contrast (V0)

How the PCF8574 Backpack Works

The backpack connects the PCF8574's pins like this: P0 = RS, P1 = RW, P2 = E, P3 = the backlight and P4 to P7 = D4 to D7. The LCD's D0 to D3 stay unconnected, so the LCD always runs in 4-bit mode. The LCD takes a half byte (a nibble) when E falls, and the sketch cannot pulse E directly, so LiquidCrystal_I2C sends three port bytes for each nibble: the data, the data with E high, and the data with E low again. A character is two nibbles, so it costs six I2C transactions, about 1.5 ms at the standard 100 kHz.

On a 20x4 this matters more than on a 16x2: the whole screen is 80 characters, so redrawing it takes about 125 ms. Our demo shows the cure: it redraws only the parts that change. The address works as on every PCF8574 backpack: a PCF8574T answers at 0x20 to 0x27, a PCF8574AT at 0x38 to 0x3F, and the pads A0, A1 and A2 choose the address (an open pad is a 1, a bridged pad a 0). A new board has all pads open, so it is 0x27 or 0x3F. The I2C LCD 16x2 Library for Proteus V1.0 article explains the backpack in more detail.

What's New in I2C LCD 20x4 Library for Proteus V1.0

Here is what the first version brings:

  • A real 20x4 glass on our HD44780 model: the controller's line 1 shows on rows 1 and 3, line 2 on rows 2 and 4, exactly like every real 20x4, with two rows of blocks before the LCD is initialised.
  • Our own PCF8574 model inside TEPLCD.DLL, the same backpack as in our I2C LCD 16x2.
  • Clickable backpack parts: the contrast trimmer, the LED jumper and the A0 - A2 address pads work while the simulation runs, with a red WRONG ADDRESS warning.
  • Four devices in TEPI2CLCD20X4.LIB: blue and yellow-green, each Simple and Advance.
  • Four LCD test tools on the Advance: Init check, I2C bus, Speed and Characters.
  • A two-UNO dashboard demo with LiquidCrystal_I2C: a clock, a bar that fills once a minute, four text lines, and Serial commands such as TEXT1 to TEXT4, BAR, MINUTE and SCAN.
  • A compact package: about 1.43 MB, without the C++ source code.

The model file TEPLCD.DLL is shared with our LCD 16x2 V3.0 and I2C LCD 16x2 V1.0 libraries and runs their devices too. If you have them, keep this newer DLL.

I2C LCD 20x4 Simple vs I2C LCD 20x4 Advance

All four devices share one model and the same module. They differ in the glass colour and in where you see the extras:

The four devices (search "I2CLCD20X4" in Pick Devices; category Optoelectronics > Alphanumeric LCDs)
DeviceName in Pick DevicesGlassExtras
I2CLCD20X4BLUETEPI2C LCD 20x4 V1.0 Blue - SimpleBlue, white charactersSTATUS panel on the sheet
I2CLCD20X4BLUEADVTEPI2C LCD 20x4 V1.0 Blue - AdvanceBlue, white charactersPop-up window and tools
I2CLCD20X4GREENTEPI2C LCD 20x4 V1.0 Yellow-green - SimpleYellow-green, dark charactersSTATUS panel on the sheet
I2CLCD20X4GREENADVTEPI2C LCD 20x4 V1.0 Yellow-green - AdvanceYellow-green, dark charactersPop-up window and tools

Pick a Simple LCD when you want to see the LCD's state and its I2C address right on the schematic. Pick an Advance LCD when the schematic is crowded, or when you want the bus log and the tools. The backpack's trimmer, jumper and pads work on all four.

Download I2C LCD 20x4 Library for Proteus

Click the button below to download I2C-LCD-20x4-Library-for-Proteus-v1.0.zip (about 1.43 MB, without the C++ source code):

I2C LCD 20x4 Library for Proteus V1.0
  • README.txt: a detailed guide to the files, the wiring, the backpack, the row order, the test tools and real hardware.
  • Proteus Library Files: TEPI2CLCD20X4.LIB (all four devices), TEPSERIALMON.LIB and our Arduino UNO library ArduinoV3TEP.LIB / ArduinoV3TEP.IDX.
  • Proteus Model Files: TEPLCD.DLL and TEPSERIALMON.DLL.
  • Proteus Simulation: I2C-LCD-20x4-ArduinoUnoV3.pdsprj, I2C_LCD_20x4_Demo.hex (both UNOs run it) and copies of both DLLs.
  • Arduino Code: I2C_LCD_20x4_Demo.ino, the demo sketch. It needs the "LiquidCrystal I2C" library by Frank de Brabander from the Library Manager.

Want all four LCDs? This LCD is also part of our LCD Library for Proteus V3.0: the LCD 16x2, LCD 20x4, I2C LCD 16x2 and I2C LCD 20x4 in one library (16 parts, one DLL), with a demo in which one Arduino UNO drives all four.

No Proteus yet? Read How to Download and Install Proteus 7 and 8. New to libraries? Follow How to Install a New Library in Proteus.

How to Install I2C LCD 20x4 Library for Proteus

  1. Close Proteus and extract the whole zip file to a normal folder, for example your Desktop.
  2. Copy the four files from Proteus Library Files into the LIBRARY folder, usually C:\Program Files (x86)\Labcenter Electronics\Proteus 8 Professional\LIBRARY (on some PCs C:\ProgramData\Labcenter Electronics\Proteus 8 Professional\LIBRARY; use the folder that already holds Proteus's own .LIB files).
  3. Copy TEPLCD.DLL and TEPSERIALMON.DLL from Proteus Model Files into the MODELS folder.
  4. Delete TEPI2CLCD20X4.IDX from the LIBRARY folder if there is one; Proteus makes a new index.
  5. Start Proteus, press P and search for I2CLCD20X4. You get the four devices.

Tip: the demo project needs the .hex file and the two DLLs beside it, so keep the Proteus Simulation folder together. The library was tested in Proteus 8.5; Proteus 7 is not supported.

The I2C LCD 20x4 Board in Proteus

On a real module, the backpack hides behind the LCD. In Proteus we drew it folded up above the LCD, like an exploded view, so you can see and click its parts. The LCD board below it has the proportions of the real 2004A: the green PCB with its four mounting holes, the 16 pads (here with the backpack's pins soldered in), the metal frame and the glass with all 80 character cells. The backpack's 4-pin header sits on its left end with the pins pointing left, in the order printed on the real board: GND, VCC, SDA, SCL. Here is LCD1, the blue Simple LCD, running the demo's dashboard:

Figure: LCD1 (blue, Simple) at 0x27: "Hello from 0x27", "Up 00:00:20" with the heart, "LCD 20x4 over I2C" and the minute bar; READY - I2C 0x27, 4-BIT, 2 LINES = 4 ROWS, 5x8 DOTS, DISPLAY ON; I2C 0x27 8775 TX, WRITES 1464.

Pinout

The 4-pin header of the backpack, top to bottom
PinNameWhat it doesDemo connection
1GNDGroundGND (net label)
2VCCSupply, 5 VVCC (net label, the 5 V rail)
3SDAI2C data (open drain, with the backpack's pull-up)Arduino A4, through the label SDA_1
4SCLI2C clockArduino A5, through the label SCL_1

The Backpack Up Close

Figure: The backpack of LCD1: the pins with SCL_1, SDA_1, VCC and GND labels, the trimmer (V0 0.75 V), the LED jumper, the PCF8574T marked 0x27, the open A0 - A2 pads and the I2C LED, over the LCD's PWR, CMD and DATA LEDs, all lit.

The parts you see are drawn by the model and change while the simulation runs: the trimmer's slot turns and the label above it shows V0, the jumper shows its cap or two bare pins, a bridged pad gets a solder blob, the chip shows its type (PCF8574T or PCF8574AT) and its address, and the small I2C LED flashes blue when the Arduino talks to this address.

LEDs and Indicators

Board indicators (the same on all four devices)
IndicatorWhat it showsOn the real module?
The backlightThe glass glows when P3 is high and the jumper is fittedYes
PWR LED (red)Lit while the module has powerTEP addition
CMD LED (orange)Flashes when the LCD takes a commandTEP addition
DATA LED (green)Flashes when the LCD takes a characterTEP addition
I2C LED (blue)Flashes on every I2C transaction to this addressTEP addition
Chip markingPCF8574T or PCF8574AT and the address, liveTEP addition
SIMPLE / ADVANCE badge, PANEL / OPEN PANELWhich device this is, and the panel buttonTEP addition

Component Properties

I2C LCD properties (double-click the LCD > Edit Properties)
PropertyMeaningDefault
PANELSimple devices only: the STATUS panel at the start, OPEN or CLOSEDOPEN
ADDRESS0x20 - 0x27 (a PCF8574T board) or 0x38 - 0x3F (a PCF8574AT board); the pads follow it0x27
TRIMMERThe contrast trimmer at the start: V0 in % of VCC (0 = dark blocks, 50 = blank)15
LED_JUMPERFITTED or REMOVEDFITTED
BACKLIGHTBACKPACK (P3 and the jumper), ON or OFF (forced)BACKPACK
CONTRASTTRIMMER or AUTO (always good)TRIMMER

Every run starts from these properties; the clicks on the backpack and the panel's buttons change them for that run only. In the demo, LCD2 has ADDRESS = 0x3F: it is a PCF8574AT board.

How a 20x4 LCD Shows Its Four Rows

Here is the part that surprises everybody the first time. The HD44780 controller knows only two lines, each 40 characters long in its memory (DDRAM). A 16x2 shows the first 16 characters of each line. A 20x4 shows line 1 on rows 1 and 3 and line 2 on rows 2 and 4: the first 20 characters of a line go on the upper row, the next 20 on the row two places below. So the controller still runs in 2-line mode, and the four rows have these addresses:

The rows of a 20x4 LCD (LiquidCrystal_I2C does this mapping in setCursor)
Row on the glasssetCursor()DDRAM addressesController line
Row 1lcd.setCursor(0, 0)0x00 - 0x13Line 1, characters 1 - 20
Row 2lcd.setCursor(0, 1)0x40 - 0x53Line 2, characters 1 - 20
Row 3lcd.setCursor(0, 2)0x14 - 0x27Line 1, characters 21 - 40
Row 4lcd.setCursor(0, 3)0x54 - 0x67Line 2, characters 21 - 40

Two things follow from it. First, a text that runs past the end of row 1 does not continue on row 2: it continues on row 3. So print every row after its own setCursor(0, row), as our demo does. Second, a 20x4 that has power and contrast but was never initialised shows two rows of blocks, rows 1 and 3, because after power-on the controller is in 1-line mode and drives line 1 only. We started the demo paused, at time 0, before lcd.init():

Figure: Time 0, before the sketch's first byte: blocks on rows 1 and 3; the banner says WAITING FOR I2C - NOTHING WRITTEN TO 0x27 YET, I2C 0x27 IDLE, WRITES 0.

On a real 20x4, this picture means: the module has power and the contrast is fine, but the sketch never reached the LCD. Check the address, SDA and SCL, and that your sketch calls lcd.init(). One more classic: a sketch written for a 16x2, with LiquidCrystal_I2C lcd(0x27, 16, 2), cannot reach rows 3 and 4 properly; tell the library 20, 4.

Click the Backpack: Trimmer, Jumper and Address Pads

Turning the Contrast Trimmer

Click the left half of the blue trimmer (the - side) to turn the contrast down, the right half (the + side) to turn it up; each click moves V0 by 5 % of VCC, 0.25 V. At the start V0 is 0.75 V and the contrast is GOOD. Seven clicks on the - side bring V0 to 2.50 V: all four rows fade to faint ghosts, and the panel says TOO LOW:

Figure: Seven clicks on -: V0 2.50 V, CONTRAST TOO LOW; the dashboard is barely visible, exactly what a badly set trimmer does.

Now ten clicks on the + side: V0 drops to 0 V, the full 5 V is across the crystal, and even the empty dots of all 80 cells turn dark. The panel says TOO HIGH:

Figure: Ten clicks on +: V0 0.00 V, CONTRAST TOO HIGH; dark blocks behind the text in every cell. Three clicks on - bring it back to GOOD.

This is the classic "my I2C LCD shows nothing" case. On a real module, turn the trimmer with a small screwdriver until the blocks of rows 1 and 3 just show, then a little back.

Pulling the LED Jumper

Click the LED jumper: its cap comes off, two bare pins show, and the backlight goes out, even though the sketch keeps P3 high. The panel says OFF (NO JUMPER):

Figure: The jumper pulled: BACKLIGHT OFF (NO JUMPER); the glass is dark and the characters grey, while the clock runs on.

Click it again to fit the cap. The sketch can switch the backlight too: lcd.noBacklight() sets P3 low (the panel then says OFF (P3 LOW)), and lcd.backlight() sets it high again.

Bridging an Address Pad: WRONG ADDRESS

Click pad A0: it gets a solder blob, and the backpack's address becomes 0x26 at once. The sketch still writes to 0x27, so nothing reaches the LCD any more: the clock stops. The banner turns red and says WRONG ADDRESS - THE SKETCH WRITES TO 0x27, THIS BACKPACK IS 0x26, and the I2C readout says 0x26 SKETCH: 0x27:

Figure: Pad A0 bridged: the chip shows 0x26, the clock stops at 00:01:02, the banner says WRONG ADDRESS and the I2C readout 0x26 SKETCH: 0x27.

Click the pad again and everything runs on. On a real module, a wrong address looks just like a bad contrast: a blank LCD, or two rows of blocks if it was never initialised. In our model you see the reason at once, and the I2C bus tool of the Advance tells you both addresses.

I2C LCD 20x4 Simple: The STATUS Panel

Under the Simple LCD sits the I2C LCD 20x4 STATUS panel (see the pictures above):

The I2C LCD 20x4 STATUS panel
PartWhat it shows
BannerREADY (with the I2C address, bus, "2 LINES = 4 ROWS" and font), WAITING FOR I2C, WRONG ADDRESS, NOT INITIALISED, TOO FAST or NO POWER
DISPLAY, CURSOR, BLINK, ENTRYThe display, cursor, blink and entry mode flags
ADDRESSWhere the LCD's address counter points, with the row and column on the 20x4 (0x54 = ROW 4, COL 1)
VCC, CONTRASTThe supply, and the contrast with V0 (GOOD, WEAK, TOO LOW or TOO HIGH)
BACKLIGHTON (P3 HIGH), OFF (P3 LOW), OFF (NO JUMPER), or forced
I2CThis backpack's address and the transactions so far, or the address the sketch uses instead
WRITESBytes the LCD took (commands and characters)
BusThe last 6 transfers to the LCD, newest first
ButtonsBACKLIGHT (BACKPACK, ON, OFF) and CONTRAST (TRIMMER, AUTO (GOOD)) override the backpack for this run

The red X in the panel's header closes it, and PANEL on the board opens it again.

I2C LCD 20x4 Advance: The Pop-Up Window

The Advance LCD keeps only the module on the schematic. Here is LCD2, the yellow-green Advance LCD; its chip says PCF8574AT and 0x3F:

Figure: LCD2 (yellow-green, Advance) at 0x3F: "Hello from 0x3F", "Up 00:01:32"; the chip reads PCF8574AT 0x3F.

At Run, the "TEP I2C LCD 20x4 Advance" pop-up opens with the look of our TEP Serial Monitor: move it by its header, resize or minimise it, and reopen it with OPEN PANEL. The header has the Display, Bus log and Tests tabs and the theme, Settings and Help icons. Under the header, the BACKLIGHT (Backpack, On, Off) and CONTRAST (Trimmer, Auto) buttons work like the Simple panel's.

The Display Page

Figure: The Display page after TEXT1 and BAR 75: "Advance at 0x3F", "Up 00:02:10", "LCD 20x4 over I2C" and a 75 % bar; LINES 2 (4 ROWS), ADDRESS 0x54; the status bar says 4-bit, 2 lines = 4 rows.
  • The glass: a big copy of the LCD, dot by dot.
  • Tiles: BUS 4-BIT, LINES 2 (4 ROWS), FONT 5x8, DISPLAY ON, CURSOR OFF, BLINK OFF, ENTRY INCREMENT and the address counter.
  • DDRAM: the two lines of 40 bytes, and here you can see the row order: line 1 holds "Advance at 0x3F" in its first 20 cells and "LCD 20x4 over I2C" in the next 20; line 2 holds the clock and then the bar. Green cells are on the glass.
  • CGRAM: the 8 own characters of the demo: two hearts, a smiley, a bell and four bar steps.
  • Supply, contrast trimmer and backlight: VCC, the contrast with V0 and the trimmer setting, and the backlight with P3 and the jumper.
  • The latest transfers: the bytes the LCD took, about 1.5 ms apart over I2C.

The Bus Log Page

Figure: The Bus log: a clock update ("Up 00:02:11") after a pause of 467.93 ms; 1.49 ms between bytes, 1.45 ms of margin.

The Bus log shows what the LCD took from the backpack: one row per byte, newest first, with the time, the gap, CMD or DATA, the byte, its meaning and the margin to the busy time. Read the picture from the bottom up: SET DDRAM ADDRESS 0x40 (row 2), then "U", "p", a space, the digits of the clock, the spaces up to 0x53, SET DDRAM ADDRESS 0x53 and the heart in the last cell of row 2. Every byte comes 1.49 ms after the one before: that is the I2C speed. The controller needs only 37 to 41 us, so there is a margin of 1.45 ms.

The Tests Page: Four LCD Test Tools

The tools read the bus as the model sees it during the running simulation. Each has a bold "Good for:" line and a verdict per row: green is fine, amber means check, red means wrong. All the results below come from LCD2 in Proteus 8.5.

1. Init Check: Is the Start-Up Right?

Figure: Init check: every row green, Lines "2 lines (N = 1): rows 1 + 3 and 2 + 4", verdict "the LCD is initialised like the datasheet's figure (LiquidCrystal_I2C does this)".
Init check (LCD2)
CheckResult in the picture
Wait after power-on1121.7 ms (the datasheet asks for 40 ms)
Function set x3 (wake-up)5 function sets seen
Wait after the 1st / 2nd5.27 ms / 5.27 ms (at least 4.1 ms / 100 us)
Interface4-BIT (DB4..DB7)
Lines2 lines (N = 1): rows 1 + 3 and 2 + 4
Display on, clear, entry modeYes, yes, increment (left to right)
Bytes ignored (busy)None during the start-up
VerdictThe LCD is initialised like the datasheet's figure

The Lines row is the one that matters most on a 20x4: with N = 0 (1-line mode), rows 2 and 4 would stay blank and rows 1 and 3 would show blocks, and the tool would turn red. Why 1121.7 ms after power-on? LiquidCrystal_I2C's init() waits 50 ms, writes the port once, and then waits a whole second before it starts the LCD; add the sketch's address search, and the first command arrives 1.12 s after power-on.

2. I2C Bus: Is the Sketch Talking to This Backpack?

Figure: I2C bus: 0x3F, PCF8574AT, pads open; the sketch writes to 0x3F; 2 address checks here and 126 to other addresses; SCL 100 kHz; the port 0x09 = RS 1, RW 0, E 0, LED 1, D7-D4 0x0.

Good for: "a blank I2C LCD - the wrong address, SDA / SCL swapped, or a clock too fast." The tool shows:

I2C bus (LCD2)
RowResult in the picture
This backpack0x3F - PCF8574AT, pads A2 A1 A0: open open open
The sketch writes to0x3F - this backpack (51830 writes since power-on)
Measuring for134.450 s of simulation: 51832 transactions to 0x3F, 51830 bytes
Address checks2 to this address, 126 transactions to other addresses (a scanner / auto-detect)
SCL clock100 kHz (the PCF8574 is rated for 100 kHz)
The port now0x09 = RS 1 RW 0 E 0 LED 1 D7-D4 0x0
Bytes lost (too fast)None

Where do the 126 checks come from? The sketch's address search at the start tried 0x20 to 0x27 and 0x38 to 0x3E before it found 0x3F (15 checks), and then the SCAN command on U2 tried every address from 0x08 to 0x77. Below the rows, the last transactions are listed with every port byte decoded: you can watch the E bit go 0, 1, 0 as LiquidCrystal_I2C pulses each nibble. Bridge a pad of LCD2 and the "sketch writes to" row turns red.

3. Speed: How Long Does a 20x4 Screen Take?

Figure: Speed: 1489.2 us per byte, a full line in 31.27 ms, the full screen in 125.10 ms (at most 8 screens per second), 671 characters per second.

Good for: "flicker / slow screens - how long a full line and a full screen take on this bus." The tool takes the typical gap between two bytes inside a burst of writes:

Speed (LCD2, the demo sketch with LiquidCrystal_I2C)
ResultValue
Time per byte1489.2 us (the controller needs only 37 to 41 us)
A full line (20 characters + the address)31.27 ms
A full screen (80 characters + 4 addresses)125.10 ms, so at most 8 screens per second
Characters per second671 while writing
BusI2C at 100 kHz: LiquidCrystal_I2C sends 3 port writes per nibble, 6 per byte

Eight screens per second is fine for a clock, but a sketch that clears and rewrites all four rows in a loop will flicker on a 20x4. Write only what changed: our demo updates row 2 twice a second and row 4 only when the bar grows. In the chart, the green bars are characters, the blue ones commands, and the two tall bars are the pauses between the clock updates.

4. Characters: Find Any Symbol's Code

Figure: Characters: the A00 ROM; the characters on the glass are highlighted, among them the hearts and the full block 0xFF of the bar; the degree sign is selected: Code 0xDF = 223, lcd.write(223);

Good for: "finding a symbol's code (the degree sign is 0xDF = 223) - click a character." The map shows all 256 codes of the A00 ROM in the datasheet's layout, with the characters now on the display highlighted. Click one to see its code and the line to print it, for example lcd.write(223); for a temperature in degrees, and, for one of the 8 own characters, its rows as a byte array for createChar().

I2C LCD 20x4 with Arduino in Proteus

Open I2C-LCD-20x4-ArduinoUnoV3.pdsprj from the Proteus Simulation folder, with I2C_LCD_20x4_Demo.hex and both DLLs beside it. Its two Arduino UNOs from our Arduino Library for Proteus V3.0 run the same HEX file:

  • NODE 1 - SIMPLE (on-sheet panel): UNO 1 (ARD1) with LCD1 (blue, Simple, address 0x27) and Serial Monitor U1.
  • NODE 2 - ADVANCE (pop-up panel): UNO 2 (ARD2) with LCD2 (yellow-green, Advance, address 0x3F, a PCF8574AT board) and Serial Monitor U2.
Figure: The whole circuit running, without the pop-up windows: LCD1 shows "Hello from 0x27", LCD2 "Hello from 0x3F", both "Up 00:01:29" and the minute bar.

Wiring

Demo wiring (the same on both UNOs)
Backpack pinArduino UNOWhy
GNDGNDGround
VCC5VSupply
SDAA4 (SDA)I2C data
SCLA5 (SCL)I2C clock
Serial Monitor RXD / TXD / GNDD1 / D0 / groundShows what the Arduino prints, sends what you type

On an Arduino UNO, SDA is A4 and SCL is A5 (see our Introduction to Arduino UNO). In the demo, every pin goes through a short wire to a net label: in Proteus, terminals with the same name are connected. SDA_1 at the backpack of LCD1 meets SDA_1 at A4 of UNO 1, and node 2 has its own SDA_2 and SCL_2, so the two buses stay apart. VCC and GND are Proteus's power rails.

Figure: Node 1: the backpack's SCL_1, SDA_1, VCC and GND labels, and SDA_1 / SCL_1 at A4 and A5 of ARD1.

Why labels and not wires? The backpack's pins and the UNO's A4 / A5 both point left, with SDA above SCL on both. Any pair of wires between them must turn around, and turning around swaps their order, so two direct wires would have to cross. Labels keep the schematic clean.

The Arduino Code

The demo sketch, I2C_LCD_20x4_Demo V1.0, uses the LiquidCrystal_I2C library (version 1.1.2, "LiquidCrystal I2C" by Frank de Brabander in the Library Manager) and Arduino's Wire library. It starts with the LCD object: 20 columns and 4 rows, and an address that is only a starting value:

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

LiquidCrystal_I2C lcd(0x27, 20, 4);    // the address is replaced by the one found
byte lcdAddress = 0;                   // 0 = no LCD answered

The sketch finds the address itself. An I2C address check is an empty write: if a device answers with ACK, Wire.endTransmission() returns 0. The search tries the PCF8574T range first, then the PCF8574AT range:

bool answers(byte a) {                 // an address check: does a device ACK?
  Wire.beginTransmission(a);
  return Wire.endTransmission() == 0;
}

byte findLcd() {                       // the first PCF8574 / PCF8574A address that answers
  const byte first[2] = {0x20, 0x38};
  for (byte b = 0; b < 2; b++)
    for (byte a = first[b]; a < first[b] + 8; a++)
      if (answers(a)) return a;
  return 0;
}

setup() uses what it found: a new LiquidCrystal_I2C object with that address and the 20x4 size, then lcd.init() and lcd.backlight(). That is why the same HEX file works on both UNOs, at 0x27 and at 0x3F:

void setup() {
  Serial.begin(9600);
  Serial.println(F("I2C LCD 20x4 V1.0 demo - The Engineering Projects"));
  Wire.begin();
  lcdAddress = findLcd();
  if (lcdAddress) {
    lcd = LiquidCrystal_I2C(lcdAddress, 20, 4);
    Serial.print(F("LCD found at ")); printHex(lcdAddress); Serial.println();
  } else Serial.println(F("No I2C LCD found - check SDA (A4), SCL (A5), VCC"));
  lcd.init();                  // the HD44780 start-up over I2C (the library waits ~1 s)
  lcd.backlight();

Then come the 8 own characters: a beating heart in two frames, a smiley, a bell and four bar steps of 1 to 4 columns. A bar step is just a column pattern repeated over the 8 rows of the cell:

byte heartBig[8]   = {0x00, 0x0A, 0x1F, 0x1F, 0x1F, 0x0E, 0x04, 0x00};
byte heartSmall[8] = {0x00, 0x00, 0x0A, 0x0E, 0x0E, 0x04, 0x00, 0x00};
byte bar1[8]       = {0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10};

As on any HD44780, createChar() leaves the address counter in CGRAM, so the sketch calls setCursor() before it prints the start screen with the address in it:

  lcd.setCursor(0, 0);         // createChar leaves the address in CGRAM
  snprintf(text[0], sizeof text[0], "Hello from 0x%02X", lcdAddress ? lcdAddress : 0x27);
  redraw();

To print a row, the sketch moves to the start of that row and pads the text with spaces to 20 characters, so a shorter text never leaves old characters behind, and a text never runs on into the next line of the controller (which, remember, would be two rows further down):

void printLine(byte row, const char* s) {     // the text, padded to 20
  lcd.setCursor(0, row);
  byte n = 0;
  for (; s[n] && n < 20; n++) lcd.print(s[n]);
  for (; n < 20; n++) lcd.print(' ');
}

The clock on row 2 is the up time from millis(), with the heart in the last cell, column 19:

void showClock() {
  unsigned long s = millis() / 1000;
  char buf[21];
  snprintf(buf, sizeof buf, "Up %02lu:%02lu:%02lu", s / 3600 % 100, s / 60 % 60, s % 60);
  printLine(1, buf);
  lcd.setCursor(19, 1);
  lcd.write(byte(beat ? HEART_BIG : HEART_SMALL));
}

Row 4 is the bar: 20 cells of 5 columns give 100 steps, so BAR 37 is exactly 37 columns. A full cell is the ROM's block 0xFF, a part cell one of the four bar characters. The first line of showBar() is the speed trick: if the bar has not changed, nothing is sent over I2C at all. The minute bar simply turns the seconds into steps, so it fills once a minute:

void showBar(int steps) {              // 0..100 steps: 20 cells x 5 columns
  if (steps == shownSteps) return;
  shownSteps = steps;
  lcd.setCursor(0, 3);
  for (byte c = 0; c < 20; c++) {
    int left = steps - c * 5;
    if (left >= 5) lcd.write(byte(0xFF));
    else if (left >= 1) lcd.write(byte(4 + left - 1));
    else lcd.print(' ');
  }
}

void showMinute() {                    // the bar fills once a minute
  unsigned long s = millis() / 1000;
  showBar((s % 60 + 1) * 100 / 60);
}

One command handles all four text rows: TEXT1 to TEXT4. TEXT2 replaces the clock and TEXT4 the bar:

  else if (!strncasecmp(line, "TEXT", 4) && line[4] >= '1' && line[4] <= '4' && !line[5]) {
    byte r = line[4] - '1';
    strncpy(text[r], arg, 20); text[r][20] = 0;
    if (r == 1) line2 = SHOW_TEXT;
    if (r == 3) line4 = SHOW_TEXT;
    printLine(r, text[r]);

With a backpack, the backlight is not a PWM pin any more but bit P3 of the PCF8574, so the LIGHT command switches it on or off:

  } else if (!strcasecmp(line, "LIGHT")) {
    if (onOff(arg, lightOn)) {
      if (lightOn) lcd.backlight(); else lcd.noBacklight();
      Serial.print(F("OK - backlight ")); Serial.println(lightOn ? F("on") : F("off"));
    }

The loop reads the Serial Monitor and, twice a second, beats the heart, updates the clock and the minute bar, without delay():

  unsigned long now = millis();
  if (now - lastTick >= 500) {       // the heart beats twice a second
    lastTick = now;
    beat = !beat;
    if (line2 == SHOW_CLOCK) showClock();
    if (line4 == SHOW_MINUTE) showMinute();
    parkCursor();
  }

SCAN lists every device on the bus, from 0x08 to 0x77, with the same address check: the classic I2C scanner, built into the demo. To change the sketch, install the library from the Library Manager, export a new HEX file and load it into both UNOs; see How to get the HEX file from Arduino. New to Arduino? Start with our Arduino Tutorial for Beginners.

Serial Monitor Commands

Commands of the demo sketch, upper or lower case (quick buttons in the full interface: U1 STATUS, TEXT1 Hello, TEP!, LIGHT OFF, LIGHT ON; U2 STATUS, BAR 75, SCAN, MINUTE)
CommandWhat it does
HELPThe command list
STATUSThe LCD's address, lines 1 to 4, the backlight, cursor and blink
SCANEvery I2C device on the bus (0x08 - 0x77)
TEXT1 <text> ... TEXT4 <text>Row 1 to row 4 (20 characters; a longer text is cut); TEXT2 stops the clock, TEXT4 the bar
CLOCKRow 2 shows the up time with the beating heart again
MINUTERow 4 shows the bar that fills once a minute again
BAR <0-100>Row 4 shows a bar graph (100 steps)
CHARSRow 4 shows the 8 own characters
LIGHT ON / OFFThe backlight (P3 of the backpack)
CURSOR ON / OFF, BLINK ON / OFFThe underline cursor, the blinking block cursor (after the row 1 text)
CLEARAll four rows empty

How to Run the Demo

  1. Press Run. Both Serial Monitors (9600 baud) and LCD2's pop-up open; move them aside, as they open over the schematic. After about a second the LCDs show the dashboard, "Hello from 0x27" and "Hello from 0x3F".
  2. Watch the bar on row 4 fill up within a minute and start again.
  3. Click the trimmer halves, the LED jumper and an address pad of LCD1, and watch the panel.
  4. On U1, type TEXT3 with your own text, BAR 37 and LIGHT OFF; on U2, SCAN and STATUS.
  5. On LCD1's panel, try BACKLIGHT OFF and CONTRAST AUTO (GOOD).
  6. On LCD2's pop-up, look at the DDRAM grid and the Bus log, and run the four tools.

I2C LCD 20x4 Proteus Simulation Results

The Serial Monitor in Its Simple Interface

Both monitors in this demo run in the Simple interface of our TEP Serial Monitor, as in every monitor screenshot here: only the log, the send box and one slim toolbar row stay visible.

  • Switch to it: right-click the log and choose Simple interface, or press Ctrl+Shift+U.
  • Back to the full view: click the corner button, press Esc or Ctrl+Shift+U, or right-click and choose Full interface.
  • Your choice is remembered.

Type a command in the send box and press Enter. Every line the demo prints is 80 characters or less. Read all about the monitor in TEP Serial Monitor Advance for Proteus.

U1: HELP and STATUS

Figure: U1 in the Simple interface: "LCD found at 0x27", the HELP list and STATUS with lines 1 to 4.
I2C LCD 20x4 V1.0 demo - The Engineering Projects
LCD found at 0x27
Type HELP for the commands
HELP
Commands:
  TEXT1..TEXT4 <text>  a line (20 characters)
  CLOCK          line 2 = the up time
  MINUTE         line 4 = a bar that fills once a minute
  BAR <0-100>    line 4 = a bar graph (100 steps)
  CHARS          line 4 = the 8 CGRAM characters
  LIGHT ON|OFF   the backlight (the backpack's P3)
  CURSOR ON|OFF  BLINK ON|OFF
  CLEAR  SCAN  STATUS  HELP
STATUS
LCD at 0x27
Line 1: Hello from 0x27
Line 2: clock
Line 3: LCD 20x4 over I2C
Line 4: minute bar
Backlight on, cursor off, blink off

The second line is the address search at work: the sketch found the backpack at 0x27 by itself. In the sketch's own words, "line" means a row of the screen here.

U2: SCAN, BAR, TEXT1 and STATUS

Figure: U2 in the Simple interface: "LCD found at 0x3F", SCAN ("I2C devices: 0x3F"), BAR 75, TEXT1 Advance at 0x3F and STATUS.
I2C LCD 20x4 V1.0 demo - The Engineering Projects
LCD found at 0x3F
Type HELP for the commands
SCAN
I2C devices: 0x3F
BAR 75
OK - bar 75%
TEXT1 Advance at 0x3F
OK - line 1: Advance at 0x3F
STATUS
LCD at 0x3F
Line 1: Advance at 0x3F
Line 2: clock
Line 3: LCD 20x4 over I2C
Line 4: bar 75%
Backlight on, cursor off, blink off

SCAN finds one device, the backpack at 0x3F: each UNO has its own I2C bus in this demo. BAR 75 draws 15 full cells on row 4, and the Display page and the featured picture above show the result on the yellow-green glass.

Troubleshooting

  • The glass stays dark and the PWR LED is off: no power. Check VCC and GND (the panel says NO POWER with the voltage).
  • WAITING FOR I2C: nothing reached this address. Check SDA to A4 and SCL to A5 (swapped lines look the same: nothing works), and that the sketch runs.
  • WRONG ADDRESS: the sketch writes to another address. Change the address in the sketch, or the ADDRESS property or the pads; the I2C bus tool shows both addresses.
  • Blocks on rows 1 and 3: the LCD was not initialised (lcd.init() or lcd.begin() missing; on the Advance, run the Init check).
  • Text from row 1 goes on in row 3: that is the 20x4's row order. Print every row after its own setCursor(0, row), and keep each text to 20 characters.
  • Rows 3 and 4 never show what you print: the sketch was written for a 16x2; give the library 20, 4.
  • Faint characters or blocks behind them: the contrast. Click the trimmer, or set CONTRAST AUTO (GOOD).
  • The backlight stays off: the LED jumper, lcd.backlight() in the sketch, or BACKLIGHT OFF on the panel.
  • The part is not simulated, or no monitor window: TEPLCD.DLL or TEPSERIALMON.DLL is missing from MODELS and the project folder; click OPEN MONITOR on a closed monitor.
  • The Advance pop-up does not appear: click OPEN PANEL on the board, or turn "Open the panel at Run" back on in Settings.

Things to Know Before Using a Real I2C LCD 20x4

The demo sketch uses only Wire, LiquidCrystal_I2C and Serial, so it runs on a real UNO and I2C LCD 20x4 too. Keep in mind:

  • Turn the trimmer first: nine times out of ten, a blank I2C LCD is just the contrast. Turn the blue trimmer on the back until the blocks of rows 1 and 3 show, then a little back.
  • Find the address: run an I2C scanner (or the demo's SCAN). A PCF8574T board is 0x27 and a PCF8574AT board 0x3F, unless pads are bridged.
  • Tell the library the size: LiquidCrystal_I2C lcd(0x27, 20, 4), and print every row after its own setCursor().
  • The LED jumper must be fitted for the backlight; some sketches also forget lcd.backlight(). The 20x4's backlight draws more current than a 16x2's, so check your module's datasheet when it runs from a battery or a weak supply.
  • Pull-ups: the backpack has 4.7k pull-ups on SDA and SCL. For long wires or many devices on the bus, check the total.
  • 100 kHz: the PCF8574 is rated for Wire's standard 100 kHz; 400 kHz usually works but is outside its datasheet.
  • Speed: about 1.5 ms per character and 125 ms for the whole screen, so update only what changed.

Limitations of the Simulation

  • Not modelled: the PCF8574's interrupt output (not wired on the backpack), I2C timing violations (any clock the AVR makes is accepted; the I2C bus tool shows when it is above 100 kHz), the 5x10 font on the glass, the A02 ROM, the slow response of the liquid crystal and the module's current draw.
  • The backpack is drawn above the LCD so you can see its parts; on a real module it sits behind the LCD.
  • The model is our own implementation, written from the HD44780 and PCF8574 datasheets. HD44780 is a Hitachi part and PCF8574 an NXP / TI part; this library is not made or endorsed by them or by Labcenter.
  • Licences: the demo sketch uses LiquidCrystal_I2C 1.1.2 and Arduino's Wire library. The HEX file contains them and the Arduino AVR core 1.8.6, which includes LGPL-licensed code; its source code is on GitHub.
  • Tested in Proteus 8.5 in October 2026 (the demo as shipped, both LCDs, the dashboard, every Serial command, the trimmer, the jumper, the pads, the panel, the pop-up, the four tools and every screenshot here; about 0.63 x real time with the pop-up and both monitors open; our LCD 16x2 V3.0 and I2C LCD 16x2 V1.0 demos run unchanged on the same DLL) and with automatic PC checks, 0 failed: 45 with the real 20x4 demo sketch and LiquidCrystal_I2C talking bit by bit to the backpack model, 7,202 on the HD44780 controller, and the demo project read back from disk. Proteus 7 is not supported.

So, that was all about the I2C LCD 20x4 Library for Proteus V1.0. I hope the real row order, the clickable trimmer, jumper and pads, and the test tools make the big I2C LCD much easier to understand, so your 20x4 works the first time you wire a real one. If you use the I2C LCD 20x4 Library for Proteus in a project, please share your feedback in the comments or in our forum, and if you have any questions, ask in the comments and I will help you out. Till the next tutorial, take care and have fun!