
I2C LCD 20x4 Library for Proteus V1.0 (PCF8574 + Arduino)

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:
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
| Feature | Value |
|---|---|
| Display | 4 rows x 20 characters, each 5x8 dots (HD44780 controller) |
| Board | 98 x 60 mm, the backpack on the back |
| Interface | I2C: SDA and SCL (A4 and A5 on an Arduino UNO), plus GND and VCC |
| Chip on the backpack | PCF8574T (addresses 0x20 - 0x27) or PCF8574AT (0x38 - 0x3F) |
| Default address | 0x27 (PCF8574T) or 0x3F (PCF8574AT): all three pads open |
| Supply | 5 V on VCC |
| Contrast | The blue trimmer on the backpack |
| Backlight | Switched by the PCF8574 (P3) through a transistor; the LED jumper must be fitted |
| Speed | About 1.5 ms per character at 100 kHz: 31 ms for a row, 125 ms for the whole screen |
| Arduino library | LiquidCrystal_I2C (Library Manager) and Wire: LiquidCrystal_I2C lcd(0x27, 20, 4) |
Terms You Will See in This Article
| Term | Meaning |
|---|---|
| I2C | A two-wire bus: SDA carries the data, SCL the clock; many devices share it, each with its own address |
| Address | The 7-bit number a device answers to, written in hex (0x27) |
| ACK / NACK | After each byte the receiver pulls SDA low (ACK) or not (NACK); nobody at an address means NACK |
| PCF8574 | The I2C chip on the backpack: one byte in, 8 output pins (P0 - P7) |
| Line / row | A line is the controller's 40-character memory row; a row is what you see on the glass |
| DDRAM / CGRAM | The LCD's character memory / its 8 own characters |
| Trimmer | The 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:
| Device | Name in Pick Devices | Glass | Extras |
|---|---|---|---|
| I2CLCD20X4BLUETEP | I2C LCD 20x4 V1.0 Blue - Simple | Blue, white characters | STATUS panel on the sheet |
| I2CLCD20X4BLUEADVTEP | I2C LCD 20x4 V1.0 Blue - Advance | Blue, white characters | Pop-up window and tools |
| I2CLCD20X4GREENTEP | I2C LCD 20x4 V1.0 Yellow-green - Simple | Yellow-green, dark characters | STATUS panel on the sheet |
| I2CLCD20X4GREENADVTEP | I2C LCD 20x4 V1.0 Yellow-green - Advance | Yellow-green, dark characters | Pop-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.LIBand our Arduino UNO libraryArduinoV3TEP.LIB/ArduinoV3TEP.IDX. - Proteus Model Files:
TEPLCD.DLLandTEPSERIALMON.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
- Close Proteus and extract the whole zip file to a normal folder, for example your Desktop.
- 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 PCsC:\ProgramData\Labcenter Electronics\Proteus 8 Professional\LIBRARY; use the folder that already holds Proteus's own .LIB files). - Copy
TEPLCD.DLLandTEPSERIALMON.DLLfrom Proteus Model Files into the MODELS folder. - Delete
TEPI2CLCD20X4.IDXfrom the LIBRARY folder if there is one; Proteus makes a new index. - 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:
Pinout
| Pin | Name | What it does | Demo connection |
|---|---|---|---|
| 1 | GND | Ground | GND (net label) |
| 2 | VCC | Supply, 5 V | VCC (net label, the 5 V rail) |
| 3 | SDA | I2C data (open drain, with the backpack's pull-up) | Arduino A4, through the label SDA_1 |
| 4 | SCL | I2C clock | Arduino A5, through the label SCL_1 |
The Backpack Up Close
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
| Indicator | What it shows | On the real module? |
|---|---|---|
| The backlight | The glass glows when P3 is high and the jumper is fitted | Yes |
| PWR LED (red) | Lit while the module has power | TEP addition |
| CMD LED (orange) | Flashes when the LCD takes a command | TEP addition |
| DATA LED (green) | Flashes when the LCD takes a character | TEP addition |
| I2C LED (blue) | Flashes on every I2C transaction to this address | TEP addition |
| Chip marking | PCF8574T or PCF8574AT and the address, live | TEP addition |
| SIMPLE / ADVANCE badge, PANEL / OPEN PANEL | Which device this is, and the panel button | TEP addition |
Component Properties
| Property | Meaning | Default |
|---|---|---|
| PANEL | Simple devices only: the STATUS panel at the start, OPEN or CLOSED | OPEN |
| ADDRESS | 0x20 - 0x27 (a PCF8574T board) or 0x38 - 0x3F (a PCF8574AT board); the pads follow it | 0x27 |
| TRIMMER | The contrast trimmer at the start: V0 in % of VCC (0 = dark blocks, 50 = blank) | 15 |
| LED_JUMPER | FITTED or REMOVED | FITTED |
| BACKLIGHT | BACKPACK (P3 and the jumper), ON or OFF (forced) | BACKPACK |
| CONTRAST | TRIMMER 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:
| Row on the glass | setCursor() | DDRAM addresses | Controller line |
|---|---|---|---|
| Row 1 | lcd.setCursor(0, 0) | 0x00 - 0x13 | Line 1, characters 1 - 20 |
| Row 2 | lcd.setCursor(0, 1) | 0x40 - 0x53 | Line 2, characters 1 - 20 |
| Row 3 | lcd.setCursor(0, 2) | 0x14 - 0x27 | Line 1, characters 21 - 40 |
| Row 4 | lcd.setCursor(0, 3) | 0x54 - 0x67 | Line 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():
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:
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:
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):
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:
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):
| Part | What it shows |
|---|---|
| Banner | READY (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, ENTRY | The display, cursor, blink and entry mode flags |
| ADDRESS | Where the LCD's address counter points, with the row and column on the 20x4 (0x54 = ROW 4, COL 1) |
| VCC, CONTRAST | The supply, and the contrast with V0 (GOOD, WEAK, TOO LOW or TOO HIGH) |
| BACKLIGHT | ON (P3 HIGH), OFF (P3 LOW), OFF (NO JUMPER), or forced |
| I2C | This backpack's address and the transactions so far, or the address the sketch uses instead |
| WRITES | Bytes the LCD took (commands and characters) |
| Bus | The last 6 transfers to the LCD, newest first |
| Buttons | BACKLIGHT (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:
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
- 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
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?
| Check | Result in the picture |
|---|---|
| Wait after power-on | 1121.7 ms (the datasheet asks for 40 ms) |
| Function set x3 (wake-up) | 5 function sets seen |
| Wait after the 1st / 2nd | 5.27 ms / 5.27 ms (at least 4.1 ms / 100 us) |
| Interface | 4-BIT (DB4..DB7) |
| Lines | 2 lines (N = 1): rows 1 + 3 and 2 + 4 |
| Display on, clear, entry mode | Yes, yes, increment (left to right) |
| Bytes ignored (busy) | None during the start-up |
| Verdict | The 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?
Good for: "a blank I2C LCD - the wrong address, SDA / SCL swapped, or a clock too fast." The tool shows:
| Row | Result in the picture |
|---|---|
| This backpack | 0x3F - PCF8574AT, pads A2 A1 A0: open open open |
| The sketch writes to | 0x3F - this backpack (51830 writes since power-on) |
| Measuring for | 134.450 s of simulation: 51832 transactions to 0x3F, 51830 bytes |
| Address checks | 2 to this address, 126 transactions to other addresses (a scanner / auto-detect) |
| SCL clock | 100 kHz (the PCF8574 is rated for 100 kHz) |
| The port now | 0x09 = 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?
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:
| Result | Value |
|---|---|
| Time per byte | 1489.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 second | 671 while writing |
| Bus | I2C 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
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.
Wiring
| Backpack pin | Arduino UNO | Why |
|---|---|---|
| GND | GND | Ground |
| VCC | 5V | Supply |
| SDA | A4 (SDA) | I2C data |
| SCL | A5 (SCL) | I2C clock |
| Serial Monitor RXD / TXD / GND | D1 / D0 / ground | Shows 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.
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
| Command | What it does |
|---|---|
| HELP | The command list |
| STATUS | The LCD's address, lines 1 to 4, the backlight, cursor and blink |
| SCAN | Every 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 |
| CLOCK | Row 2 shows the up time with the beating heart again |
| MINUTE | Row 4 shows the bar that fills once a minute again |
| BAR <0-100> | Row 4 shows a bar graph (100 steps) |
| CHARS | Row 4 shows the 8 own characters |
| LIGHT ON / OFF | The backlight (P3 of the backpack) |
| CURSOR ON / OFF, BLINK ON / OFF | The underline cursor, the blinking block cursor (after the row 1 text) |
| CLEAR | All four rows empty |
How to Run the Demo
- 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".
- Watch the bar on row 4 fill up within a minute and start again.
- Click the trimmer halves, the LED jumper and an address pad of LCD1, and watch the panel.
- On U1, type TEXT3 with your own text, BAR 37 and LIGHT OFF; on U2, SCAN and STATUS.
- On LCD1's panel, try BACKLIGHT OFF and CONTRAST AUTO (GOOD).
- 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
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
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!
























Comments
0