Michael Cartwright
Published © LGPL

HD44780 LCD Driver: 20x4 and 16x2 - parallel (8 or 4 bit)

A clean implementation should have been trivial with the existing information on the web, but it wasn't. Feel free to cut-and-paste..

IntermediateFull instructions provided2 hours2,560
HD44780 LCD Driver: 20x4 and 16x2 - parallel (8 or 4 bit)

Things used in this project

Hardware components

Arduino UNO
Arduino UNO
×1
HD44780 LCD (20x4)
×1
Single Turn Potentiometer- 10k ohms
Single Turn Potentiometer- 10k ohms
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Schematics

Wiring for Arduino Uno

Pins for LCD

These have been the same 16 pins for all models I've encountered. My 20x4 had no labels, just numbers 1..16 so this is handy to have around.

Hitachi Data Sheet for HF44780U

Source material for this work.

Code

HD4478U LCD Parallel Driver (4 or 8 bit)

Arduino
//#define SERIAL_DIAGNOSTICS
#define LCD_8_BITS

#define REGISTER_SELECT_PIN 10      // device pin 4  0-Command, 1-Data
#define READ_WRITE_PIN 11           // device pin 5  0-Write, 1-Read
#define ENABLE_PIN 12               // device pin 6

#ifdef LCD_8_BITS
#define DATA_0 2
#define DATA_1 3
#define DATA_2 4
#define DATA_3 5
#endif
#define DATA_4 6
#define DATA_5 7
#define DATA_6 8
#define DATA_7 9

byte lcdColumns;
byte lcdRows;
byte currentCol;
byte currentRow;
byte lcdRowStart[4]; // see LCDInstruction(byte data)

// I referenced this for ideas:
// https://github.com/arduino-libraries/LiquidCrystal
// but got rid of all the delays's (except Hitachi's startup madness) 
// and read the LCD "Busy" flag instead

void LCDDataAsOutput()
{
#ifdef LCD_8_BITS
  pinMode(DATA_0, OUTPUT);
  pinMode(DATA_1, OUTPUT);
  pinMode(DATA_2, OUTPUT);
  pinMode(DATA_3, OUTPUT);
#endif
  pinMode(DATA_4, OUTPUT);
  pinMode(DATA_5, OUTPUT);
  pinMode(DATA_6, OUTPUT);
  pinMode(DATA_7, OUTPUT);
}
void LCDDataAsInput()
{
#ifdef LCD_8_BITS
  pinMode(DATA_0, INPUT);
  pinMode(DATA_1, INPUT);
  pinMode(DATA_2, INPUT);
  pinMode(DATA_3, INPUT);
#endif
  pinMode(DATA_4, INPUT);
  pinMode(DATA_5, INPUT);
  pinMode(DATA_6, INPUT);
  pinMode(DATA_7, INPUT);
}

#ifdef LCD_8_BITS 
void LCDWriteData8(byte data)
{
  digitalWrite(DATA_0, ((data & 0b00000001) != 0) ? HIGH : LOW);
  digitalWrite(DATA_1, ((data & 0b00000010) != 0) ? HIGH : LOW);
  digitalWrite(DATA_2, ((data & 0b00000100) != 0) ? HIGH : LOW);
  digitalWrite(DATA_3, ((data & 0b00001000) != 0) ? HIGH : LOW);
  digitalWrite(DATA_4, ((data & 0b00010000) != 0) ? HIGH : LOW);
  digitalWrite(DATA_5, ((data & 0b00100000) != 0) ? HIGH : LOW);
  digitalWrite(DATA_6, ((data & 0b01000000) != 0) ? HIGH : LOW);
  digitalWrite(DATA_7, ((data & 0b10000000) != 0) ? HIGH : LOW);
}
#else
void LCDWriteData4(byte data)
{
  digitalWrite(DATA_4, ((data & 0b00000001) != 0) ? HIGH : LOW);
  digitalWrite(DATA_5, ((data & 0b00000010) != 0) ? HIGH : LOW);
  digitalWrite(DATA_6, ((data & 0b00000100) != 0) ? HIGH : LOW);
  digitalWrite(DATA_7, ((data & 0b00001000) != 0) ? HIGH : LOW);
}
#endif
void LCDWriteData(byte data)
{
#ifdef LCD_8_BITS  
  LCDWriteData8(data);
  // pulse ENABLE_PIN
  digitalWrite(ENABLE_PIN, LOW);
  digitalWrite(ENABLE_PIN, HIGH);
  digitalWrite(ENABLE_PIN, LOW);
#else
  LCDWriteData4(data >> 4);
  // pulse ENABLE_PIN
  digitalWrite(ENABLE_PIN, LOW);
  digitalWrite(ENABLE_PIN, HIGH);
  digitalWrite(ENABLE_PIN, LOW);
  LCDWriteData4(data &0x0F);
  // pulse ENABLE_PIN
  digitalWrite(ENABLE_PIN, LOW);
  digitalWrite(ENABLE_PIN, HIGH);
  digitalWrite(ENABLE_PIN, LOW);
#endif  
}
#ifdef LCD_8_BITS  
byte LCDReadData8()
{
  byte data = 0;
  data |= ((digitalRead(DATA_0) == HIGH) ? 0b00000001 : 0b00000000);
  data |= ((digitalRead(DATA_1) == HIGH) ? 0b00000010 : 0b00000000);
  data |= ((digitalRead(DATA_2) == HIGH) ? 0b00000100 : 0b00000000);
  data |= ((digitalRead(DATA_3) == HIGH) ? 0b00001000 : 0b00000000);
  data |= ((digitalRead(DATA_4) == HIGH) ? 0b00010000 : 0b00000000);
  data |= ((digitalRead(DATA_5) == HIGH) ? 0b00100000 : 0b00000000);
  data |= ((digitalRead(DATA_6) == HIGH) ? 0b01000000 : 0b00000000);
  data |= ((digitalRead(DATA_7) == HIGH) ? 0b10000000 : 0b00000000);
  return data;
}
#else
byte LCDReadData4()
{
  byte data = 0;
  data |= ((digitalRead(DATA_4) == HIGH) ? 0b00000001 : 0b00000000);
  data |= ((digitalRead(DATA_5) == HIGH) ? 0b00000010 : 0b00000000);
  data |= ((digitalRead(DATA_6) == HIGH) ? 0b00000100 : 0b00000000);
  data |= ((digitalRead(DATA_7) == HIGH) ? 0b00001000 : 0b00000000);
  return data;
}
#endif
byte LCDReadData()
{
  byte data = 0;

#ifdef LCD_8_BITS  
  // pulse ENABLE_PIN
  digitalWrite(ENABLE_PIN, LOW);
  digitalWrite(ENABLE_PIN, HIGH);
  data = LCDReadData8();
  digitalWrite(ENABLE_PIN, LOW);
#else
  // pulse ENABLE_PIN
  digitalWrite(ENABLE_PIN, LOW);
  digitalWrite(ENABLE_PIN, HIGH);
  data = LCDReadData4() << 4;
  digitalWrite(ENABLE_PIN, LOW);
  // pulse ENABLE_PIN
  digitalWrite(ENABLE_PIN, LOW);
  digitalWrite(ENABLE_PIN, HIGH);
  data = data | LCDReadData4();
  digitalWrite(ENABLE_PIN, LOW);
#endif
  return data;
}

void LCDWait()
{
  LCDDataAsInput();
  digitalWrite(REGISTER_SELECT_PIN, LOW);
  digitalWrite(READ_WRITE_PIN, HIGH);
  for (;;)
  {
    byte data = LCDReadData();
#ifdef SERIAL_DIAGNOSTICS    
    char buffer[20];
    sprintf(buffer, "Busy 0x%02X", data);
    Serial.println(buffer);
#endif
    if ((data & 0x80) == 0) // busy?
    {
      break;
    }
  }
  LCDDataAsOutput();
}
byte LCDGetCurrentAddress()
{
  LCDWait();
  LCDDataAsInput();
  digitalWrite(REGISTER_SELECT_PIN, LOW);
  digitalWrite(READ_WRITE_PIN, HIGH);
  byte data = LCDReadData();
  LCDDataAsOutput();
  return data;
}
void LCDInstruction(byte data)
{
  LCDWait();
  digitalWrite(REGISTER_SELECT_PIN, LOW);
  digitalWrite(READ_WRITE_PIN, LOW);
  LCDWriteData(data);
}
void LCDSetCursorPosition(byte col, byte row)
{
  byte address = lcdRowStart[row] + col;
#ifdef SERIAL_DIAGNOSTICS  
  char buffer[40];
  sprintf(buffer, "SetCursorPosition: %d,%d 0x%02X", col, row, address);
  Serial.println(buffer);
#endif
  if (currentRow < lcdRows) // don't wrap around if (col,row) out of range (less confusion)
  {
    byte instruction = 0b10000000 | address;
    LCDInstruction(instruction); // Set DDRAM address
  }
  currentCol = col;
  currentRow = row;
}
void LCDCharacter(byte c)
{
  LCDSetCursorPosition(currentCol, currentRow);
  if (currentRow < lcdRows) // don't wrap around if (col,row) out of range (less confusion)
  {
    LCDWait();
    digitalWrite(REGISTER_SELECT_PIN, HIGH);
    digitalWrite(READ_WRITE_PIN, LOW);
    LCDWriteData(c);
  }
  currentCol++;
  if (currentCol == lcdColumns)
  {
    currentCol = 0;
    currentRow++;
  }
}

void LCDCharacterAt(byte c, byte col, byte row)
{
  currentCol = col;
  currentRow = row;
  LCDCharacter(c);
}
#ifdef LCD_8_BITS 
void LCDInitialize8Bit(byte functionSet)
{
  // as per Figure 23 (page 45) of the Hitachi data sheet - yes, much like beating it with a rock!

  delay(50); // > 40ms for Vcc to rise above 2.7V

  LCDWriteData8(functionSet);
  // pulse ENABLE_PIN
  digitalWrite(ENABLE_PIN, LOW);
  digitalWrite(ENABLE_PIN, HIGH);
  digitalWrite(ENABLE_PIN, LOW);
  delayMicroseconds(4500);

  LCDWriteData8(functionSet);
  // pulse ENABLE_PIN
  digitalWrite(ENABLE_PIN, LOW);
  digitalWrite(ENABLE_PIN, HIGH);
  digitalWrite(ENABLE_PIN, LOW);
  delayMicroseconds(150);

  LCDWriteData8(functionSet);
  // pulse ENABLE_PIN
  digitalWrite(ENABLE_PIN, LOW);
  digitalWrite(ENABLE_PIN, HIGH);
  digitalWrite(ENABLE_PIN, LOW);

  LCDInstruction(functionSet);
}
#else
void LCDInitialize4Bit(byte functionSet)
{
  // as per Figure 24 (page 46) of the Hitachi data sheet - yes, much like beating it with a rock!

  delay(50); // > 40ms for Vcc to rise above 2.7V

  LCDWriteData4(0b0010);
  // pulse ENABLE_PIN
  digitalWrite(ENABLE_PIN, LOW);
  digitalWrite(ENABLE_PIN, HIGH);
  digitalWrite(ENABLE_PIN, LOW);
  delayMicroseconds(4500);

  LCDWriteData4(0b0010);
  // pulse ENABLE_PIN
  digitalWrite(ENABLE_PIN, LOW);
  digitalWrite(ENABLE_PIN, HIGH);
  digitalWrite(ENABLE_PIN, LOW);
  delayMicroseconds(150);

  LCDWriteData4(0b0010);
  // pulse ENABLE_PIN
  digitalWrite(ENABLE_PIN, LOW);
  digitalWrite(ENABLE_PIN, HIGH);
  digitalWrite(ENABLE_PIN, LOW);

  // This 4 bit initialization works well for cold reset (no power to the LCD) but not for resetting
  // an already initialized and powered up LCD (without power cycling).
  // More luck with warm reset with even number of 4 bit writes (in case LCD is already in 4 bit mode)
  LCDWriteData4(0b0010);
  // pulse ENABLE_PIN
  digitalWrite(ENABLE_PIN, LOW);
  digitalWrite(ENABLE_PIN, HIGH);
  digitalWrite(ENABLE_PIN, LOW);

  LCDInstruction(functionSet);
}
#endif
void LCDInitialize()
{
  // 20x4
  lcdRowStart[0] = 0x00;
  lcdRowStart[1] = 0x40;
  lcdRowStart[2] = 0x14;
  lcdRowStart[3] = 0x54;
  lcdColumns = 20;
  lcdRows = 4;

  // 16x2
  //lcdRowStart[0] = 0x00;
  //lcdRowStart[1] = 0x40;
  //lcdColumns = 16;
  //lcdRows = 2;

  currentCol = 0;
  currentRow = 0;
  
  pinMode(REGISTER_SELECT_PIN, OUTPUT);
  pinMode(READ_WRITE_PIN, OUTPUT);
  pinMode(ENABLE_PIN, OUTPUT);
  LCDDataAsOutput();
  digitalWrite(REGISTER_SELECT_PIN, LOW);
  digitalWrite(READ_WRITE_PIN, LOW);
  digitalWrite(ENABLE_PIN, LOW);

#ifdef LCD_8_BITS 
  LCDInitialize8Bit(0b00111000); // Set 8-bit mode; 2-line display; 5x8 font
#else
  LCDInitialize4Bit(0b00101000); // Set 4-bit mode; 2-line display; 5x8 font
#endif
  LCDInstruction(0b00001110); // Display on; cursor on; blink off
  LCDInstruction(0b00000110); // Increment and shift cursor; don't shift display
  LCDInstruction(0b00000001); // Clear screen
  LCDSetCursorPosition(0,0);
}

void setup() 
{
#ifdef SERIAL_DIAGNOSTICS    
  Serial.begin(115200);
#endif  
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);

  LCDInitialize();
  
  // 80 characters:
  const char * str = "I <3 my Witch! 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@#$Extra";
  
  while (*str)
  {
    LCDCharacter(byte(*str)); // increments our cursor for each character
    
#ifdef SERIAL_DIAGNOSTICS      
    byte address = LCDGetCurrentAddress(); // get the LCD address
    char buffer[40];
    sprintf(buffer, "Character: '%c', Address: 0x%02X", *str, address);
    Serial.println(buffer);
#endif    
    str++;
  }
}

void loop() 
{
  // bottom right last 3 character cells: '+++'
  LCDSetCursorPosition(lcdColumns-3, lcdRows-1); 
  LCDCharacter('+');
  LCDCharacter('+');
  LCDCharacter('+');

  // using built-in LED as a heartbeat to show that I did not hang or crash in my code in setup()
  digitalWrite(LED_BUILTIN, HIGH);
  delay(500); 

  // bottom right last 3 character cells: '---'
  LCDSetCursorPosition(lcdColumns-3,lcdRows-1);
  LCDCharacter('-');
  LCDCharacter('-');
  LCDCharacter('-');

  digitalWrite(LED_BUILTIN, LOW);
  delay(500);
}

Credits

Michael Cartwright

Michael Cartwright

21 projects • 14 followers

Comments