View Course Path

LCD interfacing with 8051 – 8-bit, 4-bit mode, and with 8255 PPI

In this tutorial, we will start easy by directly interfacing LCD in 4-bit and 8-bit modes to the 8051 microcontroller. And then, we will move on to interfacing the LCD module to the 8051 via 8255 PPI (programmable peripheral interface).

Components required to interface LCD module with 8051

  1. 8051 microcontroller – AT89C51/AT89S51/52 or similar variants.
  2. 8255 PPI
  3. LCD module: 16 x 2 characters display (LM016L)
  4. Oscillator crystal – 12Mhz
  5. Capacitors – 22pF x 2, 10µF x 1
  6. Resistors – 10kΩ x 1
  7. 74LS373 decoder

What is an LCD?

LCD is short for Liquid Crystal Display. Each LCD screen contains a matrix of pixels that together form an image on the screen. The LCD is a flat panel display technology commonly used in consumer electronics.

The 16×2 LCD module consists of 2 rows, each with 16 columns, which can, in turn, each display 16 characters. The main advantage of using a character LCD instead of a seven-segment display and other multi-segment LEDs is that there is no limitation in displaying special & custom character animations. Off the 16 pins, eight are data pins through which data or commands are passed into the LCD registers.

Pin diagram of an LCD module

LCD(16x2) pin diagram

  1. Pin1 (VSS): Ground pin of the LCD module. (0V is given to this pin.)
  2. Pin2 (VDD): Power to LCD module (+5V supply is given to this pin.)
  3. Pin3 (VEE): Contrast adjustment pin. This is done by connecting the ends of a 10K potentiometer to +5V and ground and then connecting the slider pin to the VEE pin. The normal setting is between 0.4V and 0.9V.
  4. Pin4 (RS): Register select pin.
    • The LCD module has two registers, namely, command register and data register.
    • RS = 1 implies that the data register is selected. RS = 0 implies that the command register is selected.
  5. Pin5 (R/W): Read/Write modes.
    • Logic HIGH at this pin = read mode.
    • Logic LOW at this pin = write mode.
  6. Pin6(E): Enables the LCD module.
    • A HIGH to LOW pulse at this pin will enable the module.
  7. Pin7 (DB0) to Pin14 (DB7): The data pins.
    • The commands and data are fed to the LCD module through these pins.
    • They are 8-bit wide and called as the 8-bit data bus.
  8. Pin15(LED+): Anode of the back-light LED.
  9. Pin16(LED-): Cathode of the back-light LED.

How does an LCD module work?

First of all, we’re using a 16×2 LCD (read as 16 by 2), i.e., we can display 16 characters on row 1 as well as row 2. You’ve already seen the pin diagram. The three main important pins of this LCD are RS, R/W, and E (Enable pin). Whereas, the VEE pin is connected to a 10k potentiometer to control the brightness of the display.

The LCD module consists of 2 registers:

  • Command Register: Users can write any command such as clear display, display ON, cursor ON, or select the 16×2 mode of LCD.
  • Data Register: Users must write the ASCII value of the character that they wish to display on the LCD.

Let us understand how the control signals work:

  1. RS: To select between the command or the data register, the RS (called Register Select) signal is used.
    • If we make the RS pin HIGH and feed input to the data lines (DB0 to DB7), this input will be interpreted as the data that is to be displayed on the LCD screen.
    • If we make the RS pin LOW and feed input to the data lines, then this will be interpreted as a command (a command to be written to LCD controller – like positioning cursor or clear screen or scroll.)
  2. R/W (bar): It is the read or write signal
    • R/W (bar) = LOW, implies Write Mode
    • R/W (bar) = HIGH, implies Read Mode
  3. EN: The Enable signal is used to latch the data (that is to place the data on the data bus from D0 – D7 and wait for a clock or pulse to send it), and when high to low pulse is sent into the E pin of LCD the data is displayed on the LCD.

Now, we’ll look at the commands we send to the command register. You can refer to these for quick reference.

  1. 38H or 00111000B: It is used to interface LCD in an 8-bit mode (use eight pins to communicate). It selects LCD of two rows and each character of a 5×7 matrix display. Now don’t get confused with 16×2 and 5×7. 16×2 represents 16 characters each in 2 rows of the display whereas, 5×7 represents the dot matrix display of each character.
    5x7 dot matrix
  2. 28H or 00101000B: Used to interface LCD in 4-bit mode (only four pins are used to communicate.)
  3. OEH or 00001110B: This command is used for Display ON and cursor ON function.
  4. 01H or 00000001B: This command is used for the Clear Display function.
  5. 80H or 10000000B: Each row consists of 16 characters, and each character has a unique address depending on the manufacturer. To display any character in the LCD’s display, the first ASCII value is sent to the address of the first character into the command register. After this, the cursor automatically increments by one and moves to the next character position.

The Display Data RAM (DDRAM) is a RAM on the LCD module that stores the ASCII code of the characters that we send to the LCD module. The DDRAM can store up to 80 characters (it has a capacity of 80×1 byte). For example, starting from 0x00 to 0x4F. But only some of these 80 characters are displayed on the LCD. In the case of a 16×2 LCD, only 32 of these memory locations are displayed.

Now, if we write a particular character to the DDRAM address 0x00, it will be displayed in the first cell of the first line. Similarly, if we write a character to address 0x40, it will appear in the same position but on the next line. Now, the AC (Address Counter) command is used to access any address of this DDRAM and is also responsible for the positioning of the character in the LCD display.

The set DDRAM address instruction is used to set the address of the DDRAM. For example, when 0x80 is sent to data lines, it will move the cursor to move to the first cell of the first line of the display.

The following table will give you a brief idea about some other commands that can be sent to the LCD.

Code (Hex) Command to LCD
0x01 Clear the display screen
0x06 Shift the cursor right
0x0C Display on, cursor off
0x0E Display on, cursor blinking
0x80 Force the cursor to the beginning of the 1st line
0xC0 Force the cursor to the beginning of the 2nd line
0x10 Shift cursor position to the left
0x14 Shift cursor position to the right
0x18 Shift entire display to the left
0x1C Shift entire display to the right
0x38 2 lines, 5×8 matrix, 8-bit mode
0x28 2 lines, 5×8 matrix, 4-bit mode
0x30 1 line, 8-bit mode
0x20 1 line, 4-bit mode

4-bit mode

In a 4-bit mode, only the higher-order data bus lines are connected, i.e., D4, D5, D6, and D7 to the LCD. Therefore, the data/ commands are sent in a 4-bit (nibble) format. But how do we send 8-bit data in a 4-bit form? We send one nibble at a time. That is, first, the higher nibble of the data is sent, which is then followed by the lower nibble.

Let us take a small example. The port we are selecting to send the data to the LCD from is Port 1. Its address in the internal RAM is from 90H. So, we set this by using the command sfr ldata = 0x90;.

  1. First and foremost, send 28H to the send_command() function to set the LCD in 4-bit mode.
  2. For demonstration, let’s assume the data we’re sending is value = 0x38. To extract the higher nibble, we use the command ldata = (ldata & 0x0F) | (value & 0xF0);.
      • ldata = (1001 0000 & 0000 1111) | (0011 1000 & 1111 0000)
      • ldata = (0000 0000) | (0011 0000)
      • ldata = (0011 0000) = higher nibble of 0x38
  1. Next, to extract the lower nibble, we use the command ldata = (ldata & 0x0F) | (value << 4);, note that her “<<” is called the Left Shift Operator, i.e. value << 4 means shifting the value 4 bits towards the left and other bits will be zero by default.
    • ldata = (0011 0000 & 0000 1111) | (0011 1000 << 4)
    • ldata = (0000 0000) | (1000 0000)
    • ldata = (1000 0000) = lower nibble of 0x38
  2. Now this data is sent to pins D4 – D7 of the LCD display one by one without any delay (first the higher (0x30), then lower nibble (0x80)) causing the effect of sending an 8-bit data to D0 – D7. Note that the most significant nibble is transferred first followed by the least significant nibble.

That’s how we send 8-bit data in the 4-bit mode.

If your project requires a lot of peripherals to be connected to the same microcontroller, then we would require a large number of ports. In this scenario, you can smartly utilize the existing ports to send more data with fewer numbers of data lines. Hence, the 4-bit mode is used as we send or receive 8-bit information to or from the LCD module, we’re using up only four pins of the microcontroller.

However, the disadvantage is that it reduces the speed of operation as we’re sending the information in twice the time now. So, when the speed with which the data is displayed is not much of an issue, then the LCD module can be used in the 4-bit mode.

Circuit diagram to interface 16×2 LCD in 4-bit mode with 8051

Circuit diagram to interface LCD module in 4-bit mode with 8051
Note: There is a correction to be made in the above circuit diagram. Just connect Pin1 of the LCD to GND and Pin 2 to +5V. The diagram shows both the pins connected to +5 – this is incorrect.

C Code to interface 16×2 LCD in 4-bit mode with 8051

#include<reg51.h>

sfr ldata = 0x90; //P1 = LCD dat pins
sbit rs = P2^0;
sbit rw = P2^1;
sbit en = P2^2;

void send_command(unsigned char);
void send_data(unsigned char);
void ms_delay(unsigned int);

void main()
{
    unsigned int i=0;
    unsigned char line_1[] = "TECHNOBYTE"; 
    unsigned char line_2[] = "MICROCONTROLLERS"; 

    P1 = 0x00; // Port-1 as Output
    P2 = 0x00; // Port-2 as Output

    send_command(0x02); // Initialize LCD in 4_bit mode
    ms_delay(250);
    send_command(0x28); // Enable 5x7 dot matrix for each character
    ms_delay(250);
    send_command(0x0E); // Display ON & cursor ON
    ms_delay(250);
    send_command(0x01); // Clear the Display
    ms_delay(250);
    send_command(0x83); // Set thee cursor at line 1, position 3
    ms_delay(250);

    // Display the dat on LCD's first line
    for (i = 0; i < 11; i++) 
    { 
        send_data(line_1[i]);  
    }

    send_command(0xC0); // Select the second line 

    // Display the dat on LCD's second line
    for (i = 0; i < 16; i++)
    { 
        send_data(line_2[i]); 
    }
    while(1); // Run this forever until power it cutoff
}

void send_command(unsigned char value)
{
    ldata = (ldata & 0x0F) | (value & 0xF0); // To send the upper nibble
    rs = 0;
    rw = 0;
    en = 1;
    ms_delay(10);
    en = 0;

    ldata = (ldata & 0x0F) | (value << 4); // To send the lower nibble
    rs = 0;
    rw = 0;
    en = 1;
    ms_delay(10);
    en = 0;
    return;
}

void send_data(unsigned char value)
{
    ldata = (ldata & 0x0F) | (value & 0xF0); // To send the upper nibble
    rs = 1;
    rw = 0;
    en = 1;
    ms_delay(10);
    en = 0;

    ldata = (ldata & 0x0F) | (value << 4); // To send the lower nibble
    rs = 1;
    rw = 0;
    en = 1;
    ms_delay(10);
    en = 0;
    return;
}

void ms_delay(unsigned int ms) // Function to create a delay of 'ms' milliseconds
{
    unsigned int i, j;
    for(i = 0; i <= ms; i++){
        for(j = 0; j < 1275; j++);}
}

8-bit Mode

In 8-bit mode, we connect all the eight pins of Port 1 (P0 – P7) to the LCDs pins (D0 – D7). Hence, we don’t need to separate the higher and lower nibble. At the very beginning, we send 38H to initialize the LCD in 8-bit mode, with a 5×7 dot matrix configuration.

Here is a flowchart defining all the functions used in the program to interface the LCD module in an 8-bit mode with 8051.

flowchat to interface lcd to 8051 using PPI

Circuit diagram to interface 16×2 LCD in 8-bit mode with 8051

Circuit diagram to interface LCD module in 8-bit mode with 8051

C Code to interface 16×2 LCD in 8-bit mode with 8051

#include<reg51.h>

sfr ldata = 0x90; //P1 = LCD dat pins
sbit rs = P2^0;
sbit rw = P2^1;
sbit en = P2^2;

void send_command(unsigned char);
void send_data(unsigned char);
void ms_delay(unsigned int);

void main()
{
    unsigned int i=0;
    unsigned char line_1[] = "TECHNOBYTE"; 
    unsigned char line_2[] = "MICROCONTROLLERS"; 

    P1 = 0x00; // Port-1 as Output
    P2 = 0x00; // Port-2 as Output

    send_command(0x38); // 16_bit mode; Enable 5x7 dot matrix for each character
    ms_delay(250);
    send_command(0x0E); // Display ON & cursor ON
    ms_delay(250);
    send_command(0x01); // Clear the Display
    ms_delay(250);
    send_command(0x06); // To shift the cursor right
    ms_delay(250);
    send_command(0x83); // Set the cursor at line 1, position 3
    ms_delay(250);

    // Display the dat on LCD's first line
    for (i = 0; i < 11; i++) 
    { 
        send_data(line_1[i]);  
    }

    send_command(0xC0); // Select the second line 

    // Display the dat on LCD's second line
    for (i = 0; i < 16; i++)
    { 
        send_data(line_2[i]); 
    }
    while(1); // Run this forever until power it cutoff
}

void send_command(unsigned char value)
{
    ldata = value
    rs = 0;
    rw = 0;
    en = 1;
    ms_delay(10);
    en = 0;
    return;
}

void send_data(unsigned char value)
{
    ldata = value
    rs = 1;
    rw = 0;
    en = 1;
    ms_delay(10);
    en = 0;
    return;
}

void ms_delay(unsigned int ms) // Function to create a delay of 'ms' milliseconds
{
    unsigned int i, j;
    for(i = 0; i <= ms; i++){
        for(j = 0; j < 1275; j++);}
}

Interfacing LCD with 8051 using 8255

To interface an LCD module with 8051 using 8255 PPI, we need to design a chip select logic to set a particular address for the additional ports of the 8255 PPI (Port A, Port B, Port C, and the control register). Here we’re going to set the following parameters to access Port A and B of 8255 as an output port, from which the wires are connected to the LCD module.

  • Address of Port A = 4000H
  • Address of Port B = 4001H
  • Both the ports are in Mode 0.
  • Both the ports are configured as output ports.

You can get a better understanding of how to construct the control word for the control register of 8255 to interface it with 8051 here: Interfacing of 8051 with 8255 Programmable Peripheral Interface.

Circuit diagram to interface LCD (16×2) with 8051

  • Step 1: If you’re using Proteus or and other simulation software or even hardware, select the AT89C51 or AT89S51 microcontroller or any other compatible variant.
  • Step 2: Connect a 12 MHz oscillator between pin 18 and 19.
  • Step 3: Connect two capacitors of 22pF, with one terminal on either side of the oscillator and the other terminal to ground, as shown below.
  • Step 4: Set Pin 31, i.e., EA (bar) pin to HIGH by connecting it to the +5V DC source.
  • Step 5: Now, to make the RESET circuit, connect Pin 9 (RST) to +5V through a capacitor of 10µF and connect the same pin to +0V (GND) through a 10kΩ resistor or a potentiometer.
  • Step 6: Connect WR and RD of 8255 PPI to P3.6 and P3.7, respectively, of 8051.
  • Step 7: Connect Port 0 (P0.0 – P0.7) to data pin (D0 – D7), respectively.
  • Step 8: Connect CS, RESET, GND to ground, and VCC to +5V supply.
  • Step 9: We use a decoder circuit to select either PORT A to send the data or PORT B to activate the LCD module. So, connect AD0 – AD7 to D0 – D7 of 74LS373 decoder.
  • Step 10: By default, the 0xFF is sent to all the ports of 8051, Therefore if we invert the output P2.6 (which is HIGH) to LOW and feed it to the chip select pin of 8255, then Port A will have an address of 4000H, Port B will have an address of 40001H and so on.
  • Step 10: Let’s move to the module itself, VDD and VEE are connected to +5V supply, and VSS is connected to ground.
  • Step 11: Connect PB0, PB1, and PB2 of 8255 PPI to RS, RW, and E of the LCD module, respectively.
  • Step 12: All the data pins from PA0 – PA7 of 8255 to D0 – D7 of the LCD module.

Circuit diagram to interface LCD module using 8255 ppi with 8051

Code to interface LCD module (16×2) to 8051 using 8255 PPI

Here, chip select logic of the 8255 PPI is in such a way that the Port A is addressed at 4000H and port B is addressed at 4001H.

#include< regx51.h >

char xdata PORTA _at_ 0x4000; //by making PORTA with address 4000H we are selecting Port A of the 8255
// and the CS low (select the 8255)
char xdata PORTB _at_ 0x4001; //by making PORTA with address 4001H we are selecting Port B of the 8255
// and the CS low (select the 8255)
sbit A0=P1^0;
sbit A1=P1^1;
sbit wr=P3^6;
sbit rd=P3^7;

void send_command(unsigned char i);
void send_data(unsigned char i);
void delay();

void main(void)
{
    unsigned int i=0;
    unsigned char line_1[] = "TECHNOBYTE"; 
    unsigned char line_2[] = "MICROCONTROLLER";

    P2 = 0x00; // To make port 2 as an output port 
    P0 = 0x00; // To make port 0 as an output port
    rd=1; // Disabling read from     
    wr=0; // Enabling write to

    send_command(0x38); // Initialize LCD in 16x2 matrix mode
    send_command(0x0E); // Display ON and cursor ON
    send_command(0x01); // Clear the LCD
    send_command(0x83); // Select first line and position of the cursor to 83H + position 

    // Display the dat on LCD's first line
    for (i = 0; i < 11; i++) 
    { 
        send_data(line_1[i]); 
    }
    
    rd=1; // Disabling read from 
    wr=0; // Enabling write to 

    send_command(0xC0); // Select the second line 

    // Display the dat on LCD's second line
    for (i = 0; i < 16; i++)
    { 
        send_data(line_2[i]); 
    } 

    while(1); //Loop forever to display the dat until the power is switched OFF
}

//Function to send command
void send_command(unsigned char i)
{ 
    PORTA = i; //put data into 4000H

    // Port B is connected to the command pins i.e. RS, R/W, and E 
    PORTB = 0x04; //4001H = 04H (00000100B) implies E = 1, RW = 0 (Write to LCD), RS = 0 (Command to be wriiten)
    delay();

    PORTB = 0x00; //4001H = 00H (00000000B) implies E = 0 (High to low pulse), RW = 0 (Write to LCD), RS = 0 (Command to be wriiten)
    delay();
    return;
}

//Function to send dat
void send_data (unsigned char i) 
{
    PORTA = i; //put data into 4000H

    // Port B is connected to the command pins i.e. RS, R/W, and E 
    PORTB = 0x05; // 4001H = 05H (00000101B) implies E = 1, RW = 0 (Write to LCD), RS = 1 (Data to be wriiten to LCD)
    delay();

    PORTB = 0x01; // 4001H = 01H (0000 0001B) implies E = 0 (High to low pulse), RW = 0 (Write to LCD), RS = 1 (Data to be wriiten to LCD)
    delay();
    return;
}

// Function to generate delay
void delay() 
{
    unsigned int i =0;
    for (i = 0; i < 100; i++);
}

The key takeaways from this elaborate tutorial are,

  • We understood how an LCD module works in both 4-bit and 8-bit mode. And also how to develop the coding logic from scratch, and finally how to interface it with 8051 microcontrollers.
  • We also learned how to interface the LCD module using an 8255 PPI.

2 thoughts on “LCD interfacing with 8051 – 8-bit, 4-bit mode, and with 8255 PPI

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.