Interfacing an LCD Display with PIC16F877A Microcontroller

Reading Time: 9 minutes

Today we are going to discuss interfacing an LCD display with PIC16F877A microcontroller. For any microcontroller project, there are inputs to the microcontroller and outputs from it to the user. One of the most important ways to give outputs to the user is through visual feedback, an LCD. Interfacing an LCD to your microcontroller is another puzzle-solving problem. Also, it’s relatively very cheap.

What are we going to achieve here?

At the end of this tutorial, you will be able to get a 16×2 LCD display working with your microcontroller project.

Disclaimer

This is intended for educational purposes only. I am not responsible for any damages incurred to your equipment, circuit components, or other properties. This project is using a simple 12V adapter as the power source. Working with higher voltages for this is not recommended. If you find any error in the following (which I have proof-read), please don’t hesitate to leave a comment below or to contact me over LinkedIn or Facebook. Enjoy!

Requirements

  • A Computer
    • MPLAX X IDE Software
    • XC8 Compiler
  • A Microcontroller
  • 16×2 LCD Display
  • Circuit Components
    • Breadboard
    • LM7805 Voltage regulator + Datasheet
    • 20MHz Crystal Oscillator
    • 2 x 22pF capacitors (C2 and C4 in the circuit diagram)
    • 0.1uF Capacitor (C3 in the circuit diagram)
    • 220nF capacitor (C1 in the circuit diagram)
    • 47k (47 kiloOhm) Resistor (R2 in the circuit diagram)
    • 10k variable resistor (R1 in the circuit diagram)
    • 330R resistor (R3 in the circuit diagram)
    • Enough Jumper wires
  • PICkit 3 Programmer
  • 12V wall adapter (also can use a 9V battery)
  • A Cup of tea to celebrate your achievement!

Getting the things needed

Circuit components
Required softwares (FREE downloads)

Let’s Build our Circuit!

You have done puzzles when you were young right? Well, get yourself ready for another puzzle. Combining all the electronic components of any electronics project is no more complex than the puzzles that we solved as kids. That’s how I’d like to think of it when it feels overwhelming.

Just follow the circuit diagram given below, and try to fit the stuff into the breadboard. If you find it difficult, please stay tuned for the upcoming video tutorial.

Here’s a .pdf on how to connect the wires of the PICkit if you need it. Don’t worry it’s for PICkit4, but will work just fine for our simple project. http://ww1.microchip.com/downloads/en/devicedoc/50002721a.pdf

Source code files (2)

main.c

This is the main.c file for our project. This is where you would edit the necessary codes to display anything you want that can be displayed on the 16×2 LCD display.

/*
 * File:   main.c
 * Author: RJS
 *
 * 
 */

//Define crystal oscillator frequency.
#define _XTAL_FREQ 20000000

/*Define the ports for the LCD. Change as needed.
 */

#define RS RD2
#define EN RD3
#define D4 RD4
#define D5 RD5
#define D6 RD6
#define D7 RD7

//Include needed libraries.
#include "lcd.h"
#include <xc.h>
#include <pic16f877a.h>

// CONFIG
#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable bit (BOR disabled)
#pragma config LVP = OFF        // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

void main() {
    TRISD=0;
    Lcd_Start();

    //---------lcd display---------------------------------------------------------
    Lcd_Clear();
    Lcd_Set_Cursor(1,1);
    Lcd_Print_String("SUBSCRIBE TO");
    Lcd_Set_Cursor(2,1);
    Lcd_Print_String("rukbook.com");
    //---------lcd display---------------------------------------------------------
    
    while(1)
    {
        ;
    }
}
                

        
       
LCD.h

This is a header file developed by https://circuitdigest.com/. This file contains all the necessary functions that we need to display things using our LCD display. Otherwise, we would have to hard code everything when we want to display something as simple as a letter.

The .h file format stands for “header file” in C (or C++) language(s).

Don’t be afraid of the large size of the LCD.h file, we don’t want to do any edits to that. Just leave as it is.

/* Microchip Technology Inc. and its subsidiaries.  You may use this software 
 * and any derivatives exclusively with Microchip products. 
 * 
 * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS".  NO WARRANTIES, WHETHER 
 * EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, INCLUDING ANY IMPLIED 
 * WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, AND FITNESS FOR A 
 * PARTICULAR PURPOSE, OR ITS INTERACTION WITH MICROCHIP PRODUCTS, COMBINATION 
 * WITH ANY OTHER PRODUCTS, OR USE IN ANY APPLICATION. 
 *
 * IN NO EVENT WILL MICROCHIP BE LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, 
 * INCIDENTAL OR CONSEQUENTIAL LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND 
 * WHATSOEVER RELATED TO THE SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS 
 * BEEN ADVISED OF THE POSSIBILITY OR THE DAMAGES ARE FORESEEABLE.  TO THE 
 * FULLEST EXTENT ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS 
 * IN ANY WAY RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF 
 * ANY, THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE.
 *
 * MICROCHIP PROVIDES THIS SOFTWARE CONDITIONALLY UPON YOUR ACCEPTANCE OF THESE 
 * TERMS. 
 */

/* 
 * File:   
 * Author: 
 * Comments:
 * Revision history: 
 */

// This is a guard condition so that contents of this file are not included
// more than once.  
#ifndef XC_HEADER_TEMPLATE_H
#define	XC_HEADER_TEMPLATE_H

#include <xc.h> // include processor files - each processor file is guarded.  

// TODO Insert appropriate #include <>

// TODO Insert C++ class definitions if appropriate

// TODO Insert declarations

// Comment a function and leverage automatic documentation with slash star star
/**
    <p><b>Function prototype:</b></p>
  
    <p><b>Summary:</b></p>

    <p><b>Description:</b></p>

    <p><b>Precondition:</b></p>

    <p><b>Parameters:</b></p>

    <p><b>Returns:</b></p>

    <p><b>Example:</b></p>
    <code>
 
    </code>

    <p><b>Remarks:</b></p>
 */
// TODO Insert declarations or function prototypes (right here) to leverage 
// live documentation

#ifdef	__cplusplus
extern "C" {
#endif /* __cplusplus */

    // TODO If C++ is being used, regular C code needs function names to have C 
    // linkage so the functions can be used by the c code. 

#ifdef	__cplusplus
}
#endif /* __cplusplus */

#endif	/* XC_HEADER_TEMPLATE_H */

//LCD Functions Developed by Circuit Digest.
void Lcd_SetBit(char data_bit) //Based on the Hex value Set the Bits of the Data Lines
{
    if(data_bit& 1) 
        D4 = 1;
    else
        D4 = 0;

    if(data_bit& 2)
        D5 = 1;
    else
        D5 = 0;

    if(data_bit& 4)
        D6 = 1;
    else
        D6 = 0;

    if(data_bit& 8) 
        D7 = 1;
    else
        D7 = 0;
}

void Lcd_Cmd(char a)
{
    RS = 0;           
    Lcd_SetBit(a); //Incoming Hex value
    EN  = 1;         
        __delay_ms(4);
        EN  = 0;         
}

Lcd_Clear()
{
    Lcd_Cmd(0); //Clear the LCD
    Lcd_Cmd(1); //Move the cursor to first position
}

void Lcd_Set_Cursor(char a, char b)
{
    char temp,z,y;
    if(a== 1)
    {
      temp = 0x80 + b - 1; //80H is used to move the curser
        z = temp>>4; //Lower 8-bits
        y = temp & 0x0F; //Upper 8-bits
        Lcd_Cmd(z); //Set Row
        Lcd_Cmd(y); //Set Column
    }
    else if(a== 2)
    {
        temp = 0xC0 + b - 1;
        z = temp>>4; //Lower 8-bits
        y = temp & 0x0F; //Upper 8-bits
        Lcd_Cmd(z); //Set Row
        Lcd_Cmd(y); //Set Column
    }
}

void Lcd_Start()
{
  Lcd_SetBit(0x00);
  for(int i=1065244; i<=0; i--)  NOP();  
  Lcd_Cmd(0x03);
    __delay_ms(5);
  Lcd_Cmd(0x03);
    __delay_ms(11);
  Lcd_Cmd(0x03); 
  Lcd_Cmd(0x02); //02H is used for Return home -> Clears the RAM and initializes the LCD
  Lcd_Cmd(0x02); //02H is used for Return home -> Clears the RAM and initializes the LCD
  Lcd_Cmd(0x08); //Select Row 1
  Lcd_Cmd(0x00); //Clear Row 1 Display
  Lcd_Cmd(0x0C); //Select Row 2
  Lcd_Cmd(0x00); //Clear Row 2 Display
  Lcd_Cmd(0x06);
}

void Lcd_Print_Char(char data)  //Send 8-bits through 4-bit mode
{
   char Lower_Nibble,Upper_Nibble;
   Lower_Nibble = data&0x0F;
   Upper_Nibble = data&0xF0;
   RS = 1;             // => RS = 1
   Lcd_SetBit(Upper_Nibble>>4);             //Send upper half by shifting by 4
   EN = 1;
   for(int i=2130483; i<=0; i--)  NOP(); 
   EN = 0;
   Lcd_SetBit(Lower_Nibble); //Send Lower half
   EN = 1;
   for(int i=2130483; i<=0; i--)  NOP();
   EN = 0;
}

void Lcd_Print_String(char *a)
{
    int i;
    for(i=0;a[i]!='\0';i++)
       Lcd_Print_Char(a[i]);  //Split the string using pointers and call the Char function 
}

Code explanation of main.c

Let’s first consider the source code of main.c and get an understanding of the code.

Lines 1 to 6
/*
 * File:   main.c
 * Author: RJS
 *
 * 
 */

These lines create a multi-line comment. A multi-line comment is started with /* and ended with */. This method of commenting can be found in various other programming languages also, like C++, Java, and JavaScript.

These lines are not compiled by the compiler. (They are simply ignored). The reason comments are included in such a script is to give an idea of the code to the programmer in a later day or to someone completely unfamiliar with the particular code.

Lines 8 to 19
//Defube crystal oscillator frequency.
#define _XTAL_FREQ 20000000

/*Define the ports for the LCD. Change as needed.
 */

#define RS RD2
#define EN RD3
#define D4 RD4
#define D5 RD5
#define D6 RD6
#define D7 RD7

These lines are #define preprocessor directives. This preprocessor directive is used to define symbolic constants in C. That means creating labeled constants with particular values.

For example, the name of the first constant is _XTAL_FREQ. The value of that constant is 20000000, which is the frequency of the crystal oscillator (20MHz) we are using. Notice that here we have followed the norm that names of symbolic constants are in uppercase in C.

The RS symbolic constant name is holding the value RD2. Here, the RS is a pin found in the 16×2 LCD display module. The RD2 is a pin (the pin 21) of the PIC16F877A. We are simply defining these symbolic constants so that we can easily refer to the pins of the microcontroller.

The same applies to the rest of the lines up to line 19.

Lines 21 to 24
//Include needed libraries.
#include "lcd.h"
#include <xc.h>
#include <pic16f877a.h>

These lines are #include preprocessor directives. This preprocessor directive is used to add necessary external codes to a script in C. Think of it as just copy-pasting the contents of each of these header files to the top of our main.c script’s code.

Lines 26 to 34
// CONFIG
#pragma config FOSC = HS        // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF       // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF      // Power-up Timer Enable bit (PWRT disabled)
#pragma config BOREN = OFF      // Brown-out Reset Enable bit (BOR disabled)
#pragma config LVP = OFF        // Low-Voltage (Single-Supply) In-Circuit Serial Programming Enable bit (RB3 is digital I/O, HV on MCLR must be used for programming)
#pragma config CPD = OFF        // Data EEPROM Memory Code Protection bit (Data EEPROM code protection off)
#pragma config WRT = OFF        // Flash Program Memory Write Enable bits (Write protection off; all program memory may be written to by EECON control)
#pragma config CP = OFF         // Flash Program Memory Code Protection bit (Code protection off)

These special lines of code are mostly auto-generated using the Configuration Bits option in MPLAB X IDE.

Let’s not worry about them for now. (also I am not very much familiar with them)

Line 37
TRISD=0;

This sets all the pins of port D of the microcontroller to be output pins (0 sets as output and 1 sets as input). We do this because we need to output data from the microcontroller to our LCD display.

Line 38
Lcd_Start();

This initializes our LCD display.

Line 41
Lcd_Clear();

This clears whatever thing is currently displayed on the LCD display. We do this because then we can start fresh to display something.

Lines 42 and 44
Lcd_Set_Cursor(1,1);
Lcd_Set_Cursor(2,1);

These lines set the cursor of the LCD to a particular position. The 16×2 display has 16 columns and 2 rows.

In line 42, the cursor is brought to 1, 1 coordinate; the upper leftmost block of the 16×2 LCD grid.

In line 44, the cursor is brought to 2, 1 coordinate; the bottom leftmost block of the 16×2 LCD grid.

After setting the cursor, the LCD writes from left to right.

Lines 43 and 45
Lcd_Print_String("SUBSCRIBE TO");
Lcd_Print_String("rukbook.com");

These lines are used to actually display the text. These functions are defined in the lcd.h file. We are calling those functions from our main.c as needed.

Lines 48 to 51
while(1)
{
	;
}

This is an infinite while loop. This is the main loop of our program. The conditional expression we have used here is number 1. Since any non-zero value is a logical true in C language, this condition is always true. Therefore this while loop is always executed.

The ; in line 50 is an interesting code part. It is called a null statement in C. It does absolutely nothing, but is important for the program to run without errors. If we removed that semicolon, the program gives errors and the LCD display starts flickering. We could add more and more code inside this while loop as our program become more advanced.

Programming our Microcontroller

After setting up our project circuit as in the above circuit, connect the PICkit3 to your computer’s USB port.

Turn on the switch of the circuit (if not using a switch, connect the 12V power supply to the terminals as in the above circuit).

Then Programming (downloading our code into the microcontroller) the Microcontroller is as easy as just clicking the button indicated in the image.

If you see the following displayed on the Output Window in MPLAB X IDE, that means you are good to go!

Remove all the connections of PICkit3 from the circuit and enjoy your success!

Conclusion

That’s how interfacing an LCD display with PIC16F877A is done. Why not share this article on Facebook, LinkedIn or any other platform of your choice? Also, make sure to subscribe to rukbook.com so that you can be notified with an email whenever a new article appears on rukbook.com. Thanks for reading.

Subscribe

* indicates required
3.8 6 votes
Article Rating

Leave a comment. We love your feedback.

2 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Nimasha
Nimasha
4 months ago

Great 🔥

2
0
Would love your thoughts, please comment.x
()
x