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.
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.
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.
The last important function is one to clear the entire display. This function sends out a single "clear screen" command.
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
}