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

Tutorial Contents


  Required Components For This Tutorial  

Qty. Component Name Buy On Amazon.com
1 PIC16F877A

or PIC18F2550

Add

Add

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

 

COMPLETE CONNECTION DIAGRAM OF LCD

 


   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).

16x2-LCD CGROM 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

 

PCBgogo Ad

   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 16×2 LCD Driver   

 

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.

#define LCD_DATA_PORT_D TRISB
#define LCD_RS_D TRISB5
#define LCD_EN_D TRISB4
#define RS RB5
#define EN RB4
#define D4 RB0
#define D5 RB1
#define D6 RB2
#define D7 RB3

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.

void LCD_DATA(unsigned char Data)
{
  if(Data & 1)
    D4 = 1;
  else
    D4 = 0;
  if(Data & 2)
    D5 = 1;
  else
    D5 = 0;
  if(Data & 4)
    D6 = 1;
  else
    D6 = 0;
  if(Data & 8)
    D7 = 1;
  else 
    D7 = 0;
}

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.

void LCD_CMD(unsigned char CMD)
{
  // Select Command Register
  RS = 0;
  // Move The Command Data To LCD
  LCD_DATA(CMD);
  // Send The EN Clock Signal
  EN = 1;
  __delay_us(LCD_EN_Delay);
  EN = 0;
}

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.

void LCD_Init()
{
  // IO Pin Configurations
  LCD_DATA_PORT_D = 0x00;
  LCD_RS_D = 0;
  LCD_EN_D = 0;
  // The Init. Procedure
  LCD_DATA(0x00);
  __delay_ms(30);
  __delay_us(LCD_EN_Delay);
  LCD_CMD(0x03);
  __delay_ms(5);
  LCD_CMD(0x03);
  __delay_us(150);
  LCD_CMD(0x03);
  LCD_CMD(0x02);
  LCD_CMD(0x02); 
  LCD_CMD(0x08); 
  LCD_CMD(0x00);
  LCD_CMD(0x0C);
  LCD_CMD(0x00);
  LCD_CMD(0x06);
}

 

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!

void LCD_Write_Char(char Data)
{
  char Low4,High4;
  Low4 = Data & 0x0F;
  High4 = Data & 0xF0;
  RS = 1;
  LCD_DATA(High4>>4);
  EN = 1;
  __delay_us(LCD_EN_Delay);
  EN = 0;
  __delay_us(LCD_EN_Delay); 
  LCD_DATA(Low4);
  EN = 1;
  __delay_us(LCD_EN_Delay);
  EN = 0;
  __delay_us(LCD_EN_Delay);
}

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.

void LCD_Write_String(char *str)
{
  int i;
  for(i=0;str[i]!='\0';i++)
    LCD_Write_Char(str[i]);
}

 


  Adding Auxiliary Functions To LCD Driver  

 

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

void LCD_Clear()
{
  LCD_CMD(0);
  LCD_CMD(1);
}

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

void LCD_Set_Cursor(unsigned char r, unsigned char c)
{
  unsigned char Temp,Low4,High4;
  if(r == 1) 
  {
    Temp = 0x80 + c - 1; //0x80 is used to move the cursor
    High4 = Temp >> 4;
    Low4 = Temp & 0x0F;
    LCD_CMD(High4);
    LCD_CMD(Low4);
  }
  if(r == 2) 
  {
    Temp = 0xC0 + c - 1;
    High4 = Temp >> 4;
    Low4 = Temp & 0x0F;
    LCD_CMD(High4);
    LCD_CMD(Low4);
  }
}

Shift Entire Display Right

Shift LCD Entire Display Right

void LCD_SR()
{
  LCD_CMD(0x01);
  LCD_CMD(0x0C);
}

Shift Entire Display Left

Shift LCD Entire Display Left

void LCD_SL()
{
  LCD_CMD(0x01);
  LCD_CMD(0x08);
}

 


   Organizing The LCD Driver Files   

 

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

#include <xc.h>

#define _XTAL_FREQ 48000000

#define LCD_EN_Delay 500
#define LCD_DATA_PORT_D TRISB
#define LCD_RS_D TRISB5
#define LCD_EN_D TRISB4
#define RS RB5
#define EN RB4
#define D4 RB0
#define D5 RB1
#define D6 RB2
#define D7 RB3

//==============================================
//-----[ Prototypes For All LCD Functions ]-----

void LCD_Init(); // Initialize The LCD For 4-Bit Interface
void LCD_Clear(); // Clear The LCD Display
void LCD_SL(); // Shift The Entire Display To The Left
void LCD_SR(); // Shift The Entire Display To The Right

void LCD_CMD(unsigned char); // Send Command To LCD
void LCD_DATA(unsigned char); // Send 4-Bit Data To LCD
void LCD_Set_Cursor(unsigned char, unsigned char); // Set Cursor Position
void LCD_Write_Char(char); // Write Character To LCD At Current Position
void LCD_Write_String(char*); // Write A String To LCD

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.

#include <xc.h>
#include "LCD.h"
#include "config.h"

void main(void) {

  LCD_Init();
  LCD_Clear();
  LCD_Set_Cursor(1,1);
  LCD_Write_String("Hello World\0");
  while(1)
  {

  }
  return;
}

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

 


   Writing Text To LCD 16×2 – 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.

timer-preloading

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.

timer-preloading

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

/*
* File: main.c
* Author: Khaled Magdy
* LAB Number: 28
* LAB Name: Writing Text To LCD
*/
#include <xc.h>
#include "config.h"
#define _XTAL_FREQ 48000000

#define LCD_EN_Delay 500
#define LCD_DATA_PORT_D TRISB
#define LCD_RS_D TRISB5
#define LCD_EN_D TRISB4
#define RS RB5
#define EN RB4
#define D4 RB0
#define D5 RB1
#define D6 RB2
#define D7 RB3
//==========================================
//-----[ Prototypes For All Functions ]-----
void LCD_Init();
void LCD_Clear();
void LCD_SL();
void LCD_SR();
void LCD_CMD(unsigned char);
void LCD_DATA(unsigned char);
void LCD_Set_Cursor(unsigned char, unsigned char);
void LCD_Write_Char(char);
void LCD_Write_String(char*);
//------------------------------------------
void main(void) {

  LCD_Init();
  LCD_Clear();
  LCD_Set_Cursor(1,1);
  LCD_Write_String("Khaled Magdy\0");
  LCD_Set_Cursor(2,1);
  LCD_Write_String(" DeepBlue\0");
  while(1)
  {
    
  }
  return;
}
//============================================
//-----[ Alphanumeric LCD 16x2 Routines ]-----
void LCD_DATA(unsigned char Data)
{
  if(Data & 1)
    D4 = 1;
  else
    D4 = 0;
  if(Data & 2)
    D5 = 1;
  else
    D5 = 0;
  if(Data & 4)
    D6 = 1;
  else
    D6 = 0;
  if(Data & 8)
    D7 = 1;
  else
    D7 = 0;
}
void LCD_CMD(unsigned char CMD)
{
  // Select Command Register
  RS = 0;
  // Move The Command Data To LCD
  LCD_DATA(CMD);
  // Send The EN Clock Signal
  EN = 1;
  __delay_us(LCD_EN_Delay);
  EN = 0; 
}
void LCD_Clear()
{
  LCD_CMD(0);
  LCD_CMD(1);
}
void LCD_Set_Cursor(unsigned char r, unsigned char c)
{
  unsigned char Temp,Low4,High4;
  if(r == 1) 
  {
    Temp = 0x80 + c - 1; //0x80 is used to move the cursor
    High4 = Temp >> 4;
    Low4 = Temp & 0x0F;
    LCD_CMD(High4);
    LCD_CMD(Low4);
  }
  if(r == 2) 
  {
    Temp = 0xC0 + c - 1;
    High4 = Temp >> 4;
    Low4 = Temp & 0x0F;
    LCD_CMD(High4);
    LCD_CMD(Low4);
  }
}
void LCD_Init()
{
  // IO Pin Configurations
  LCD_DATA_PORT_D = 0x00;
  LCD_RS_D = 0;
  LCD_EN_D = 0; 
  // The Init. Procedure As Described In The Datasheet
  LCD_DATA(0x00);
  __delay_ms(30);
  __delay_us(LCD_EN_Delay);
  LCD_CMD(0x03);
  __delay_ms(5);
  LCD_CMD(0x03);
  __delay_us(150);
  LCD_CMD(0x03);
  LCD_CMD(0x02);
  LCD_CMD(0x02);
  LCD_CMD(0x08);
  LCD_CMD(0x00);
  LCD_CMD(0x0C);
  LCD_CMD(0x00);
  LCD_CMD(0x06);
}
void LCD_Write_Char(char Data)
{
  char Low4,High4;
  Low4 = Data & 0x0F;
  High4 = Data & 0xF0;
  RS = 1;
  LCD_DATA(High4>>4);
  EN = 1;
  __delay_us(LCD_EN_Delay);
  EN = 0;
  __delay_us(LCD_EN_Delay);
  LCD_DATA(Low4);
  EN = 1;
  __delay_us(LCD_EN_Delay);
  EN = 0;
  __delay_us(LCD_EN_Delay);
}
void LCD_Write_String(char *str)
{
  int i;
  for(i=0;str[i]!='\0';i++)
    LCD_Write_Char(str[i]);
}
void LCD_SL()
{
  LCD_CMD(0x01);
  LCD_CMD(0x08);
}
void LCD_SR()
{
  LCD_CMD(0x01);
  LCD_CMD(0x0C);
}

 

 

       2. Simulation       

 

Here is the simulation’s result.

LCD LAB1 Simulation

 

       3. Prototyping       

 

LCD 16x2 LAB1 Text Display

 

Download Project

 


   Writing A Moving Text To LCD – 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.

timer-preloading

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.

timer-preloading

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

/*
* File: main.c
* Author: Khaled Magdy
* LAB Number: 28
* LAB Name: Writing Text To LCD
*/
#include <xc.h>
#include "config.h"
#define _XTAL_FREQ 48000000

#define LCD_EN_Delay 500
#define LCD_DATA_PORT_D TRISB
#define LCD_RS_D TRISB5
#define LCD_EN_D TRISB4
#define RS RB5
#define EN RB4
#define D4 RB0
#define D5 RB1
#define D6 RB2
#define D7 RB3
//==========================================
//-----[ Prototypes For All Functions ]-----
void LCD_Init();
void LCD_Clear();
void LCD_SL();
void LCD_SR();
void LCD_CMD(unsigned char);
void LCD_DATA(unsigned char);
void LCD_Set_Cursor(unsigned char, unsigned char);
void LCD_Write_Char(char);
void LCD_Write_String(char*);
//------------------------------------------
void main(void) {

  LCD_Init();
  LCD_Clear();
  LCD_Set_Cursor(1,1);
  LCD_Write_String("Khaled Magdy\0");
  LCD_Set_Cursor(2,1);
  LCD_Write_String(" DeepBlue\0");
  while(1)
  {
    for(int i=0; i<4; i++)
    {
      __delay_ms(300);
      LCD_SR();
    }
    for(int i=0; i<4; i++)
    {
      __delay_ms(300);
      LCD_SL();
    }
  }
  return;
}
//============================================
//-----[ Alphanumeric LCD 16x2 Routines ]-----
void LCD_DATA(unsigned char Data)
{
  if(Data & 1)
    D4 = 1;
  else
    D4 = 0;
  if(Data & 2)
    D5 = 1;
  else
    D5 = 0;
  if(Data & 4)
    D6 = 1;
  else
    D6 = 0;
  if(Data & 8)
    D7 = 1;
  else
    D7 = 0;
}
void LCD_CMD(unsigned char CMD)
{
  // Select Command Register
  RS = 0;
  // Move The Command Data To LCD
  LCD_DATA(CMD);
  // Send The EN Clock Signal
  EN = 1;
  __delay_us(LCD_EN_Delay);
  EN = 0; 
}
void LCD_Clear()
{
  LCD_CMD(0);
  LCD_CMD(1);
}
void LCD_Set_Cursor(unsigned char r, unsigned char c)
{
  unsigned char Temp,Low4,High4;
  if(r == 1) 
  {
    Temp = 0x80 + c - 1; //0x80 is used to move the cursor
    High4 = Temp >> 4;
    Low4 = Temp & 0x0F;
    LCD_CMD(High4);
    LCD_CMD(Low4);
  }
  if(r == 2) 
  {
    Temp = 0xC0 + c - 1;
    High4 = Temp >> 4;
    Low4 = Temp & 0x0F;
    LCD_CMD(High4);
    LCD_CMD(Low4);
  }
}
void LCD_Init()
{
  // IO Pin Configurations
  LCD_DATA_PORT_D = 0x00;
  LCD_RS_D = 0;
  LCD_EN_D = 0; 
  // The Init. Procedure As Described In The Datasheet
  LCD_DATA(0x00);
  __delay_ms(30);
  __delay_us(LCD_EN_Delay);
  LCD_CMD(0x03);
  __delay_ms(5);
  LCD_CMD(0x03);
  __delay_us(150);
  LCD_CMD(0x03);
  LCD_CMD(0x02);
  LCD_CMD(0x02);
  LCD_CMD(0x08);
  LCD_CMD(0x00);
  LCD_CMD(0x0C);
  LCD_CMD(0x00);
  LCD_CMD(0x06);
}
void LCD_Write_Char(char Data)
{
  char Low4,High4;
  Low4 = Data & 0x0F;
  High4 = Data & 0xF0;
  RS = 1;
  LCD_DATA(High4>>4);
  EN = 1;
  __delay_us(LCD_EN_Delay);
  EN = 0;
  __delay_us(LCD_EN_Delay);
  LCD_DATA(Low4);
  EN = 1;
  __delay_us(LCD_EN_Delay);
  EN = 0;
  __delay_us(LCD_EN_Delay);
}
void LCD_Write_String(char *str)
{
  int i;
  for(i=0;str[i]!='\0';i++)
    LCD_Write_Char(str[i]);
}
void LCD_SL()
{
  LCD_CMD(0x01);
  LCD_CMD(0x08);
}
void LCD_SR()
{
  LCD_CMD(0x01);
  LCD_CMD(0x0C);
}

 

     2. Simulation       

 

Here is the simulation’s result.

LCD LAB2 Simulation Animation

 

       3. Prototyping       

 

 

Download Project

 


   Concluding Remarks   

 

  1  

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

  2  

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).

  3  

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.

 

 

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!

Regards,

 

Previous Tutorial Previous Tutorial Tutorial 25 Next Tutorial Next Tutorial
Share This Page With Your Network!

Khaled Magdy

I'm an embedded systems engineer doing both Software & Hardware. I'm an EE guy who studied Computer Engineering, But I'm also passionate about Computer Science. I love reading, writing, creating projects and Technical training. A reader by day a writer by night, it's my lifestyle. You can view my profile or follow me via contacts.

You may also like...

3 Responses

  1. Marcel van Santen says:

    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. Sipho says:

    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.

  1. May 29, 2021

    […] I should preface this section by saying that because I really didn’t feel like deciphering the HD44780 datasheet, the upcoming stuff is largely referenced off this really nice post: https://deepbluembedded.com/interfacing-16×2-lcd-with-pic-microcontrollers-mplab-xc8/ […]

Leave a Reply

%d bloggers like this: