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
}