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_BFvoid 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}

