Hello friends, I hope you all are doing great. Today, I am going to share the new LCD 16x2 Library for Proteus V3.0. The 16x2 character LCD, the blue or yellow-green "1602" display with 16 pins, is the first display most of us connect to an Arduino. It shows two lines of 16 characters and is driven by the famous HD44780 controller. With this LCD 16x2 Library for Proteus, your sketch writes to an LCD that behaves like the real one: the real timing, the contrast pot, the backlight, your own characters, and even the row of blocks you see when the LCD is not initialised.

Version 3.0 is the next version of our LCD libraries, and this time it is our own HD44780 model, not a new symbol on top of Proteus's built-in LCD. You get four devices: a blue and a yellow-green LCD, each as LCD 16x2 Simple, with a live STATUS panel on the schematic, and LCD 16x2 Advance, with a pop-up window, a bus log and four LCD test tools. The new LCD 16x2 Arduino Proteus demo runs Arduino's LiquidCrystal library on two Arduino UNOs, with the Simple interface of our TEP Serial Monitor.

NOTICE: This library is very special to our team. Instead of drawing one more symbol over Proteus's LCD, we wrote the HD44780 controller ourselves: every command, the busy times, the character ROM, the custom characters, the contrast and the backlight. It took our team a lot of hard work, many test runs and many design changes. 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 LCD 16x2 Library for Proteus V3.0:

Figure: The LCD 16x2 demo in Proteus 8.5: NODE 1 (blue, Simple) and NODE 2 (yellow-green, Advance), LCD2's Display page with "Hello, World!" over a 75 % bar graph, and U2 answering BAR 75 and STATUS in its Simple interface.

What is a 16x2 LCD?

A 16x2 LCD is a character display: 2 lines with 16 characters each. Every character is a small block of 5x8 dots, so the display can show letters, digits, symbols and a few shapes of your own. The module you buy is a green board of about 80 x 36 mm with the glass in a metal frame, a 16-pin header on the top edge and an LED backlight behind the glass. The two common colours are blue (white characters on a blue backlight) and yellow-green (dark characters on a yellow-green backlight).

On the back sits the HD44780 controller (or a compatible chip). Your Arduino never draws dots: it sends commands (clear, move the cursor, display on) and characters (codes such as 0x48 for "H") over a parallel bus, and the controller turns each code into dots from its built-in character ROM. For a deeper look at the module itself, read our Introduction to 16x2 LCD Module.

The 16x2 LCD at a Glance

The 16x2 LCD in numbers
FeatureValue
Display2 lines x 16 characters, each 5x8 dots
ControllerHD44780 (or compatible), with a character ROM, 80 bytes of display RAM and 8 own characters
Bus8-bit (D0 - D7) or 4-bit (D4 - D7), plus RS, RW and E
Supply5 V on VDD, ground on VSS
ContrastThe voltage on V0, set with a 10k pot (good: about 0.4 to 1 V)
BacklightAn LED on A (+) and K (-); the blue one takes about 20 mA, the yellow-green one about 120 mA
Timing37 us per command, 41 us per character, 1.52 ms for clear and home
Arduino libraryLiquidCrystal (comes with the Arduino IDE)

LCD Terms You Will See in This Article

LCD terms
TermMeaning
DDRAMDisplay data RAM: the character codes; 80 bytes, of which 2 x 16 are on the glass
CGRAMCharacter generator RAM: room for 8 characters you design (codes 0 to 7)
CGROMThe built-in character ROM; ours is the common A00 table
Busy timeHow long the controller needs after a byte; a byte sent earlier is lost
RSRegister select: 0 = command, 1 = character
EEnable: the controller takes the byte when E falls
V0The contrast pin: the bigger VDD - V0, the darker the dots
A / KAnode and cathode of the backlight LED

How the HD44780 Controller Works

Commands and Characters

Every byte travels with RS: RS = 0 makes it a command, RS = 1 a character. A character goes into DDRAM at the address counter, and the address moves on by one. A command can clear the display (all spaces, cursor home), set the address (for example 0x40, the start of line 2), turn the display, the cursor and the blink on or off, shift the whole display, or choose the bus width and the number of lines. In 4-bit mode, which LiquidCrystal uses with 6 wires, each byte is sent as two halves, so E pulses twice per byte.

The Busy Time

The controller is slow. After most commands it is busy for 37 us, after a character for 41 us, after clear or home for 1.52 ms, and after power-on for about 10 ms. A byte that arrives while the controller is busy is simply lost, on a real LCD and in our model. That is why LiquidCrystal waits 100 us after every byte and 2 ms after a clear. If you write your own driver and forget the waits, characters go missing; our panel then shows TOO FAST and our Timing tool shows which byte was lost.

The Start-Up and the Row of Blocks

After power-on, the controller is in 8-bit mode with one line and the display off. In one-line mode, the top row of a 16x2 shows a row of dark blocks. If you see only that row, the sketch never initialised the LCD, mostly because of wrong pins in LiquidCrystal lcd(...). lcd.begin(16, 2) sends the start-up sequence from the datasheet: three wake-up commands, the switch to 4-bit mode, 2 lines, display on, clear and the entry mode. Our model shows the blocks too, and our Init check tool compares your start-up with the datasheet.

Your Own Characters

CGRAM holds 8 characters of your own, codes 0 to 7. Each is 8 rows of 5 dots, one byte per row. lcd.createChar(n, rows) writes them, and lcd.write(byte(n)) prints one. Our demo makes a beating heart (two frames), a smiley, a bell and four bar steps for a bar graph. One trap: createChar() leaves the address counter in CGRAM, so call lcd.setCursor() or lcd.clear() before you print again. A real HD44780 does exactly the same, and so does our model.

Contrast and Backlight

The dots darken with the voltage across the liquid crystal, VDD - V0. A 10k pot between 5 V and ground sets V0. Too little voltage (V0 high) and the characters fade out; too much (V0 near 0 V) and the empty dots turn dark as well, so you see blocks behind the text. The backlight is an LED with a series resistor. You can switch it from a pin, and with analogWrite() on a PWM pin you can dim it.

What's New in LCD 16x2 Library for Proteus V3.0

Here is what version 3.0 brings:

  • Our own HD44780 model (TEPLCD.DLL): the real busy times (bytes sent too fast are lost and reported), CGRAM, the full A00 ROM (ASCII, katakana, Greek letters, the degree sign 0xDF and the full block 0xFF), reading back, the blocks before initialisation, display shift, cursor and blink.
  • Real analog pins: VDD and VSS, the contrast pin V0 (turn a pot and watch the contrast change) and the LED backlight A / K (dim it with PWM). Pins you leave open get friendly defaults.
  • Four devices in TEPLCD16X2.LIB: blue and yellow-green, each Simple and Advance.
  • A real-looking board: the real 1602 size, 16 gold pads, the metal frame and the glass with every 5x8 dot cell, plus PWR, CMD and DATA LEDs (TEP additions).
  • Four LCD test tools on the Advance: Init check, Timing, Speed and Characters.
  • A two-UNO demo with the TEP Serial Monitor and Serial commands (TEXT1, BAR, CHARS, LIGHT, BLINK, SCROLL and more).
  • Fast in Proteus: about 0.65 x real time with both Serial Monitors and the pop-up open.
  • A compact package: about 1.37 MB, without the C++ source code.

Earlier versions of our LCD library use other file names, so you can keep them installed next to V3.0.

LCD 16x2 Simple vs LCD 16x2 Advance

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

The four devices (search "LCD16X2" in Pick Devices; category Optoelectronics > Alphanumeric LCDs)
DeviceName in Pick DevicesGlassExtras
LCD16X2BLUETEPLCD 16x2 V3.0 Blue - SimpleBlue, white charactersSTATUS panel on the sheet
LCD16X2BLUEADVTEPLCD 16x2 V3.0 Blue - AdvanceBlue, white charactersPop-up window and tools
LCD16X2GREENTEPLCD 16x2 V3.0 Yellow-green - SimpleYellow-green, dark charactersSTATUS panel on the sheet
LCD16X2GREENADVTEPLCD 16x2 V3.0 Yellow-green - AdvanceYellow-green, dark charactersPop-up window and tools

Pick a Simple LCD when you want to see the LCD's state right on the schematic. Pick an Advance LCD when the schematic is crowded, or when you want the bus log and the test tools.

Download LCD 16x2 Library for Proteus

Click the button below to download LCD-16x2-Library-for-Proteus-v3.0.zip (about 1.37 MB, without the C++ source code):

LCD 16x2 Library for Proteus V3.0
  • README.txt: a detailed guide to the files, the wiring, the model, the test tools and real hardware.
  • Proteus Library Files: TEPLCD16X2.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: LCD-16x2-ArduinoUnoV3.pdsprj, LCD_16x2_Demo.hex (both UNOs run it) and copies of both DLLs.
  • Arduino Code: LCD_16x2_Demo.ino, the demo sketch. It uses Arduino's LiquidCrystal library, which comes with the Arduino IDE.

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 LCD 16x2 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 TEPLCD16X2.IDX from the LIBRARY folder if there is one; Proteus makes a new index.
  5. Start Proteus, press P and search for LCD16X2. 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 16x2 LCD Board in Proteus

We drew the LCD as a clean TEP board in the real size and colours of a 1602 module: the green PCB with four mounting holes, the 16 gold pads with the pin names, the black metal frame with its tabs and the glass with all 32 character cells. Before Run, the glass is dark with faint cells, like an LCD without power. At Run, the glass glows with the backlight (brighter at both ends, where its LEDs sit), and every character is drawn dot by dot with the contrast. Here is LCD1, the blue Simple LCD, in the demo:

Figure: LCD1 (blue, Simple) after TEXT1 Hello, TEP!: "Up 00:01:52" with the heart, READY - 4-BIT BUS, 2 LINES, 5x8 DOTS, DISPLAY ON, VDD 5.00 V, CONTRAST GOOD V0 0.76 V, BACKLIGHT ON 16.8 mA, TOO FAST 0.

Pinout

The 16-pin header, left to right (pin 1 has the square pad)
PinNameWhat it doesDemo connection
1VSSGroundGND
2VDDSupply, 5 V+5V
3V0Contrast voltageThe wiper of the 10k pot
4RSRegister select: command (0) or character (1)Arduino D2
5RWRead (1) or write (0)GND (the sketch only writes)
6EEnable: the byte is taken when E fallsArduino D3
7 - 10D0 - D3Data bus, low halfNot connected (4-bit bus)
11 - 14D4 - D7Data bus, high halfArduino D4 - D7
15ABacklight LED anode (+)D10 (LCD1, PWM) or +5V (LCD2)
16KBacklight LED cathode (-)GND

LEDs and Indicators

Board indicators (the same on all four devices)
IndicatorWhat it showsOn the real board?
The backlightThe glass glows with the current through A / K (dim with PWM)Yes
PWR LED (red)Lit while the LCD has power (VDD above 4 V)TEP addition
CMD LED (orange)Flashes when a command arrivesTEP addition
DATA LED (green)Flashes when a character arrivesTEP addition
SIMPLE / ADVANCE badgeWhich device this isTEP addition
PANEL / OPEN PANELThe panel buttonTEP addition

A real 1602 board has only its backlight; the three LEDs, the badge and the button are TEP additions that help you see what the bus is doing.

Component Properties

LCD properties (double-click the LCD > Edit Properties)
PropertyMeaningDefault
PANELSimple devices only: the STATUS panel at the start, OPEN or CLOSEDOPEN
BACKLIGHTPINS (from A / K), ON or OFF (forced)PINS
CONTRASTPIN (from V0) or AUTO (always good)PIN

If you leave pins open, the model is friendly: an open VDD counts as 5 V, an open V0 as good contrast, and an open A as backlight on. So you can start with just the bus wires and add the analog parts later.

LCD 16x2 Simple: The STATUS Panel

Under the Simple LCD sits the LCD 16x2 STATUS panel (see the image above):

The LCD 16x2 STATUS panel
PartWhat it shows
BannerThe state in words: READY (with the bus, lines and font), NOT INITIALISED (the row of blocks), TOO FAST, NO POWER, DISPLAY OFF or POWER-ON RESET
DISPLAY, CURSOR, BLINKThe display, cursor and blink flags
ENTRY, ADDRESSThe entry mode and where the address counter points (row and column, or OFF SCREEN)
VDD, CONTRASTThe supply voltage, and the contrast with V0 (GOOD, WEAK, TOO LOW or TOO HIGH)
BACKLIGHTThe backlight current (ON, DIM or OFF)
WRITES, TOO FASTBytes taken, and bytes lost because they came during the busy time
BusThe last 6 transfers, newest first: time, CMD or DATA and what each byte means
ButtonsBACKLIGHT (A / K PINS, ON, OFF) and CONTRAST (V0 PIN, AUTO (GOOD)) override the pins for this run

In the image, ADDRESS says 0x50 (OFF SCREEN): the clock's heart was just written to the last cell of line 2 (0x4F), and the address moved on to 0x50, which is not on the glass. That is normal: the next setCursor() moves it back.

Wiring with Net Labels

The LCD's header points up, so in the demo the power pins reach GND and +5V through short wires with net labels. In Proteus, terminals with the same name are connected, so the V0 pin labelled V0_1 is connected to the wiper of RV1, which carries the same label. The six bus wires come from the UNO without a single crossing.

Figure: The header of LCD1: GND, +5V and V0_1 above VSS, VDD and V0, GND above RW and K, the bus wires on RS, E, D4 - D7 and A; RV1 (10k, at 15 %) between +5V and ground, its wiper labelled V0_1.

Turning the Contrast Pot

Click the arrows on RV1 (click and hold to move fast) and watch the glass. At 15 % the wiper gives V0 = 0.76 V and the contrast is GOOD. Turn it up to 44 %: V0 rises to 2.18 V, the voltage across the crystal drops, and the characters turn faint. The panel says WEAK:

Figure: RV1 at 44 %: CONTRAST WEAK, V0 2.18 V; the characters are faint. Above about 60 % they vanish (TOO LOW).

Now turn RV1 all the way down to 0 %. V0 is 0 V, the full 5 V is across the crystal, and even the empty dots turn dark. You see blocks behind the text, and the panel says TOO HIGH:

Figure: RV1 at 0 %: CONTRAST TOO HIGH, V0 0.00 V; dark blocks behind the text. The CMD and DATA LEDs flash with the clock update.

This is exactly what happens on a real LCD. A blank LCD at power-on is very often just the contrast: turn the pot until the blocks of the top row just show, then a little back.

Dimming the Backlight with PWM

LCD1's backlight anode A is wired to D10, a PWM pin. The blue backlight takes about 20 mA, which an Arduino pin can drive. The demo's LIGHT command calls analogWrite(); our model averages the PWM current over 30 ms, as your eye does. LIGHT 60 gives 60 / 255 of the full 16.8 mA:

Figure: After LIGHT 60: BACKLIGHT DIM 3.9 mA, the glass darker; CONTRAST GOOD V0 0.90 V (RV1 back at 18 %).

The BACKLIGHT buttons on the panel override the pins for the run: OFF switches the backlight off whatever A does, and ON lights it fully. CONTRAST AUTO (GOOD) ignores V0, which is handy while you have no pot wired yet.

Closing the Panel

Click the red X in the panel's header: the board button says PANEL: CLOSED, and the LCD keeps running. PANEL on the board brings the panel back.

Figure: The panel closed: PANEL: CLOSED; the LCD keeps showing "Hello, TEP!" and "Up 00:03:09".

LCD 16x2 Advance: The Pop-Up Window

The Advance LCD keeps only the board on the schematic. Here is LCD2, the yellow-green Advance LCD, after the demo's CHARS command:

Figure: LCD2 (yellow-green, Advance) after CHARS: line 2 shows the 8 CGRAM characters - the big and small heart, the smiley, the bell and the four bar steps.

At Run, the "TEP LCD 16x2 Advance" pop-up opens with the look of our TEP Serial Monitor and no Windows title bar: 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 palette (theme), gear (Settings) and ? (Help) icons. Under the header, BACKLIGHT and CONTRAST buttons work like the Simple panel's.

The Display Page

Figure: The Display page after CHARS and BLINK ON: BLINK ON, ADDRESS 0x0D, the DDRAM grid, the 8 CGRAM characters, VDD 5.00 V, contrast good V0 0.76 V, backlight 123.0 mA.
  • The glass: a big copy of the LCD, dot by dot, with the backlight and the contrast.
  • Tiles: BUS 4-BIT, LINES 2, FONT 5x8, DISPLAY ON, CURSOR OFF, BLINK ON, ENTRY INCREMENT and ADDRESS 0x0D.
  • DDRAM: both lines of 40 bytes; the green frame is what the glass shows, and the blue cell is the address counter. Line 2 holds the codes 0 to 7 of the custom characters.
  • CGRAM: the 8 characters as the sketch defined them.
  • Supply, contrast and backlight: VDD 5.00 V, Contrast good V0 0.76 V (100 %), Backlight 123.0 mA (123 %): LCD2's yellow-green backlight is on +5V.
  • The latest transfers: the newest bytes with their meaning.

The ADDRESS tile says 0x0D: the demo parks the blinking cursor right after "Hello, World!" (13 characters, so column 13). In the transfers you can see the command 0x0D, DISPLAY ON, CURSOR OFF, BLINK ON, followed by SET DDRAM ADDRESS 0x0D.

The Bus Log Page

Figure: The Bus log: the CHARS command writing the custom characters 0 to 7 with spaces, then BLINK ON; about 306 to 313 us between bytes, 265 to 275 us of margin.

The Bus log keeps the last 512 bytes, newest first. Each row has the time, the gap to the byte before, CMD or DATA, the byte, what it means (for example "SET DDRAM ADDRESS 0x40", or which character went to which address) and the margin: how long after the end of the busy time the byte came. A negative margin would mean a lost byte. The filters show only commands, only characters or only problems, and Pause freezes the list.

Read the picture from the bottom up: the CHARS command sets the address 0x40 (line 2), writes custom char 0, a space, custom char 1, a space and so on. With LiquidCrystal on a 16 MHz UNO, the gap between bytes is about 307 us, and the margin is about 265 us: far more than the controller needs.

The Tests Page: Four LCD Test Tools

The four tools read the bytes as the model takes them from the bus, the same bytes a real LCD would get. 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 lcd.begin() Right?

Figure: Init check: every row green, verdict "the LCD is initialised like the datasheet's figure (LiquidCrystal does this)".

Good for: "an LCD that stays blank or shows a row of blocks - is the sketch's lcd.begin() right?" The tool checks the first transfers after power-on against the datasheet's start-up figure:

Init check (LCD2)
CheckResult in the picture
Wait after power-on66.2 ms (the datasheet asks for 40 ms)
Function set x3 (wake-up)9 function sets seen
Wait after the 1st / 2nd4.65 ms / 4.65 ms (at least 4.1 ms / 100 us)
Interface4-BIT (DB4..DB7)
Lines2 lines (N = 1)
Display on, clear, entry modeYes, yes (DDRAM filled with spaces), increment (left to right)
Bytes ignored (busy)None during the start-up
VerdictThe LCD is initialised like the datasheet's figure

Why 9 function sets? Look at the start-up list in the picture: LiquidCrystal's constructor already calls begin(16, 1) for one line, and then your lcd.begin(16, 2) runs the whole sequence again, now with 2 lines. The second wake-up starts while the controller is in 4-bit mode, so its first two halves join into one byte, 0x33; the HD44780's start-up is built to recover from exactly that. Our model follows the real chip here, and the result is a correctly initialised LCD.

2. Timing: Is the Sketch Too Fast for the Controller?

Figure: Timing over 8.9 s: 360 bytes, none ignored, smallest margin 265.7 us, shortest E pulse 6.8 us over 720 pulses.

Good for: "lost characters or commands - every byte must wait for the previous one's busy time." Click Start measuring and let the sketch write. The tool counts the bytes, the bytes ignored while busy (none here), the smallest margin after the 37 / 41 us busy times (265.7 us), the margin after a clear or home (1.52 ms; LiquidCrystal waits 2 ms), and the shortest E pulse, which must be at least 450 ns: 6.8 us over 720 pulses. "After clear / home" stays amber here, because the demo did not clear the LCD while the tool was measuring.

3. Speed: How Fast Can the Sketch Update the LCD?

Figure: Speed over 8.75 s: 17 bursts, 307.4 us per byte while writing, a full screen in 10.45 ms (at most 96 screens 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; the pauses of the sketch between its writes (the clock updates twice a second) are not the bus speed and are not counted. With LiquidCrystal in 4-bit mode on a 16 MHz UNO:

Speed (LCD2, the demo sketch with LiquidCrystal)
ResultValue
Time per byte307.4 us (the controller needs only 37 to 41 us)
A full line (16 characters + the address)5.23 ms
A full screen (32 characters + 2 addresses)10.45 ms, so at most 96 screens per second
Characters per second3253 while writing

The chart shows the gap before each of the last 60 bytes on a log scale: green bars are characters, blue bars commands, and red bars would be ignored bytes. The yellow line is the controller's busy time; every bar is far above it. The three tall blue bars are the pauses between the clock updates. So a full rewrite of the screen costs about 10 ms, which is why good LCD sketches update only what changed.

4. Characters: Find Any Symbol's Code

Figure: Characters: the A00 ROM; the degree sign 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 in the datasheet's layout: the upper half of the code across, the lower half down. Codes 0 to 15 are the 8 CGRAM characters (twice), so you see the demo's hearts, smiley, bell and bars in the first column. The characters on the glass now are highlighted. Click one, and it is drawn big with its code and the line to print it, for example lcd.write(223); for the degree sign, ideal for a temperature display. For a CGRAM character, it even shows the 8 rows as a byte array for createChar().

Theme, Settings and Help

The palette icon switches the theme between TEP Dark and Light. The gear opens Settings: the theme, the text size (Small, Normal or Large), open the panel at Run (On or Off) and a button that resets the window size. Everything is saved for your Windows user.

Figure: Settings in TEP Dark (Normal text, open at Run On), saved under HKCU\Software\TheEngineeringProjects\TEP LCD 16x2 Advance.

The ? icon opens Help & Support, the same as in the TEP Serial Monitor:

Figure: Help & Support: every card shows its link; TEP LCD 16x2 Advance v3.0, build 2026-10-10, TEPLCD.DLL.

Report a bug and Suggest a feature lead to the LCD board of our forum; Read the article, User guide and Check for updates lead to this article. Copy diagnostics copies the LCD's state for a bug report, and a links.ini file next to the DLL can point the cards somewhere else.

LCD 16x2 with Arduino in Proteus

Open LCD-16x2-ArduinoUnoV3.pdsprj from the Proteus Simulation folder, with LCD_16x2_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), the contrast pot RV1 and Serial Monitor U1; the backlight on D10 (PWM).
  • NODE 2 - ADVANCE (pop-up panel): UNO 2 (ARD2) with LCD2 (yellow-green, Advance), RV2 and Serial Monitor U2; the backlight on +5V, because the yellow-green backlight takes about 120 mA, too much for a pin.
Figure: The whole circuit running, without the pop-up windows: LCD1 shows "Hello, TEP!" and the clock, LCD2 "Hello, World!" over a 75 % bar graph.

Wiring

Demo wiring (the same on both UNOs, except A)
LCD pinArduino / supplyWhy
RS, ED2, D3Command or character, and the strobe
D4, D5, D6, D7D4, D5, D6, D7The 4-bit data bus
RW, VSS, KGNDWrite only; ground; the backlight's cathode
VDD+5VSupply
V0The wiper of a 10k pot between +5V and GNDContrast
AD10 (PWM) on LCD1, +5V on LCD2Backlight
Serial Monitor RXD / TXD / GNDArduino D1 / D0 / groundShows what the Arduino prints, sends what you type

D0 to D3 of the LCD stay open in 4-bit mode. The pin numbers are not fixed: any six digital pins work, as long as the LiquidCrystal lcd(...) line names them in the order RS, E, D4, D5, D6, D7 (see our Introduction to Arduino UNO for the pins).

The Arduino Code

The demo sketch, LCD_16x2_Demo V3.0, uses Arduino's LiquidCrystal library, which comes with the Arduino IDE. It starts with the LCD object and the backlight pin:

#include <LiquidCrystal.h>

LiquidCrystal lcd(2, 3, 4, 5, 6, 7);   // RS, E, D4, D5, D6, D7
const byte BACKLIGHT_PIN = 10;         // analogWrite(0..255) dims the backlight

Next come four of the 8 custom characters. Each line is one character, 8 rows of 5 dots; 0x0A is the row ".X.X." and 0x1F a full row:

byte heartBig[8]   = {0x00, 0x0A, 0x1F, 0x1F, 0x1F, 0x0E, 0x04, 0x00};
byte heartSmall[8] = {0x00, 0x00, 0x0A, 0x0E, 0x0E, 0x04, 0x00, 0x00};
byte smiley[8]     = {0x00, 0x0A, 0x0A, 0x00, 0x11, 0x0E, 0x00, 0x00};
byte bell[8]       = {0x04, 0x0E, 0x0E, 0x0E, 0x1F, 0x00, 0x04, 0x00};

setup() starts Serial and the backlight, initialises the LCD, loads the 8 characters and draws the start screen. Note the setCursor(0, 0) after the last createChar():

void setup() {
  Serial.begin(9600);
  pinMode(BACKLIGHT_PIN, OUTPUT);
  analogWrite(BACKLIGHT_PIN, backlight);
  lcd.begin(16, 2);
  lcd.createChar(HEART_BIG, heartBig);
  lcd.createChar(HEART_SMALL, heartSmall);
  lcd.createChar(SMILEY, smiley);
  lcd.createChar(BELL, bell);
  lcd.createChar(4, bar1);
  lcd.createChar(5, bar2);
  lcd.createChar(6, bar3);
  lcd.createChar(7, bar4);
  lcd.setCursor(0, 0);         // createChar leaves the address in CGRAM
  redraw();
  Serial.println(F("LCD 16x2 V3.0 demo - The Engineering Projects"));
  Serial.println(F("Type HELP for the commands"));
}

To print a line, the sketch writes the text and pads it with spaces to 16 characters, so a shorter text never leaves old characters behind:

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

The clock on line 2 is the up time from millis(), and the heart in the last cell switches between the big and the small frame:

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

The bar graph uses 80 steps: 16 cells of 5 columns. A full cell is the ROM's full block 0xFF, and a part cell is one of the four bar characters (codes 4 to 7):

void showBar() {
  int steps = (long)barPercent * 80 / 100;   // 16 cells x 5 columns
  lcd.setCursor(0, 1);
  for (byte c = 0; c < 16; 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(' ');
  }
}

BAR 75 gives 60 steps: 12 full cells and nothing more, as in the featured image. CHARS clears line 2 and writes the codes 0 to 7, each followed by a space, so you see all 8 own characters at once:

void showChars() {
  printLine(1, "");
  lcd.setCursor(0, 1);
  for (byte k = 0; k < 8; k++) { lcd.write(k); lcd.print(' '); }
}

Every print moves the address counter on, so a visible cursor would always end up behind the last write. The sketch parks it after the text of line 1 instead:

void parkCursor() {                   // a visible cursor waits after the line 1 text
  if (!cursorOn && !blinkOn) return;  // (every print moves the address on)
  byte n = strlen(text1);
  lcd.setCursor(n < 16 ? n : 15, 0);
}

The LIGHT command sets the backlight with analogWrite():

  } else if (!strcasecmp(line, "LIGHT")) {
    backlight = constrain(atoi(arg), 0, 255); analogWrite(BACKLIGHT_PIN, backlight);
    Serial.print(F("OK - backlight ")); Serial.println(backlight);

The loop reads the Serial Monitor, beats the heart twice a second and runs SCROLL one step every 200 ms, all without delay():

void loop() {
  while (Serial.available()) {
    char c = Serial.read();
    if (c == '\r' || c == '\n') {
      if (cmdLen) { cmd[cmdLen] = 0; handle(cmd); cmdLen = 0; }
    } else if (cmdLen < sizeof cmd - 1) cmd[cmdLen++] = c;
  }
  unsigned long now = millis();
  if (now - lastTick >= 500) {       // the heart beats twice a second
    lastTick = now;
    beat = !beat;
    if (line2 == SHOW_CLOCK && scrollSteps == 0) { showClock(); parkCursor(); }
  }
  if (scrollSteps > 0 && now - lastScroll >= 200) {
    lastScroll = now;
    if (scrollSteps > 16) lcd.scrollDisplayLeft(); else lcd.scrollDisplayRight();
    scrollSteps--;
  }
}

SCROLL uses the controller's display shift: the text in DDRAM stays where it is, and the window slides over it. For more scrolling ideas, see our Scrolling Text on LCD with Arduino. To change the sketch, 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 60, LIGHT 255; U2 STATUS, BAR 75, SCROLL, BLINK ON)
CommandWhat it does
HELPThe command list
STATUSLine 1, line 2, the backlight, cursor and blink
TEXT1 <text>Line 1 (16 characters; a longer text is cut)
TEXT2 <text>Line 2 (the clock stops)
CLOCKLine 2 shows the up time with the beating heart again
BAR <0-100>Line 2 shows a bar graph (80 steps)
CHARSLine 2 shows the 8 own characters
LIGHT <0-255>The backlight PWM on D10
CURSOR ON / OFF, BLINK ON / OFFThe underline cursor, the blinking block cursor
SCROLLThe display slides 16 steps left and back
CLEARBoth lines empty

How to Run the Demo

  1. Press Run. Both Serial Monitors (9600 baud) and LCD2's pop-up open. Both LCDs show "Hello, World!" and the clock with the beating heart.
  2. Turn RV1 and RV2 and watch the contrast; LCD1's panel reads WEAK, GOOD or TOO HIGH.
  3. On U1, type LIGHT 60 and TEXT1 Hello, TEP!; on U2, BAR 75, CHARS, SCROLL and BLINK ON.
  4. On LCD1's panel, try BACKLIGHT OFF and CONTRAST AUTO (GOOD); close the panel with its red X and open it with PANEL.
  5. On LCD2's pop-up, look at the Bus log and run the four tools on the Tests tab.

LCD 16x2 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 (baud rate, follow, pause, search and clear), so the window stays small.

  • 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 (the expand arrows at the top right), 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; the quick buttons come back with the full view. Every line the demo prints is 80 characters or less, so it fits this small window. Read all about the monitor in TEP Serial Monitor Advance for Proteus.

U1: TEXT1, LIGHT and HELP

Figure: U1 in the Simple interface: the start lines, "OK - line 1: Hello, TEP!", "OK - backlight 60" and the HELP list.
LCD 16x2 V3.0 demo - The Engineering Projects
Type HELP for the commands
TEXT1 Hello, TEP!
OK - line 1: Hello, TEP!
LIGHT 60
OK - backlight 60
HELP
Commands:
  TEXT1 <text>   line 1 (16 characters)
  TEXT2 <text>   line 2
  CLOCK          line 2 = the up time
  BAR <0-100>    line 2 = a bar graph
  CHARS          line 2 = the 8 CGRAM characters
  LIGHT <0-255>  backlight PWM on D10
  CURSOR ON|OFF  BLINK ON|OFF
  SCROLL  CLEAR  STATUS  HELP

The lines you typed (TX) and the sketch's answers (RX) follow each other. Every answer starts with OK and says what changed, so you can follow the LCD from the monitor alone.

U2: BAR, STATUS, CHARS, BLINK and CLOCK

Figure: U2 in the Simple interface: BAR 75, STATUS, CHARS, BLINK ON and CLOCK.
BAR 75
OK - bar 75%
STATUS
Line 1: Hello, World!
Line 2: bar 75%
Backlight 255, cursor off, blink off
CHARS
OK - the 8 CGRAM characters
BLINK ON
OK - blink on
CLOCK
OK - line 2 shows the clock

STATUS reports the backlight as 255 on LCD2 too. The sketch does not know that LCD2's anode sits on +5V instead of D10: it reports the PWM value it set. The pop-up's Display page shows the real current, 123.0 mA.

Troubleshooting

  • The glass stays dark and the PWR LED is off: no power. Check VDD and VSS (the panel says NO POWER with the voltage); the LCD starts above 4 V.
  • A row of blocks on the top line: the sketch did not initialise the LCD. Check the pins in LiquidCrystal lcd(...) and the wires to RS, E and D4 - D7 (the panel says NOT INITIALISED; on the Advance, run the Init check).
  • Characters missing or wrong: bytes sent while the controller was busy (TOO FAST on the panel, red rows in the bus log, the Timing tool). LiquidCrystal does it right; check your own driver's waits.
  • Faint characters or blocks behind them: the contrast. Turn the pot, or set CONTRAST AUTO (GOOD).
  • Text appears in a strange place after createChar(): call lcd.setCursor() or lcd.clear() after the last createChar().
  • The part is not simulated, no panel 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.
  • The red X does not close the Simple panel: the LIBRARY folder holds an older TEPLCD16X2.LIB; replace it, delete TEPLCD16X2.IDX and re-pick the part.

Things to Know Before Using a Real 16x2 LCD

The demo sketch uses only LiquidCrystal, analogWrite() and Serial, so it runs on a real UNO and LCD too. Keep in mind:

  • The contrast pot is the most common problem: a blank glass at power-on usually needs a turn of the pot until the blocks of the top row just show, then a little back.
  • The backlight resistor: most blue boards have one on the board, so A can go straight to 5 V; some yellow-green boards have none and take far more current. Check your board's datasheet.
  • Do not drive a big backlight from a pin: the blue one (about 20 mA) is fine on D10, the yellow-green one (about 120 mA) needs a transistor.
  • RW to ground: LiquidCrystal only writes; tie RW to GND and save a pin.
  • One row of blocks and nothing else means the LCD was never initialised: wrong pins, or E and RS swapped.
  • The degree sign is 0xDF (223) in the common A00 ROM: lcd.write(223);.

Need more room for text? See our Introduction to 20x4 LCD Module, the bigger brother of the 16x2.

Limitations of the Simulation

  • Not modelled: the 5x10 font on the glass (the setting is taken and shown, the characters are drawn 5x8), temperature, the slow response of the liquid crystal (the glass changes at once), the A02 (European) ROM, the LCD's own current draw on VDD and the viewing angle.
  • The model is our own implementation, written from the HD44780 datasheet; the character ROM follows the HD44780's A00 table. HD44780 is a Hitachi part; this library is not made or endorsed by Hitachi or by Labcenter.
  • Licences: the demo sketch uses Arduino's LiquidCrystal library 1.0.7 (LGPL 2.1). The HEX file contains LiquidCrystal and the Arduino AVR core 1.8.6, which include LGPL-licensed code; their source code is on GitHub.
  • Tested in Proteus 8.5 in October 2026 (the demo as shipped, both LCDs, the panel, the pop-up, the four tools and every screenshot here; about 0.65 x real time with the pop-up and both monitors open, 0.58 x while PWM dims LCD1) and with automatic PC checks, 0 failed: 7,201 on the HD44780 controller, 38 with the real demo sketch and LiquidCrystal 1.0.7 against the model, and the demo project read back from disk. Proteus 7 is not supported.

So, that was all about the LCD 16x2 Library for Proteus V3.0. I hope the real HD44780 timing, the contrast pot, the backlight, your own characters and the four test tools make the 16x2 LCD much easier to understand, so your LCD sketch works the first time you wire a real display. If you use the LCD 16x2 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!