Connecting an LCD to a Z80 with Two Glue Chips

I’ve been breadboarding a Z80 computer lately. I wanted a test bed to explore ideas and to learn how the hardware worked. I also wanted to be able to create a small, self contained Z80 machine including both input and output devices. The simplest output device to connect is probably a generic character LCD display.

A generic character LCD display.

Using Latches

Connecting a character LCD requires connecting the eight data pins and three control signals – E (enable), R/W (read or write) and RS (register select – command or data).

Initially I did this with a pair of 74LS273 8-bit latches, one for the data and the other for control signals. In operation I would output the data to the data latch then send send a byte to the control latch to set the register select pin and enable pin, then send a second byte to the control latch to cancel the enable signal.

Fig 1. Connecting an LCD display to a Z80 via latches (block diagram)
Fig 1. Connecting an LCD display to a Z80 via latches (block diagram)

In assembler we have something like this:

;A = data byte
ld c,data_port
out (c),a
ld c,signal_port
out (c),$01   ;enable + command (or $5 for data)
out (c),$00   ;disable

This worked but having to do three output operations for every byte sent felt like hard work. And there was no way to read back the busy signal1 – at least, not without adding extra hardware. And I really needed the busy signal. My breadboard computer enabled me to either use a fast crystal or a slow clock generated from an Arduino, which I sometimes needed for debugging. Not being able to read the busy signal meant I was having to hard code delay loops for running at high speed, and then build a separate version without them for slow speed running. Was there an easier way to connect the display?

Direct Connection

I reasoned that the data pins on the display where just bi-directional tri-state pins. That would mean they could be directly connected to the bus. But what would I need to drive the control lines? The LCD needs the enable pin to be driven high while the I/O operation takes place. I was already using a 74LS138 for address decoding so I could just invert the active low output from the ‘138 to get that enable signal. But what about the R/W pin?

The R/W pin needs to be low to write data to the display and high to read data. I did some googling and found references saying all you needed was a small bit of control logic but nothing with schematics except this link. This shows a circuit to control the enable pin and connects the read/write pin to an address pin. It looked workable but using the wrong value on that address pin could mean the LCD writing to the bus at the same time the Z80 was also writing to the bus which, of course, risked damaging the hardware.

I figured that the LCD is only reading the R/W pin when it is enabled, so it doesn’t matter what happens to it at other times. The pin itself needs to be low when data is being written and the Z80s /WR (write) signal is low when data is being written. I tried connecting the Z80s /WR line directly to the displays R/W input but it didn’t work.

Could I use the Z80s /RD (read) signal instead? This is normally high and only low when reading. If I inverted it then it would be normally low and only high when reading. I wired up this solution and, after a few typical breadboarding issues, it worked.

My breadboard Z80 computer with LCD display attached and working.

Why Not Use the /WR Signal?

So, I had a working solution but I couldn’t understand why the inverted /RD solution worked but the /WR solution didn’t. At the time I didn’t have an oscilloscope but I now do, so lets take a look at what’s happening.

Fig 2: Using the inverted /RD signal as the R/W signal
Fig 2: Using the inverted /RD signal as the R/W signal

Figure 2 above shows what happens when using the inverted /RD signal as the R/W input. The bottom (red) line, D0, is the E(nable) signal generated by the ‘138 and inverter. D1 is the R/W signal sent to the display. D2 to D4 are the Z80s /IORQ, /RD and /WR signals respectively. Notice how the R/W signal is simply the inverted /RD signal. Also notice how the E signal is correlated with the /IORQ signal, albeit marginally delayed by the circuitry.

The image itself shows both writing to the display (the high E signal on the left) and then reading back (in this case the busy state) with the second pulse on E.

Fig 3: Using the /WR signal as the R/W signal
Fig 3: Using the /WR signal as the R/W signal

Figure 3 with the same settings shows that same operations but using the Z80’s /WR signal to directly drive the displays R/W input.

Fig 4: Typical timing diagram for an ST7066U character LCD display controller
Fig 4: Typical timing diagram for an ST7066U character LCD display controller

And figure 4 shows the write cycle timing diagram for the LCD display.

This diagram shows that the values on the address (RS and R/W) and data lines need to be held until after the enable signal is cancelled. The Z80 timing diagram, figure 5, (and the trace in figure 3) shows that the address and data lines are held after the IORQ signal is cancelled but the WR is cancelled at the same time.

So, the display is sampling the address and data lines shortly after the enable signal is cancelled, by which point the /WR signal is no longer asserted.

Fig 5: z80 I/O timing diagram
Fig 5: Z80 I/O timing diagram

However, when we using the inverted /RD signal, the signal will always be high except when writing. Thus, when the processor is between reads and writes /RD is not asserted so our R/W signal remains asserted after the /IORQ finishes and we get the needed behaviour. The Z80 timing diagrams show that there will always be half a clock cycle (the second half of T3) before the write operation finishes. Even at 8MHz this is long enough to maintain correct operation of the LCD’s write cycle2.

When reading from the display our inverted /RD signal means that the R/W signal to the display is in the ‘read’ state only whilst IORQ is asserted. Data is sampled by the Z80 while IORQ is still asserted so, again, we get the required behaviour.

Watching the Z80 Code

Fig 6: Signals when writing data and a busy loop
Fig 6: Signals when writing data and a busy loop

While we’re on the subject of oscilloscope traces I though it might be interesting to see some more detail. Figure 6 above shows a write operation followed by repeatedly reading the busy status until the next write operation. The trace here shows the enable and R/W signals sent to the display and D7 line either being written or read back.

The R/W and D7 signals also show what is happening whilst the Z80 is reading the program code between I/O operations.

The code generating these signals is:

... Previous code
ld c,$20       ;LCD command port

ld a,$3f       ;Configure display: 8-bit interface, 2-lines
out (c),a      ;Send command
lcd_busy_loop1:
in a,(c)       ;Read status
rlca           ;Move status to carry flag
jr c,lcd_busy_loop  ;While busy

ld a,$0f       ;Turn cursors on
out (c),a      ;send command
lcd_busy_loop2:
in a,(c)      ;Read status
rlca          ;Move status to carry flag
jr c,lcd_busy_loop2 ;While busy

... Rest of code

Finally, We Get To The Schematic

Below is my final schematic (shown here using an RC2014 bus).

Fig 2. Schematic for connecting a generic character LCD to a Z80.
Fig 2. Schematic for connecting a generic character LCD to a Z80. Note: It may also be possible to connect the R/W line of the LCD directly to the /WR line of the Z80.

You can download the KiCad files and some test code at by Github.

My prototype character LCD module for the RC2014

And here, if you ignore my dreadful soldering, is my prototype RC2014 module. This also includes a second connector which is suitable for an OLED display which I had to hand.

My prototype LCD board connected to my RC2014

Footnotes

  1. The busy signal is read by reading back the command register. Bit 7 will be set if the device is busy
  2. At 8MHz half a clock cycle is about 60ns. The LCD being used here needs the address and data pins to be held for only 10ns. Other devices could require a longer hold time

Leave a Reply

Your email address will not be published. Required fields are marked *