Interfacing 16×2 LCD With PIC Microcontrollers | MPLAB XC8

Previous Tutorial Previous Tutorial Tutorial 25 Next Tutorial Next Tutorial
Interfacing 16×2 LCD With PIC Microcontrollers
Intermediate Level ★★☆☆☆

In this tutorial, we’ll discuss how Alphanumeric LCD works and how to interface a 16×2 LCD with a microcontroller. You’ll learn how LCD (Liquid Crystal Display) works internally and how to send data and commands to it with a microcontroller, specifically PIC MCUs. And you’ll also learn how to develop a simple LCD Driver for your upcoming projects.

There are 2 practical LABs associated with this tutorial and here is a brief animation indicating what you’ll be able to do after completing this tutorial.

LCD LAB2 Simulation Animation


  Required Components For This Tutorial  

Qty. Component Name Buy On
1 PIC16F877A

or PIC18F2550



1 BreadBoard Add
1 Alphanumeric LCD 16×2 Add
1 Jumper Wires Pack Add    Add
1 LM7805 Voltage Regulator (5v) Add
1 8MHz Crystal Oscillator Add
1 PICkit2 or 3 Programmer Add

The Prototyping Board Setup

Prototyping Board - Embedded Systems Tutorials With PIC MCUs

   16×2 LCD Module   

We typically add a 16×2 Alphanumeric LCD to small embedded systems & projects to enhance the user experience and UI of the device/project. You can use it to display text messages to the user, number, etc. Other types of LCDs provide different features such as the number of columns and rows (characters) and maybe colored display, and also different interfaces (parallel, spi, i2c, etc).

For this tutorial, we’ll consider the 16×2 LCD with a 16-pin header interface. Assuming it has the standard Hitachi LCD driver HD44780 controller. We’ll see how it works internally and how to interface it with microcontrollers. This small IC on the backside of the LCD module controls the LCD itself and accepts user commands and data sent by the master MCU.

LCD Module Pinout

This is the pinout for a typical LCD 16×2 display unit.

ESP32 LCD 16x2 Display Pinout Arduino Example LiquidCrystal

Pins’ Functions Description

LCD 16x2 Pin Functions

LCD Connection Diagram With MCU


   LCD Controller IC   

The LCD module consists of 16×2 character cells, and each one of them is 5×8 dots. Controlling all of this is a tedious task for our main microcontroller to do. However, it doesn’t have to do. As there is a specific function controller on the LCD itself controlling the display while reading in the user’s commands & data. Here, I’ll be considering the Hitachi HD44780 controller.

LCD Driver Block Diagram

LCD Controller HD44780U Block Diagram

Internal Registers (IR & DR)

The HD44780U has two 8-bit registers, an instruction register (IR) and a data register (DR). The IR stores instruction codes, such as display clear and cursor shift, and address information for display data RAM (DDRAM) and character generator RAM (CGRAM). The IR can only be written from the MPU. The DR temporarily stores data to be written into DDRAM or CGRAM and temporarily stores data to be read from DDRAM or CGRAM. Data written into the DR from the MPU is automatically written into DDRAM or CGRAM by an internal operation.

Display Data RAM (DDRAM)

Display data RAM (DDRAM) stores display data represented in 8-bit character codes. Its extended capacity is 80 × 8 bits, or 80 characters. The area in display data RAM (DDRAM) that is not used for display can be used as general data RAM. Therefore, whatever data you send to the DDRAM, it’ll get displayed on the LCD. As long as the characters count is below 32 (for 16×2 LCD), it’ll be visible. Otherwise, written characters are stored in the DDRAM but not visible.

Character Generator ROM (CGROM)

The character generator ROM generates 5 × 8 dots or 5 × 10 dot character patterns from 8-bit character codes. It can generate 208 5 × 8 dot character patterns and 32 5 × 10 dot character patterns. User-defined character patterns are also available by mask-programmed ROM.

The table down below shows you the standard ASCII equivalent characters for the LCD display stored in the CGROM. And you can also create your custom characters and symbols if you want to, as we’ll see in a future tutorial. Note the “A” character which has a binary code of (0100 0001)b this is equivalent to 65 (the ASCII value for A in the ASCII table).


Character Generator RAM (CGRAM)

In the character generator RAM, the user can rewrite character patterns by the program. For 5 × 8 dots, eight-character patterns can be written, and for 5 × 10 dots, four-character patterns can be written. Write into DDRAM the character codes at the addresses shown as the left column of the above table to show the character patterns stored in CGROM.

Busy Flag (BF)

When the busy flag is 1, the HD44780U is in the internal operation mode, and the next instruction will not be accepted. When RS = 0 and R/W = 1, the busy flag is output to DB7. The next instruction must be written after ensuring that the busy flag is 0.

Address Counter (AC)

The address counter (AC) assigns addresses to both DDRAM and CGRAM. When an address of an instruction is written into the IR, the address information is sent from the IR to the AC. Selection of either DDRAM or CGRAM is also determined concurrently by the instruction. After writing into (reading from) DDRAM or CGRAM, the AC is automatically incremented by 1 (decremented by 1). The AC contents are then output to DB0 to DB6 when RS = 0 and R/W = 1.

LCD Register Selection

LCD Register Selection

Cursor/Blink Control Circuit

The cursor/blink control circuit generates the cursor or character blinking. The cursor or the blinking will appear with the digit located at the display data RAM (DDRAM) address set in the address counter (AC).

LCD 16x2 Cursor And Blinking

LCD Driver Instructions Table

LCD Instructions Codes

   16×2 LCD Interfaces   

There are two ways to interface the LCD diver (controller) IC. You can use the full bus width (8-Bits) for data or alternatively you can use a 4-Bit interface for a reduced pin count needed to control the LCD. Specifically low pin count MCUs need to operate in the 4-Bit mode.

8-Bit Interface

LCD 16x2 Connection With Microcontroller 8-Bit interface

4-Bit Interface

LCD 16x2 Connection With Microcontroller

   How To Setup & Write To LCD   

1. LCD Initialization

At the beginning of your system’s firmware, you should do some initialization steps for the LCD display before it’s usable. These steps are listed by the manufacturer of the LCD Driver IC and let your LCD know how it’s going to operate afterward. Which interface mode you’ll be using (4 or 8 bits), which font and so on. 

8-Bit Interface

Down below are the exact steps to initialize the LCD module for 8-Bit line interface.

LCD Initialization 8-Bit

4-Bit Interface

Down below are the exact steps to initialize the LCD module for 4-Bit line interface.

LCD Initialization 4-Bit

2. Reading The Busy Flag

The LCD takes some time to process commands or data. Therefore, there must be a small delay before issuing a new command to the LCD. This delay could be chosen arbitrarily as long as it’s longer than the time required by the LCD itself as indicated in the datasheet. Alternatively, you can just read the busy flag bit to know whether the previous command was successfully processed or not.

When a data or a command is sent to the LCD, the BF or D7 bit of the LCD becomes 1 and as soon as the command is successfully processed, the BF becomes 0. 

Steps To Read The Busy Flag (BF)

Down below are the exact steps to read the busy flag bit.

  • Select Command Register
  • Select Read Operation
  • Send Enable Signal
  • Read The Flag (D7 pin)

3. Sending Commands To LCD

To send commands, we’ll need to select the command register and transfer the corresponding bits as we’ve done previously in the initialization step. Here are the exact steps to follow

  • Move The Data To The Output Port (LCD Pins)
  • Select The Command Register
  • Select Write Operation
  • Send Enable Signal
  • Wait For LCD To Process The Command

And below is another version of the instructions table as found in the datasheet. We may not implement all of them in this tutorial, so it’s a good idea to download the datasheet and have a look on these instructions (commands) as you may need one or more of them in your projects.

LCD Instructions Table1

LCD Instructions Table2

And here is another table for some commands examples that you can test yourself. We’ll implement a couple of them in the following practical LABs and the rest are left for you to experiment with.

LCD Commands Table

   Implementing a PIC LCD Driver Library   

1  Creating IO Pin Map Definitions

First of all, we should define the IO pins which we’ll be using to interface the LCD. It’s a recommended practice to isolate the hardware drivers firmware from the application layer and for the sake of portability of your code. This step makes your code less dependent on the specific MCU chip you’re using in the current project.

Here are the IO pins we’ll need for LCD interfacing and this is how to define them.

if at any point you found out that a specific pin must be changed, it’ll be an easy task to change it without searching your code and replacing each and every single occurrence for that line of code.

2  Sending Data To The LCD

We’ll be using the 4-Bit interface in these tutorials as it’s the most common and most wished for. Nobody wants to consume all of his microcontroller’s pins just to hook an LCD. In fact, there is an I2C interface for the LCD which we’ll be using in a future tutorial and its main feature is reducing the pin count used for LCD control.

All in all, what we need now is a routine to parse out half-a-byte of data and send these bits to the corresponding pins of the IO pins associated with LCD Data. Here is a simple implementation for such a routine.

3  Sending Commands To The LCD

As we’ve discussed earlier in this tutorial, sending a command to the LCD should start with selecting the command register. Then the command data is transferred to the LCD io data pins. Then we should clock or send enable signal pulse. This routine is followed in general settings, whether it’s a 4-Bit interface of an 8-Bit.

For our case, using a 4-Bit interface, sending an 8-Bit command will be done in 2 exact steps. Hence, we’ll be using the same routine but twice instead of once.

4  Initializing The LCD Module

Now, it’s time to create the LCD initialization routine. This function is an exact implementation of the steps we’ve discussed earlier in this tutorial. The 4-Bit interface initialization steps are indicated in a previous flow chart and our task right now is to implement it in C.

Initialization Procedure In Datasheet

LCD Initialization 4-Bit

5  Writing A Character To The LCD

Sending an 8-Bit character to the LCD followed by an enable pulse (clock) will display that character on the LCD. However, in our case of using a 4-Bit interface, this step will be divided into two consequent steps. First of which is parsing the 8-Bit character into a high_nibble and low_nibble. Then we’ll send the high4 bits first followed by an EN clock, then we’ll send the low4 bits followed by another EN clock. And that’s it!

6  Writing Text To The LCD

To send a string to the LCD, we’ll need a loop to repeatedly send characters to the LCD until a buffer end is found, and typically it’s the NULL character “\0”. Here is the implementation of this routine.

  Adding Auxiliary Functions To PIC LCD Library  

As you’ve seen in the previous sections, the datasheet of the LCD driver IC includes all the command that it could handle. And we’re going to add a couple of them to our LCD Driver code. All the rest are left for you to experiment with. Some specific commands may help you in specific projects and it’s up to you to decide on which one you need to implement.

The commands I’m going to implement in this section are the most used ones in different projects. And will help you get more familiar with sending commands in 4-Bit mode. And there is no difference from what we’ve done so far.

LCD Clear

LCD Clear Command

Set LCD Cursor Position

Set LCD Cursor Position

r -> row number, c -> column number. And the cursor position offset is calculated from the base 0x80

Shift Entire Display Right

Shift LCD Entire Display Right

Shift Entire Display Left

Shift LCD Entire Display Left

   PIC LCD Library Example Code   

As we’ve pointed out earlier, it’s a better practice to separate the hardware drivers code from the main application layer. This increases your code portability, re-usability and reduces the overhead of debugging a single 1k line of code main.c file. Apply this to all the drivers we’ve built so far (UART, SPI, PWM, etc.)

1  Create LCD.h Header File

The header file includes only the declarations of sub-routines with simple documentation indicating the functionality of each routine and what it takes and returns if it’s not void, etc. The LCD.h header file will be something like this

2  Create LCD.c Source File

This file will include all the functions’ implementations (definitions).

3  Include The LCD Driver To Your Project

Now, you can easily include your library (driver) into your main project and it’ll be something like this.

It’s now way more clean/clear. And it’s easier to debug or extend the functionality of your LCD driver module. Here is the compiled project, download it and customize it as you wish.

Download Project

   PIC16F LCD 16×2 Interfacing Example – LAB1   

Lab Name Writing Text To LCD
Lab Number 28
Lab Level Intermediate
Lab Objectives Learn how to interface the LCD 16×2 module with a microcontroller and write text to it starting at a specific position.

       1. Coding       

Open the MPLAB IDE and create a new project name it “LCD_16x2_LAB1”. If you have some issues doing so, you can always refer to the previous tutorial using the link below.


Set the configuration bits to match the generic setting which we’ve stated earlier. And if you also find troubles creating this file, you can always refer to the previous tutorial using the link below.


Now, open the main.c file and let’s start developing the firmware for our project.

Here is the Full Code Listing For This LAB

       2. Simulation       

Here is the simulation’s result.

LCD LAB1 Simulation

       3. Prototyping       

LCD 16x2 LAB1 Text Display

Download Project

   PIC Microcontroller LCD Moving Text – LAB2   

Lab Name Writing A Moving Text On LCD
Lab Number 29
Lab Level Intermediate
Lab Objectives Learn how to interface the LCD 16×2 module with a microcontroller and write text to it starting at a specific position.

       1. Coding       

Open the MPLAB IDE and create a new project name it “LCD_16x2_LAB2”. If you have some issues doing so, you can always refer to the previous tutorial using the link below.


Set the configuration bits to match the generic setting which we’ve stated earlier. And if you also find troubles creating this file, you can always refer to the previous tutorial using the link below.


Now, open the main.c file and let’s start developing the firmware for our project.

Here is the Full Code Listing For This LAB

     2. Simulation       

Here is the simulation’s result.

LCD LAB2 Simulation Animation

       3. Prototyping       

Download Project

   Concluding Remarks   


In this tutorial, we’ve implemented some of the most common LCD commands that you are most likely to use in your various projects. However, there still are some other commands that you can implement and test on your own. Just get the datasheet and start tinkering around and if you feel stuck at any point, just drop me a comment and I’ll be here to help you.

Download LCD Driver Datasheet


As we’ve discussed in a previous section, it’s a recommended practice to separate your device drivers layer from the application layer as much as possible. It helps in terms of portability and enhances code re-usability. Each device driver should have a header file .h and a source file .c and you can #include this library in whichever project you want. Reducing the time to port your project to another platform (microcontroller).


You can use the sprintf function from the standard library by including stdio.h. Now you can combine numbers (int, float, etc) with text in a single array “string” that you can print out on your LCD. We’ve done this in a previous lab for the LM35 sensor.


LCD Custom Character Generator Tool

You can also use the free tool down below to generate your own custom characters and symbols for the LCD display. Things like speaker icon, battery level indicator, and so on.

Custom LCD Character Generator Online Tool

did you find this helpful? why not share it with your network or on reddit? maybe someone else find this helpful too! Good luck and have fun!


Previous Tutorial Previous Tutorial Tutorial 25 Next Tutorial Next Tutorial
Share This Page With Your Network!
Join Our +25,000 Newsletter Subscribers!

Stay Updated With All New Content Releases. You Also Get Occasional FREE Coupon Codes For Courses & Other Stuff!

Photo of author
Khaled Magdy
Embedded systems engineer with several years of experience in embedded software and hardware design. I work as an embedded SW engineer in the Automotive & e-Mobility industry. However, I still do Hardware design and SW development for DSP, Control Systems, Robotics, AI/ML, and other fields I'm passionate about.
I love reading, writing, creating projects, and teaching. A reader by day and a writer by night, it's my lifestyle. I believe that the combination of brilliant minds, bold ideas, and a complete disregard for what is possible, can and will change the world! I will be there when it happens, will you?

4 thoughts on “Interfacing 16×2 LCD With PIC Microcontrollers | MPLAB XC8”

  1. Hi Khaled, i love your tutorials!
    But isn’t the EN delay of 500 us a bit long?
    According to my LCD data sheet it should be 220 ns minimum. I’ve reduced the value to 1 us otherwise it doesn’t work on my end.
    Greetings Marcel.

  2. Thanks Khaled. The LCD tutorial is clear and well documented. I am using pic16f1619 microcontroller curiosity development board. Sadly I am unable to display characters. When I debug the program, everything works perfectly.

  3. I had some problems getting this to work initially, until I realized that you did not have any delay after the function commands for things like clear screen and moving the cursor which take a lot of time.


Leave a Comment