LCD Display

An HD44780-type display can perform a number of functions, but we will just focus on the two most important functions we need, which is (a) init the display, and (b) display a line of text on the display.  The HD44780 datasheet gives the required initialization sequence for both 4-bit and 8-bit mode (remember, we are using 4-bit), as well as the commands to write a character to a specific X-Y location on the screen.

LCD INITIALIZATION

The HD44780 datasheet gives a sequence of instructions called "Initialization by Instruction".  This sequence is a universal initialization sequence, which will work no matter what state the display is in.  It consists of 8 commands sent to the display, with specified delays between commands.  There is a futher complication that some of the commands are sent as 8-bit commands, and some as 4-bit commands.  I won't list the commands here since you can see them in the software listing to follow, in the function lcd_init().

There is one serious omission in all the datasheets I've seen.  The Clear display function (0x01) does not indicate how long it takes to execute.  Don't assume (as I did) that it is one of those 37us commands.  It actually seems to be one of the 1.52ms commands.  Some displays work without the long delay, but others don't.  I speak from recent debugging session experience.

LCD PUT STRING

This function sets the display address according to the desired X-Y coordinates of the string, and then writes the string characters into the display memory.

LCD CLEAR DISPLAY

The last important function is one to clear the entire display.  This function sends out a single "clear screen" command.

THE LCD SOURCE CODE - AVR VERSION

Here is all the code to get the display running in 4-bit mode.  The two delay functions are included in this listing even thought they might be in another file in a typical project.

// lcd.c
// AVR version
// 4-bit interface
 
#define LCD_USE_BF    // define to use busy flag
 
#define MS_COUNT (F_CPU/14003)  // depends on clock speed, toolchain and settings
 
void nano_delay(void)
{
}
 
void tiny_delay(volatile u8 d)
{
  while (--d != 0)
    ;
}
 
void ms_delay(u16 d)
{
  while (d-- != 0)
  {
    volatile u16 i = MS_COUNT;
    while (i-- != 0)
      ;
  }
}
 
// LCD is Port A
#define LCD_PORT      PORTA
#define LCD_DD_PORT   DDRA
#define LCD_IN_PORT   PINA
 
#define LCD_D4       _BV(PA4)
#define LCD_D5       _BV(PA5)
#define LCD_D6       _BV(PA6)
#define LCD_D7       _BV(PA7)
#define LCD_RS       _BV(PA1) // 0=CMD, 1=DATA
#define LCD_RW       _BV(PA2) // 0=WR, 1=RD
#define LCD_E        _BV(PA3)
#define LCD_BL       _BV(PA0)
 
#define LCD_CTRL    (LCD_RS | LCD_RW | LCD_E)
#define LCD_DATA    (LCD_D4 | LCD_D5 | LCD_D6 | LCD_D7)
#define LCD_BLITE   (LCD_BL)
#define LCD_BF      (LCD_D7)
 
#define DISP_INIT     0x30
#define DISP_4BITS    0x20
#define DISP_ON       0x0c
#define DISP_OFF      0x08
#define DISP_CLR      0x01
#define CUR_HOME      0x02
#define DISP_EMS      0x06
#define DISP_CONFIG   0x28
 
#define DD_RAM_ADDR   0x00
#define DD_RAM_ADDR2  0x40
#define DD_RAM_ADDR3  (DD_RAM_ADDR+0x14)
#define DD_RAM_ADDR4  (DD_RAM_ADDR2+0x14)
#define CG_RAM_ADDR   0x40
 
#define LCD_LINES 4
#define LCD_WIDTH 20
 
 
void lcd_strobe(void)
{
  LCD_PORT |= LCD_E;   // start E pulse
  nano_delay();        //tiny_delay(LCD_STROBE);
  LCD_PORT &= ~LCD_E;  // end E pulse (must be >= 230ns)
  nano_delay();        //tiny_delay(LCD_STROBE);
}
 
void LCD_PORT_data(u8 d)
{
  // write upper 4 bits of data to LCD data lines
  LCD_PORT = (LCD_PORT & ~LCD_DATA) | (d & 0xf0);
}
 
#ifdef LCD_USE_BF
void lcd_wait(void)
{
  u8 data;
  
  LCD_DD_PORT &= ~LCD_DATA;         // all data lines to input
  LCD_PORT &= ~LCD_RS;              // cmd
  LCD_PORT |= LCD_RW;               // set to read
  
  do 
  {
    LCD_PORT |= LCD_E;              // 1st strobe, read BF
    nano_delay();       //tiny_delay(LCD_STROBE);
    data = LCD_IN_PORT;
    LCD_PORT &= ~LCD_E;
    nano_delay();       //tiny_delay(LCD_STROBE);
    LCD_PORT |= LCD_E;              // 2nd strobe, don't read data
    nano_delay();       //tiny_delay(LCD_STROBE);
    LCD_PORT &= ~LCD_E;
    nano_delay();       //tiny_delay(LCD_STROBE);
  } while (data & LCD_BF);          // loop while BF is set
 
  LCD_PORT &= ~LCD_RW;              // set to write
  LCD_DD_PORT |= LCD_DATA;          // all data lines to ouput
}
#endif
 
void lcd_send_cmd(u8 cmd)
{
#ifdef LCD_USE_BF
  lcd_wait();
#endif
  LCD_PORT &= ~LCD_RS;
  LCD_PORT_data(cmd & 0xf0);        // send hi 4 bits of cmd
  lcd_strobe();
  LCD_PORT_data((cmd & 0xf) << 4);  // send lo 4 bits of cmd
  lcd_strobe();
#ifndef LCD_USE_BF
  tiny_delay(90);
#endif
}
 
void lcd_putc(u8 c)
{
#ifdef LCD_USE_BF
  lcd_wait();
#endif
  LCD_PORT |= LCD_RS;
  LCD_PORT_data(c & 0xf0);         // send hi 4 bits of data
  lcd_strobe();
  LCD_PORT_data((c << 4) & 0xf0);  // send lo 4 bits of data
  lcd_strobe();
#ifndef LCD_USE_BF
  tiny_delay(90);
#endif
}
 
void lcd_init(void)
{
  LCD_DD_PORT = LCD_CTRL | LCD_DATA | LCD_BLITE;
  LCD_PORT &= ~LCD_RW;          // set to write, permanently
  LCD_PORT &= ~LCD_RS;
  LCD_PORT &= ~LCD_E;
 
  ms_delay(15);                 // must be >= 15
  LCD_PORT_data(DISP_INIT);
  lcd_strobe();                 // pseudo 8-bit command
  ms_delay(5);                  // must be >= 4.1
  LCD_PORT_data(DISP_INIT);
  lcd_strobe();                 // pseudo 8-bit command
  ms_delay(1);                  // must be >= 100us
  LCD_PORT_data(DISP_INIT);
  lcd_strobe();                 // pseudo 8-bit command
  ms_delay(1);
 
  LCD_PORT_data(DISP_4BITS);
  lcd_strobe();                 // pseudo 8-bit command
  ms_delay(1);
 
  lcd_send_cmd(DISP_CONFIG);
  lcd_send_cmd(DISP_OFF);
  lcd_send_cmd(DISP_CLR);
  ms_delay(2);  // undocumented but required delay for Clear display command
  lcd_send_cmd(DISP_EMS);
  lcd_send_cmd(DISP_ON);
  lcd_set_backlight(1);
}
 
void lcd_clear(void)
{
  lcd_send_cmd(DISP_CLR);
  ms_delay(2);  // undocumented but required delay for Clear display command
}
 
void lcd_display(int x, int y, const char *str)
{
  int n = LCD_WIDTH - x;
  u8 addr;
 
  if ((y < 0) || (y >= LCD_LINES))
    return;
 
  switch (y)
  {
  default:
  case 0:
    addr = DD_RAM_ADDR;
    break;
  case 1:
    addr = DD_RAM_ADDR2;
    break;
  case 2:
    addr = DD_RAM_ADDR3;
    break;
  case 3:
    addr = DD_RAM_ADDR4;
    break;
  }
  lcd_send_cmd(addr + x + 0x80);
  while (*str && n--)
    lcd_putc(*str++);
}
 
void lcd_set_backlight(u8 on)
{
  if (on)
      LCD_PORT |= LCD_BL;       // turn on backlight
  else
      LCD_PORT &= ~LCD_BL;      // turn off backlight
}