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.
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.
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?
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.
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.
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.
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.
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.
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
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).
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.
- The busy signal is read by reading back the command register. Bit 7 will be set if the device is busy
- 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