
LCD 16x2 Library for Proteus V3.0 (HD44780 + Arduino)

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:
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
| Feature | Value |
|---|---|
| Display | 2 lines x 16 characters, each 5x8 dots |
| Controller | HD44780 (or compatible), with a character ROM, 80 bytes of display RAM and 8 own characters |
| Bus | 8-bit (D0 - D7) or 4-bit (D4 - D7), plus RS, RW and E |
| Supply | 5 V on VDD, ground on VSS |
| Contrast | The voltage on V0, set with a 10k pot (good: about 0.4 to 1 V) |
| Backlight | An LED on A (+) and K (-); the blue one takes about 20 mA, the yellow-green one about 120 mA |
| Timing | 37 us per command, 41 us per character, 1.52 ms for clear and home |
| Arduino library | LiquidCrystal (comes with the Arduino IDE) |
LCD Terms You Will See in This Article
| Term | Meaning |
|---|---|
| DDRAM | Display data RAM: the character codes; 80 bytes, of which 2 x 16 are on the glass |
| CGRAM | Character generator RAM: room for 8 characters you design (codes 0 to 7) |
| CGROM | The built-in character ROM; ours is the common A00 table |
| Busy time | How long the controller needs after a byte; a byte sent earlier is lost |
| RS | Register select: 0 = command, 1 = character |
| E | Enable: the controller takes the byte when E falls |
| V0 | The contrast pin: the bigger VDD - V0, the darker the dots |
| A / K | Anode 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:
| Device | Name in Pick Devices | Glass | Extras |
|---|---|---|---|
| LCD16X2BLUETEP | LCD 16x2 V3.0 Blue - Simple | Blue, white characters | STATUS panel on the sheet |
| LCD16X2BLUEADVTEP | LCD 16x2 V3.0 Blue - Advance | Blue, white characters | Pop-up window and tools |
| LCD16X2GREENTEP | LCD 16x2 V3.0 Yellow-green - Simple | Yellow-green, dark characters | STATUS panel on the sheet |
| LCD16X2GREENADVTEP | LCD 16x2 V3.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 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.LIBand our Arduino UNO libraryArduinoV3TEP.LIB/ArduinoV3TEP.IDX. - Proteus Model Files:
TEPLCD.DLLandTEPSERIALMON.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
- 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
TEPLCD16X2.IDXfrom the LIBRARY folder if there is one; Proteus makes a new index. - 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:
Pinout
| Pin | Name | What it does | Demo connection |
|---|---|---|---|
| 1 | VSS | Ground | GND |
| 2 | VDD | Supply, 5 V | +5V |
| 3 | V0 | Contrast voltage | The wiper of the 10k pot |
| 4 | RS | Register select: command (0) or character (1) | Arduino D2 |
| 5 | RW | Read (1) or write (0) | GND (the sketch only writes) |
| 6 | E | Enable: the byte is taken when E falls | Arduino D3 |
| 7 - 10 | D0 - D3 | Data bus, low half | Not connected (4-bit bus) |
| 11 - 14 | D4 - D7 | Data bus, high half | Arduino D4 - D7 |
| 15 | A | Backlight LED anode (+) | D10 (LCD1, PWM) or +5V (LCD2) |
| 16 | K | Backlight LED cathode (-) | GND |
LEDs and Indicators
| Indicator | What it shows | On the real board? |
|---|---|---|
| The backlight | The 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 arrives | TEP addition |
| DATA LED (green) | Flashes when a character arrives | TEP addition |
| SIMPLE / ADVANCE badge | Which device this is | TEP addition |
| PANEL / OPEN PANEL | The panel button | TEP 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
| Property | Meaning | Default |
|---|---|---|
| PANEL | Simple devices only: the STATUS panel at the start, OPEN or CLOSED | OPEN |
| BACKLIGHT | PINS (from A / K), ON or OFF (forced) | PINS |
| CONTRAST | PIN (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):
| Part | What it shows |
|---|---|
| Banner | The 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, BLINK | The display, cursor and blink flags |
| ENTRY, ADDRESS | The entry mode and where the address counter points (row and column, or OFF SCREEN) |
| VDD, CONTRAST | The supply voltage, and the contrast with V0 (GOOD, WEAK, TOO LOW or TOO HIGH) |
| BACKLIGHT | The backlight current (ON, DIM or OFF) |
| WRITES, TOO FAST | Bytes taken, and bytes lost because they came during the busy time |
| Bus | The last 6 transfers, newest first: time, CMD or DATA and what each byte means |
| Buttons | BACKLIGHT (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.
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:
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:
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:
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.
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:
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
- 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
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?
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:
| Check | Result in the picture |
|---|---|
| Wait after power-on | 66.2 ms (the datasheet asks for 40 ms) |
| Function set x3 (wake-up) | 9 function sets seen |
| Wait after the 1st / 2nd | 4.65 ms / 4.65 ms (at least 4.1 ms / 100 us) |
| Interface | 4-BIT (DB4..DB7) |
| Lines | 2 lines (N = 1) |
| Display on, clear, entry mode | Yes, yes (DDRAM filled with spaces), increment (left to right) |
| Bytes ignored (busy) | None during the start-up |
| Verdict | The 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?
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?
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:
| Result | Value |
|---|---|
| Time per byte | 307.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 second | 3253 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
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.
The ? icon opens Help & Support, the same as in the TEP Serial Monitor:
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.
Wiring
| LCD pin | Arduino / supply | Why |
|---|---|---|
| RS, E | D2, D3 | Command or character, and the strobe |
| D4, D5, D6, D7 | D4, D5, D6, D7 | The 4-bit data bus |
| RW, VSS, K | GND | Write only; ground; the backlight's cathode |
| VDD | +5V | Supply |
| V0 | The wiper of a 10k pot between +5V and GND | Contrast |
| A | D10 (PWM) on LCD1, +5V on LCD2 | Backlight |
| Serial Monitor RXD / TXD / GND | Arduino D1 / D0 / ground | Shows 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
| Command | What it does |
|---|---|
| HELP | The command list |
| STATUS | Line 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) |
| CLOCK | Line 2 shows the up time with the beating heart again |
| BAR <0-100> | Line 2 shows a bar graph (80 steps) |
| CHARS | Line 2 shows the 8 own characters |
| LIGHT <0-255> | The backlight PWM on D10 |
| CURSOR ON / OFF, BLINK ON / OFF | The underline cursor, the blinking block cursor |
| SCROLL | The display slides 16 steps left and back |
| CLEAR | Both lines empty |
How to Run the Demo
- 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.
- Turn RV1 and RV2 and watch the contrast; LCD1's panel reads WEAK, GOOD or TOO HIGH.
- On U1, type LIGHT 60 and TEXT1 Hello, TEP!; on U2, BAR 75, CHARS, SCROLL and BLINK ON.
- On LCD1's panel, try BACKLIGHT OFF and CONTRAST AUTO (GOOD); close the panel with its red X and open it with PANEL.
- 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
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
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()orlcd.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!
























Comments
0