Interfacing LCD16x2 with AVR ATmega16/ATmega32 in 4-bit mode

LCD16x2 can be interfaced by using 4 data lines, which saves GPIO pins.

109.2k views5 min readUpdated Aug 9, 2023

Overview of LCD16x2

LCDs (Liquid Crystal Displays) are used for displaying status or parameters in embedded systems.

LCD 16x2 is a 16 pin device which has 8 data pins (D0-D7) and 3 control pins (RS, RW, EN). The remaining 5 pins are for supply and backlight for the LCD.

The control pins help us configure the LCD in command mode or data mode. They also help configure read mode or write mode and also when to read or write.

LCD 16x2 can be used in 4-bit mode or 8-bit mode depending on the requirement of the application. In order to use it, we need to send certain commands to the LCD in command mode and once the LCD is configured according to our need, we can send the required data in data mode.

For more information about LCD 16x2 and how to use it, refer to the topic LCD 16x2 display module in the sensors and modules section.

 

LCD16x2 Pinout

LCD 16x2
LCD 16x2 Pin Diagram

 

LCD16x2 4-bit Mode 

  • In 4-bit mode, data/command is sent in a 4-bit (nibble) format.
  • To do this 1st send a Higher 4-bit and then send a lower 4-bit of data/command.
  • Only 4 data (D4 - D7) pins of 16x2 of LCD are connected to the microcontroller and other control pins RS (Register select), RW (Read/write), E (Enable) is connected to other GPIO Pins of the controller.

Therefore, due to such connections, we can save four GPIO pins which can be used for another application.

 

Connection Diagram of LCD16x2 with ATmega16/32

ATmega_4bit LCD16x2 interface
Interfacing LCD 16x2 With AVR ATmega16/ATmega32

 

Programming LCD16x2 4-bit mode with AVR ATmega16/Atmega32

Initialization

  1. Wait for 15ms, Power-on initialization time for LCD16x2.
  2. Send 0x02 command which initializes LCD 16x2 in 4-bit mode.
  3. Send 0x28 command which configures LCD in 2-line, 4-bit mode, and 5x8 dots.
  4. Send any Display ON command (0x0E, 0x0C)
  5. Send 0x06 command (increment cursor)
void LCD_Init (void)  /* LCD Initialize function */
{
	LCD_Dir = 0xFF;		/* Make LCD port direction as o/p */
	_delay_ms(20);		/* LCD Power ON delay always >15ms */
	
	LCD_Command(0x33);
	LCD_Command(0x32);	/* Send for 4 bit initialization of LCD  */
	LCD_Command(0x28);	/* 2 line, 5*7 matrix in 4-bit mode */
	LCD_Command(0x0c);	/* Display on cursor off */
	LCD_Command(0x06);	/* Increment cursor (shift cursor to right) */
	LCD_Command(0x01);	/* Clear display screen */
}

 

Now we successfully initialized LCD & it is ready to accept data in 4-bit mode to display.

To send command/data to 16x2 LCD we have to send higher nibble followed by lower nibble. As 16x2 LCD’s D4 - D7 pins are connected as data pins, we have to shift the lower nibble to the right by 4 before transmitting.

 

Command write function

  1. First, send a Higher nibble of command.
  2. Make RS pin low, RS=0 (command reg.)
  3. Make RW pin low, RW=0 (write operation) or connect it to ground.
  4. Give High to Low pulse at Enable (E).
  5. Send lower nibble of command.
  6. Give High to Low pulse at Enable (E).
void LCD_Command( unsigned char cmnd )
{
	LCD_Port = (LCD_Port & 0x0F) | (cmnd & 0xF0);/* Sending upper nibble */
	LCD_Port &= ~ (1<<RS);		/* RS=0, command reg. */
	LCD_Port |= (1<<EN);		/* Enable pulse */
	_delay_us(1);
	LCD_Port &= ~ (1<<EN);
	_delay_us(200);
	LCD_Port = (LCD_Port & 0x0F) | (cmnd << 4);/* Sending lower nibble */
	LCD_Port |= (1<<EN);
	_delay_us(1);
	LCD_Port &= ~ (1<<EN);
	_delay_ms(2);
}

 

Data write function

  1. First, send a Higher nibble of data.
  2. Make RS pin high, RS=1 (data reg.)
  3. Make RW pin low, RW=0 (write operation) or connect it to ground.
  4. Give High to Low pulse at Enable (E).
  5. Send lower nibble of data.
  6. Give High to Low pulse at Enable (E).
void LCD_Char( unsigned char data )
{
	LCD_Port = (LCD_Port & 0x0F) | (data & 0xF0);/* Sending upper nibble */
	LCD_Port |= (1<<RS);  /* RS=1, data reg. */
	LCD_Port|= (1<<EN);
	_delay_us(1);
	LCD_Port &= ~ (1<<EN);
	_delay_us(200);
	LCD_Port = (LCD_Port & 0x0F) | (data << 4);  /* Sending lower nibble */
	LCD_Port |= (1<<EN);
	_delay_us(1);
	LCD_Port &= ~ (1<<EN);
	_delay_ms(2);
}

 

LCD16x2 4 bit Mode Code for ATmega16/32

/*  
   LCD16x2 4 bit ATmega16 interface
   http://www.electronicwings.com
*/

#define F_CPU 8000000UL			/* Define CPU Frequency e.g. here 8MHz */
#include <avr/io.h>			/* Include AVR std. library file */
#include <util/delay.h>			/* Include Delay header file */

#define LCD_Dir  DDRB			/* Define LCD data port direction */
#define LCD_Port PORTB			/* Define LCD data port */
#define RS PB0				/* Define Register Select pin */
#define EN PB1 				/* Define Enable signal pin */

void LCD_Command( unsigned char cmnd )
{
	LCD_Port = (LCD_Port & 0x0F) | (cmnd & 0xF0); /* sending upper nibble */
	LCD_Port &= ~ (1<<RS);		/* RS=0, command reg. */
	LCD_Port |= (1<<EN);		/* Enable pulse */
	_delay_us(1);
	LCD_Port &= ~ (1<<EN);

	_delay_us(200);

	LCD_Port = (LCD_Port & 0x0F) | (cmnd << 4);  /* sending lower nibble */
	LCD_Port |= (1<<EN);
	_delay_us(1);
	LCD_Port &= ~ (1<<EN);
	_delay_ms(2);
}

void LCD_Char( unsigned char data )
{
	LCD_Port = (LCD_Port & 0x0F) | (data & 0xF0); /* sending upper nibble */
	LCD_Port |= (1<<RS);		/* RS=1, data reg. */
	LCD_Port|= (1<<EN);
	_delay_us(1);
	LCD_Port &= ~ (1<<EN);

	_delay_us(200);

	LCD_Port = (LCD_Port & 0x0F) | (data << 4); /* sending lower nibble */
	LCD_Port |= (1<<EN);
	_delay_us(1);
	LCD_Port &= ~ (1<<EN);
	_delay_ms(2);
}

void LCD_Init (void)			/* LCD Initialize function */
{
	LCD_Dir = 0xFF;			/* Make LCD port direction as o/p */
	_delay_ms(20);			/* LCD Power ON delay always >15ms */
	
	LCD_Command(0x02);		/* send for 4 bit initialization of LCD  */
	LCD_Command(0x28);              /* 2 line, 5*7 matrix in 4-bit mode */
	LCD_Command(0x0c);              /* Display on cursor off*/
	LCD_Command(0x06);              /* Increment cursor (shift cursor to right)*/
	LCD_Command(0x01);              /* Clear display screen*/
	_delay_ms(2);
}

void LCD_String (char *str)		/* Send string to LCD function */
{
	int i;
	for(i=0;str[i]!=0;i++)		/* Send each char of string till the NULL */
	{
		LCD_Char (str[i]);
	}
}

void LCD_String_xy (char row, char pos, char *str)	/* Send string to LCD with xy position */
{
	if (row == 0 && pos<16)
	LCD_Command((pos & 0x0F)|0x80);	/* Command of first row and required position<16 */
	else if (row == 1 && pos<16)
	LCD_Command((pos & 0x0F)|0xC0);	/* Command of first row and required position<16 */
	LCD_String(str);		/* Call LCD string function */
}

void LCD_Clear()
{
	LCD_Command (0x01);		/* Clear display */
	_delay_ms(2);
	LCD_Command (0x80);		/* Cursor at home position */
}
 
int main()
{
	LCD_Init();			/* Initialization of LCD*/

	LCD_String("ElectronicWINGS");	/* Write string on 1st line of LCD*/
	LCD_Command(0xC0);		/* Go to 2nd line*/
	LCD_String("Hello World");	/* Write string on 2nd line*/
	while(1);
}

Components Used

Powered byMouser Electronics

Downloads

Comments18

Join the discussion — share a question or tip.

  • OT
    otgxotg

    This code is not working properly on my device LCD showing only 1 box dot. Please suggest me something

    • OT
      otgxotg

      I have taken RS as PB3 and EN PB2 And data pins from PC4 to PC7 and all the necessary changes have been done still not getting correct output on LCD

  • MO
    mostafamehdipour

    Hi how define D4 to D7 PIN from PORTB ?

  • AL
    alekseybazilevich

    Hi. I connected 20x4 LCD instead of 16x4. And apply the source code from this article, but I can’t figure out how to connect the third and fourth lines. What part I need to change or maybe add some where in this code to make it possible to work with the third and fourth line?

    • LO
      lokeshc

      It is simple refer below link which has 20x4 LCD library https://www.electronicwings.com/avr-atmega/gps-module-interfacing-with-atmega1632 It will help you to clear your doubts

  • EN
    enthusiasticgeek· edited

    Hi What is the license of your source code? I have modified your source code to work on ATMEGA2560 . I have added floating point and int display functions that I designed. I have given you credit to your URL but I couldn't find license . All my source code is released under MIT license on this repo. https://github.com/enthusiasticgeek/Elegoo_Mega_2560/blob/master/LCD16x2_4bit.c If you have license I will gladly add it. Thanks.

    • LO
      lokeshc

      it seems open source. So no need to add any license or you can add open source license to it.

    • EN
      enthusiasticgeek

      Replying to @lokeshc

      ok. Thanks.

  • SR
    sridhar

    if i have increased delay in LCD initialise step, it will work .....?

    • LO
      lokeshc

      yes. it will.

    • SR
      sridhar

      Replying to @lokeshc

      i didn't made any changes, the same code is working in Atmega16 (16MHZ) board.

    • KA
      KavanDave

      Replying to @sridhar

      Can you tell me the solution? I am facing the same issue. lower one is empty and upper one is getting black boxes

    • DA
      davidwongcy

      Replying to @KavanDave

      It's most probably due to your Vcc not meeting the LCD controller's requirement. It must reach 4.5v in 10ms for 5v operation, or 2.7v in 10ms for 3v operation (see Hitachi HD44780U datasheet page 59). If this condition is not met, the internal reset circuit will not work, and you'll need to perform a manual init by sending 4 init nibbles at specific intervals (datasheet page 46): Power on -&gt; wait at least 15ms -&gt; send 0x3 -&gt; wait at least 4.1ms -&gt; send 0x3 -&gt; wait at least 100us -&gt; send 0x3 -&gt; send 0x2 -&gt; follow by the rest of the initialization. Since Vcc rise time varies across hardware and is hard to guarantee, it is best to perform the manual init anyway.

    • DA
      davidwongcy

      Replying to @davidwongcy

      The modified code is as follow: void LCD_nibble_cmd(unsigned char nibble) { LCD_Port = (LCD_Port &amp; 0x0F) | (nibble &lt;&lt; 4); /* send nibble */ LCD_Port &amp;= ~ (1&lt;&lt;RS); /* RS=0, command reg. */ LCD_Port|= (1&lt;&lt;EN); _delay_us(1); LCD_Port &amp;= ~ (1&lt;&lt;EN); } void LCD_Init (void) /* LCD Initialize function */ { LCD_Dir = 0xFF; /* Make LCD port direction as o/p */ _delay_ms(20); /* LCD Power ON delay min 15ms */ LCD_nibble_cmd(3); /* manual 4 bit initialization of LCD */ _delay_ms(5); /* min 4.1ms */ LCD_nibble_cmd(3); _delay_ms(1); /* min 100us */ LCD_nibble_cmd(3); LCD_nibble_cmd(2); /* this is no longer needed LCD_Command(0x02); /* send for 4 bit initialization of LCD */ */ LCD_Command(0x28); /* 2 line, 5*7 matrix in 4-bit mode */ LCD_Command(0x0c); /* Display on cursor off*/ LCD_Command(0x06); /* Increment cursor (shift cursor to right)*/ LCD_Command(0x01); /* Clear display screen*/ _delay_ms(2); }

  • SR
    sridhar

    hi..I hooked up a 16x2 LCD with Atmega16 (16MHZ) and made sure all the connections were according to the program .My contrast is adjusted perfectly but the problem is that there are black boxes on the top line while the lower one is empty. can you help me. what could be a problem....?

    • LO
      lokeshc

      which means lcd not initialized properly. In the above program, you are using 16 mhz so there may be a chance that your delay will not be accurate.