Parallel port demo with stepper motor and serial LCD display.
Fig. 1 Parallel port demo with stepper motor and serial LCD display.


Build Serial HD44780 LCD Display Connect to Parallel Port

by Lewis Loflin

Here I'll look at my serial HD4470 based serial LCD display. It is shown in Fig. 1. It has 4 input pins plus 5-volts and ground.

HD4470 based serial LCD display schematic.
Fig. 2 HD4470 based serial LCD display schematic.


The schematic to the serial LCD display is in fig. 2. There are 4 input connections: E, RS, DATA, SCLK. They are connected to the printer port CONTROL pins.

These can be direct connections or through a breakout board. For more on this see Build or Buy Parallel Port Breakout Board? for connections.

This can also be done without the serial shift register if one wants to use the 8-bit DATA port.

This has been ported over from Arduino and Raspberry Pi. My code is designed to move between platforms.

A 74HC595 can work just as well with minor coding and wiring differences.

Serial LCD Display You Tube:
74C164 shift register with Microchip PIC
74C164 shift register with Arduino
SN74164 SSR with a LCD Display and Arduino

Serial HD44780 circuit connected PC parallel port.
Fig. 3 Serial HD44780 circuit connected PC parallel port.


Fig. 3 illustrates the serial LCD connections to a PC parallel port.

A byte (8-bit) ASCII character or control code is shifted into the 74LS165. A control code sets the LCD cursor position, etc.

RS is set HIGH for ASCII character or LOW for control code. The following list is for control codes. Write command is a subroutine I wrote for Arduino.

Note the first line is designated 0 and the second line designated 1.


  Hd44780 display commands:
  0x0f = initiate display cursor on blinking
  0x0c = initiate display cursor off
  0x01 = clear display fills display with spaces (0x20).
  0x02 = HOME returns to line zero first character
  0x38 = 2 lines X 16 char 8 bits mode. 
  Defaults to 1 line mode.
  0x10 = cursor left
  0x14 = cursor right
  0x18 = Shifts entire display left
  0x1c = Shifts entire display right

  One can also go to a specific location.
  writeCom(0x80); // begin on line 0.
  writeCom(0x80 + 0x40); // begin line 1.

  writeCom(0x38); // setup for 2 lines.
  writeCom(0x0F); // blinking cursor.

  writeCom(0x02); // home
  writeCom(0x01); // clear

Will display strings and characters.

Base address 0x37A - four bits 0-3;
bit C0 pin 1 inverted connected to 74LS164 SCLK pin 9;
bit C1 pin 14 inverted connects to 74LS164 DATA pins 1 and 2;
bit C2 pin 16 not inverted connect to RS on LCD;
bit C3 pin 17 inverted connect to E on LCD;
bit C5 is data direction bit for the data port at 0x378. We won't use that here.

The 8-bit data byte be it ASCII or command code is shifted into a 74LS164 serial shift register. This is most significant bit (D7) first. After data is shifted in, RS selects ASCII (HIGH) or control code (LOW), E switches from HIGH-LOW-HIGH. Byte is input to LCD.

LCD line 0 position 0 is at 0x80. Characters are displayed 0x80-0x8F. LCD line 1 position 0 is 0xC0. Characters are displayed 0xC0-0xCF. Home is considered line 0 position 0.

Now we will look at some code snippets. Let's define some names:


#define LOW 0
#define HIGH 1

#define RS 4
#define E 8

#define C0 1
#define C1 2
#define C2 4
#define C3 8

#define ln1 0x80 // line 0
#define ln2 0xC0 // line 1

The bits in the control register 0x37A are defined as powers of 2. This is use in subroutine setCbit(int bit, int state). This works the same way as Arduino digitalWrite().

The pulseE(void) togglee E from HIGH-LOW-HIGH. typeChar(char)

The subroutine ssrWrite(char) is a serial shift routine.


void ssrWrite(char val)  {
  // shift data into 74164
  setCBit(C0, HIGH); // inverted to LOW
  int j, temp;
  for (j = 1; j <= 8; j++)  { // shift out MSB first
    temp = val & 0b10000000; // MSB out first
    if (temp == 0x80) setCBit(C1, LOW);
    else setCBit(C1, HIGH);
    pulseE();
    val = val << 1; // shift one place left
  }  // next j
}  // end ssrWrite

The typeChar(char) uses ssrWrite() sets RS to HIGH, toggles E with pulseE().


void typeChar(char val)   {
  // HIGH is ASCII mode
  setCBit(RS, HIGH); // non-inverted
  ssrWrite(val); // serial out
  pulseE();
}

The subroutine writeCom() is nearly identical to typeChar() other than RS is set LOW.

The subroutine typeln(*s) will output a string of text on the LCD starting at the position pointer. The location or position must be set first. This is a memory address within the LCD. This text is passed as a string or character array terminated by a zero. If the string is longer than 16 characters the extra characters won't be seen.


void typeln(char *s)   {
  while (*s)  typeChar(*(s++));
} 

Let's do a simple program to print a text string at the beginning of lines 0 and 1 on the LCD display.


int main(void)   {

  // must include to access port
  if (ioperm(0x378, 3, 1))
    fprintf(stderr, "Access denied to %x\n", 0x378), exit(1);

  char myString[] = "Hello world!";

  initLCD();
  writeCom(0x80); // goto line 0
  typeln(myString);
  writeCom(0xC0); // got line 1
  typeln("Hello world!");
  return 0;
}

What did we just do? First ioperm(0x378, 3, 1) under Linux on a PC gives us access to the IO hardware. This must either be done as root or with proper sudo permissions. If this fails the program is exited.

The command initLCD() sets up the LCD for proper operation, writeCom(0x80) is a memory location as is 0xC0.

The typeln(char*) passes a string location starting at myString[0]. In reality it also directly with a string name as well.

Parallel connected HD44780 to PC printer port.
Fig. 4 Parallel connected HD44780 to PC printer DATA port.


Fig. 4 is the schematic for direct DATA port connection of the LCD display at address 0x378. The only change needed is to change where ever ssrWrite() is use the outb() command:


void typeChar(char val)   {
  // HIGH is ASCII mode
  setCBit(RS, HIGH); // non-inverted
  outb(val, 0x378);
  pulseE();
}

The complete code can be cut-pasted at the bottom of this webpage.

Youtube Videos

View all of my You Tube Videos
Also visit and subscribe to My YouTube Channel

Web site Copyright Lewis Loflin, All rights reserved.
If using this material on another site, please provide a link back to my site.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/io.h>
#include <string.h>

#define LOW 0
#define HIGH 1


#define RS 4
#define E 8

#define C0 1
#define C1 2
#define C2 4
#define C3 8

#define ln1 0x80 // line 0
#define ln2 0xC0 // line 1

void initLCD(void);
void CLR(void);
void HOME(void);
void gotoLoc(int);
void setCBit(int, int);
void ssrWrite(char);
void pulseE(void);
void typeChar(char);
void writeCom(char);
void typeln(char*);


void ssrWrite(char val)  {
  // shift data into 74164
  setCBit(C0, HIGH); // inverted to LOW
  int j, temp;
  for (j = 1; j <= 8; j++)  { // shift out MSB first
    temp = val & 0b10000000; // MSB out first
    if (temp == 0x80) setCBit(C1, LOW);
    else setCBit(C1, HIGH);
    // serial clk D0 clock in LOW to HIGH
    setCBit(C0, LOW); // inverted to HIGH
    // usleep(100);
    setCBit(C0, HIGH); // inverted to LOW
    val = val << 1; // shift one place left
  }  // next j
}  // end ssr


// clear or set individual bits in control register
void setCBit(int bit, int state)   {

  int x, y, z;
  x = inb(0x37A);
  y = x & bit; // determine bit state HIGH or LOW
  // bit is a numeric value based on powers of 2
  // keep in mind bit 2 pin 16 output is NOT inverted
  if (state == 0  && y != 0)   {
    z = x & (0xFF - bit); // bitwise AND clear bit
    outb(z, 0x37A);
  }

  if (state == 1)   {
    z = x | bit; // bitwise OR set bit
    outb(z, 0x37A);

  }

}

void printBinary(int num1)   {

  int i, j;
  for (i = 0; i < 8; i++)   {
    j = num1 & 0x80; // mask high bit
    if (j > 0) printf(" 1");
    else printf(" 0");
    num1 = num1 << 1; // left shift 1 bit
  }
  printf("\n");
}

void pulseE()   {
  // bit is inverted
  // setCBit(E, LOW);
  setCBit(E, HIGH);
  usleep(100);
  setCBit(E, LOW);
}

void initLCD()   {

  // LOW is command mode
  setCBit(RS, LOW); // non-inverted
  setCBit(E, LOW); // inverts to HIGH
  setCBit(C0, HIGH);
  // HIGH To LOW clock in DATA
  ssrWrite(0x38);
  // outb(0x38, Data); // setup for 2 lines
  pulseE();
  ssrWrite(0x0F);
  // outb(0x0F, Data); // blinking cursor
  pulseE();
  ssrWrite(0x01);
  // outb(0x01, Data); // clear
  pulseE();
  ssrWrite(0x02);
  // outb(0x02, Data); // home
  pulseE();
  usleep(10000); // 10mS wait
}

// clear display
void CLR(void)   {
  // LOW is command mode
  setCBit(RS, LOW);
  ssrWrite(0x01);
  pulseE();
  // back to ASCII
  setCBit(RS, HIGH);
}

// clear display
void gotoLoc(int pos)   {
  // LOW is command mode
  setCBit(RS, LOW);
  ssrWrite(pos);
  pulseE();
  // back to ASCII
  setCBit(RS, HIGH);
}

// go to line 0 pos 0
void HOME()   {
  // LOW is command mode
  setCBit(RS, LOW);
  ssrWrite(0x02);
  pulseE();
  // back to ASCII
  setCBit(RS, HIGH);
}


void typeChar(char val)   {
  // HIGH is ASCII mode
  setCBit(RS, HIGH); // non-inverted
  ssrWrite(val); // serial out
  // outb(val, Data);
  // usleep(100);
  pulseE();
}

void writeCom(char val)   {
  ssrWrite(val);
  // outb(val, Data); // send byte to port
  // LOW is inverted to HIGH
  setCBit(RS, LOW); // make sure RS in Command mode
  // usleep(100);
  pulseE();
}

// Below we pass a pointer to array1[0].
void typeln(char *s)   {
  // usleep(1000);
  // writeCom(pos); // where to begin
  while (*s)  typeChar(*(s++));
}  // end typeln



int count;
char result[16];

int main(void)   {

  // must include to access port
  if (ioperm(0x378, 3, 1))
    fprintf(stderr, "Access denied to %x\n", 0x378), exit(1);

  initLCD();
  gotoLoc(ln1);
  typeln("Hello Lewis!");

  // sleep(1);
  // CLR();
  gotoLoc(ln2);
  typeln("Count = "); // 8 chars
  for (count = 0; count <= 255; count++)   {
    gotoLoc(ln2 + 8);
    if (count < 15) typeChar('0');
    // gotoLoc(0xC0); // ln 2
    sprintf(result, "%X", count);
    printf("Count = %X \n", count);
    typeln(result);
    usleep(500000);
  }

  return 0;

}