LCD Interfacing

In this tutorial, the task of interfacing a dot matrix LCD (HD44780 controlled) will be covered. The Hitachi HD44780 LCD controller is one of the most common LCD display controllers available. Hitachi developed it specifically to drive alphanumeric LCD display with a simple interface that could be connected to a general purpose microcontroller or microprocessor. The device can display ASCII characters, Japanese Kana characters, and some symbols in two 28 character lines.

Common sizes for these LCDs are 8 × 1 (one row of eight characters), 16 × 2, 20 × 2 and 20 × 4. It generally comes with a green or blue LED backlight.

The Hitachi HD44780 LCD controller datasheet can be found here

The pinout of a char LCD is as follows - 

    1. Ground
    2. VCC (+3.3 to +5V)
    3. Contrast adjustment (VO)
    4. Register Select (RS). RS=0: Command, RS=1: Data
    5. Read/Write (R/W). R/W=0: Write, R/W=1: Read
    6. Clock (Enable). Falling edge triggered
    7. Bit 0 (Not used in 4-bit operation)
    8. Bit 1 (Not used in 4-bit operation)
    9. Bit 2 (Not used in 4-bit operation)
    10. Bit 3 (Not used in 4-bit operation)
    11. Bit 4
    12. Bit 5
    13. Bit 6
    14. Bit 7
    15. Backlight Anode (+)
    16. Backlight Cathode (-)

The nominal operating voltage for LED backlights is 5V at full brightness, with dimming at lower voltages dependent on the details such as LED color.

The HD44780 features 3 control lines and either 4 or 8 data lines. The RS (Register Select), Read/Write (R/W) and the Enable (E) lines constitute he 3 control lines (PIN4 - PIN6).

The microcontroller used here for the interfacing is the ATMEL ATMega32, a cheap, robust and a very readily available microcontroller in the market.

PORTC (PC0-PC7 pins) of the ATMega32 are connected to the Data Lines (D0-D7) of the LCD. Pins PA5, PA6 and PA7 of PORTA are connected to the Enable, Read/Write and Register Select lines respectively. The Ground, the VCC and the backlight pins are connected accordingly. A 10K potentiometer is connected to the Contrast adjustment line.

To communicate effectively with the LCD, the functions of the control lines should be carefully understood.

The Enable control line is used to send data to the LCD. The microcontroller should make the Enable line initially low (0), set the data lines on PORTC and set the RS and RW accordingly. Finally, the Enable line should be pulled high (1) and the microcontroller should wait for a certain amount of time (as specified in the LCD datasheet) before pulling it down to low (0) to complete the data transfer.

The RS control line helps the LCD differentiate between the data on the data lines. When RS is low (0), the data on the data lines is read as an instruction and when RS is high (1), the data is read as a character.

The RW control line is used to read from the LCD when the line is set to high (1), and is used to write to the LCD when the line is set to low (0). Data is almost always written to the LCD and very seldom read from it. So, the RW control line is mostly set to low (0).

The general steps that are to followed to send data or instructions are -

  • Select the character or instruction register by setting the RS control line.
  • Set the Enable control line low (0).
  • Set the data on the data lines.
  • Send the data by setting the Enable line high (1).
  • Wait for a certain amount of time for the data transfer to complete.
  • Pull the Enable line low (0) to complete the data transfer.

To start communicating with the LCD, the microcontroller should first configure the LCD -

  • Reset the LCD (0x03).
  • Set data length as 1 and the number of display lines to 2 (0x38).
  • Enable the LCD (0x0c).
  • Clear the LCD (0x01).
  • Set entry mode: Increment cursor by 1 after every read/write (0x06).
  • Cursor shift (0x14).
  • Set the cursor to home (0x02).

The code for the LCD driver is given below alongwith functions to display strings and integers on the LCD. This code has been adapted for the XMega by Frank Van Hooft in his blog. Carlos E Marciales also put up an example of how to use the driver routines in a project. You can find it here.

To write a constant string from the program memory, the PSTR macro should be used as shown below - 

lcd_write_string(PSTR("Hello World!"));

C Code for the LCD Driver

// LCD.c for a 20×4 char LCD interfaced with the ATMEL ATMega32 MCU
#include <stdio.h>
#include <inttypes.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#define F_CPU 11059200
#include <util/delay.h>
#define D0 PC0
#define D1 PC1
#define D2 PC2
#define D3 PC3
#define D4 PC4
#define D5 PC5
#define D6 PC6
#define D7 PC7
#define RS PA7
#define RW PA6 // Will be 0 most of the time since we will be writing
#define E PA5
void lcd_set_write_instruction()
    COMM_PORT &= ~(1<<RS);
void lcd_set_write_data()
    COMM_PORT |= (1<<RS);
void lcd_write_byte(char c)
    DATA_PORT = c;
    // E high
    COMM_PORT |= (1<<E);
    COMM_PORT &= ~(1<<E);      
void lcd_clear_and_home()
void lcd_home()
void lcd_write_data(char c)
void lcd_write_string(const char *x)
    while (pgm_read_byte(x) != 0x00)
void lcd_write_int16(int16_t in)
    uint8_t started = 0;
    uint16_t pow = 10000;
    while (pow >= 1)
        if (in/pow > 0 || started || pow == 1)
            lcd_write_data((uint8_t) (in/pow) + '0');
            started = 1;
            in = in % pow;
        pow = pow / 10;
void lcd_goto(uint8_t row, uint8_t col)
    uint8_t position = 0;
        case 0: position = 0;
        case 1: position = 0x40;
        case 2: position = 20;
        case 3: position = 0x40 + 20;
    lcd_write_byte(0x80 | (position + col));
void lcd_line_one()     {   lcd_goto(0, 0); }
void lcd_line_two()     {   lcd_goto(1, 0); }
void lcd_line_three()   {   lcd_goto(2, 0); }
void lcd_line_four()    {   lcd_goto(3, 0); }  
void lcd_init()
    MCUCSR |= (1<<7);   // Setting the JTD bit in MCU control and status register 1
    MCUCSR |= (1<<7);   // which disables the JTAG on pins PC2-PC5
    DDRC |= 0xFF;
    DDRA |= ((1<<RS) | (1<<RW) | (1<<E));
    PORTA &= ~(1<<RW);
    // Resetting the LCD
    lcd_write_byte(0x38);   // Set Data length as 1 (DL bit set) and number of display lines to 2 (N bit set)
    lcd_write_byte(0x0c);   // Enable LCD (D bit set)
    lcd_write_byte(0x01);   // Clear the LCD display
    lcd_write_byte(0x06);   // Set entry mode: Increment cursor by 1 after data read/write (I/D bit set)
    lcd_write_byte(0x14);   // Cursor shift
    lcd_clear_and_home();   // LCD cleared and cursor is brought at the beginning