|Electronics » the Z80 project » gfx system
Ever get the urge to make a complete Z80-based computer system from scratch? I
did once. I even made my own membrane keyboard and display (featuring the ultra
high resolution realism of an 8x8 grid of red LEDs.. Pong has never looked so err..
My "V1 Z80 Project" - A bit of a mess really.
Due to the ad hoc nature of the project it ended up as a sprawling mass of
cables and PCBs but it was interesting to tinker with.
One thing that I would've liked to do was make my own graphics
"card" so I could connect it to the TV and have a proper display. Unfortunately,
I didn't really have enough technical info at the time, so for that and other
reasons the Z80 project gathered dust...
Fast forward about a decade (!) and I suddenly find myself with (too much) time on
my hands, looking for a project to fiddle about with.. Of course with the power of
the Internet now at a geek's fingertips, obtaining technical information is not a
problem and sure enough, after a bit of browsing I had the necessary
PAL TV timing and voltage specifications.
Especially useful was this guy's
website. He's produced TV output (and games) using a single PIC 16C84 microcontroller!
I've used PICs myself a lot in the past so I wrote some quick test code to produce a stable TV signal using
Rickard's info as a template. At first I only sent signals to the composite video line,
this allows a grey scale image to be produced with a slowish microcontroller. Colour
via composite needs a faster microcontroller and I didnt have any at the time so I tried
using 3 ports from the PIC to control the TV's SCART RGB lines instead - see below, right.
(I've put together a simple demo circuit based on a PIC 16F628. The code is
quite easy to follow as it doesn't do much except create the timing frame
and show some colour bars. Download code/schematic here )
RGB colour TV output from a PIC16C84
Unfortunately even in RGB mode, the PIC 16C84/16F84 (and more recent 16F62x) range
of microcontrollers are too slow to produce video at a decent horizontal resolution
on their own. With a 5Mhz instruction clock (20Mhz source clock) the pixel size would
be almost four times that of a Sinclair ZX Spectrum (each pixel needs 2 instructions, a read
and write instruction - limiting the pixel clock to 2.5Mhz). Also the PICs I was
using didnt have enough ports to be able to fetch data from external memory without
a lot external latching which would slow things down even more.
So at this point I decided to use the microcontroller just to provide the TV timing
"framework" and design the serious bit of the graphics board using discrete logic
ICs. I reckoned a 256x192 pixel display would serve my purposes so
I modified the PIC RGB test code to produce an appropriate display window. This is
basically what it does to create my display window within a non-interlaced 312-line
PAL TV frame:
- Do 56 blank lines (y border)
- Do 192 display window lines (x-border, display, x-border)
- Do 56 blank lines (y border)
- Do special vertical sync lines (reset TV's raster beam)
Some detail (corrected 26/10/05 - oops!)
"Blank line" - 4uS sync low pulse, 60 uS delay..
"Display Window Line" - 4uS sync pulse, 18 us delay, 32 uS display window (256 pixels), 10 us delay..
"Vsync Lines" - For a non-interlaced display: 6 equalizing syncs, 5 long syncs,
5 equalizing syncs (see this page for details).
With the PIC microcontroller handling the TV timing, I planned out the other
features of my graphics system. It was always going to be a bit-mapped display
and I didn't want any horrendous Spectrum-like colour attributes.
The simplest design idea would have been to fetch a byte for each pixel from an SRAM
chip and use bits from that byte to drive the TV's RGB lines
on or off. As well as being wasteful of memory, such a system would also limit
to the display to 8 colours (the primary and secondary colours). Instead, pairs of bits
could be fed to 3 digital to analogue converters: More colour resolution, less wasteful
and 64 colours on screen. However, with one byte per pixel, even my 256x192
display would take 48K, slightly too much of the poor Z80's address
space! Bank switching could be implemented but that would make access to
the screen a pain. Anyway, the main reason this system wasn't used
was the sheer amount of data the Z80 processor would have to shift in order to move
anything around the screen - it just isn't up to that kind of workload.
Reading a single bitplane from an EPROM
In the end I went for Amiga-like bitplane system. In my design, several 1-bit
pages (pretty much a Spectrum's display each, less attributes) are stacked
"on top of" each other, and each pixel is created by combining the bits
(one from each page) into a "pixel word" who's bitlength depends on the number of
bitplanes used. Each bitplane would only take (32 bytes x 192) = 6KB and
only one bitplane really needs to be mapped into Z80 address space at any one time.
The CPU workload is also reduced because the CPU
doesn't have to write to all the bitplanes if less colours are
involved in an operation.
The downside to this system is that it does complicate the design. Each
pixel has to be constructed from bytes at separate locations in memory,
shifted and combined. More on this later.
A 3-bitplane system with each bit directly controlling the R,G,B lines would
make a simple circuit on the TV output side: As each line is either on or off
no digital to analogue converter is required, just a single resistor on each line
to drop the logic-level voltages down to 0.7 and 0 volts respectively. However,
I wanted a colour palette with up to 16 colours on screen - so I specified 4
bitplanes and sent the 4-bit pixel word from the bitplane combination logic
to the address lines of a small (and reasonably fast) SRAM chip which holds
the palette data.
The palette SRAM produces a whole byte for each pixel but as 8 bits doesn't
divide nicely between the three Red, Green & Blue channels I originally just ignored
the top 2 bits and allocated 2 bits per channel giving 4 brightness levels
for each colour component. To get the binary values to produce analogue voltages
between the RGB-spec 0v and 0.7v I used 2 resistors per channel, a 750 ohm
resitor on the MSB and a 1500 ohm resistor on the LSB. The other ends of the
resistor pairs are connected together and sent to the TV's RGB lines via the
SCART socket. As the TV effectively has 75 Ohm resistors to ground on each line, a
potential divider is created that sets the correct voltage levels.
My newer "2 x 3 + 2" bit DAC.
This system gave a palette of 64 colours. I later improved this slightly using
bits 6 and 7 from the palette SRAM output to give greater colour resolution
on the Red and Green lines, simply by connecting them via 3000 Ohm resistors
to the scart lines. There obviously wasn't a 9th bit for the Blue line so that
had to stay at 2-bit resolution. The output circuit is shown on the right
(the outputs are shown buffered by a 74HC574 latch IC). The composite sync
line is also shown: 5 volts from the logic is buffered by
the 10K resistor into a general purpose NPN transistor (emerging at about 4.3v
because of the base-emitter voltage drop) and meets a potential divider formed
by the 1K Ohm resistor at the emitter and the 75 Ohm resistor in the TV.
4.3v / (1000+75) * 75 = 0.3V - which is the level the TV requires for a "sync
Inactive" signal (drops to zero volts for Active). The picture quality is
perfectly fine even with simple R2R DAC affair - though I did have to keep
the video ground trace seperate from the rest of the logic and route it
the main supply "ahead" of the other PCBs to avoid on screen noise patterns.
Going back to the overall graphic system design, it occurred to me that it'd be quite
trivial to implement hardware scrolling (a 0 to 7 pixel offset of the display window
which saves the CPU having to shift bits manually). For vertical scrolling its just a case
of skipping 0 to 7 lines at the top of the frame and for horizontal scrolling
it just means delaying the output of each scan line by "x" pixel clock periods.
Now hardware scrolling isn't that much use unless you have a spare display
buffer to build the new image (whilst the hardware scroll does the fine pixel scrolling).
A second display buffer was therefore specified in my design - this would also mean that the Z80 could access one bank
of video RAM at full speed whilst the video controller was reading pixels from
One of the VRAM boards.
Another useful feature would be video-sync'd interrupts. With Commodore 64 style
raster IRQs I could change the colour palette mid screen, do split screen
scrolling and so on. I figured such a system would just require a scanline
counter set at the beginning of each frame (or anytime afterwards) which
would trigger the interrupt when it reached zero.
Well anyway, at this point my graphics system ended up consisting of five seperate
100x75mm PCBs stacked on top of each other. I used right-angled pin headers and
50-pin IDC (SCSI) cables and leads to connect them all together in a reasonably
tidy way. Here's a brief outline of their functions from a more technical
point of view:
PCB 1: Main video controller (dodgy schematic here)
This board coordinates the TV display
timing (generates syncs etc) and merges the 4 bitplanes fetched from the VRAM board(s) into
a 4 bit nybble for each pixel to send to the palette PCB (four latching parallel to serial
shift register ICs (74HC597) handle that). The video controller was orignally based
around a PIC16F628 with external counter ICs for hardware scroll etc but now uses
an SX28 at 32MHz to perform the same functions in software. The entire system's master
clock is a 64MHz quartz oscillator module whose output is fed directly to the sprite
PCB (which has an SX28 running at 64MHz), via a 74HC4024 counter/divider IC to the
video controller SX28 (32MHz) and off-board to the Z80 CPU (8Mhz). Note that the SX28
(still) doesn't actually read in or process the video data itself - it just coordinates
everything. Some signals it produces are only required locally, for example
the shift clock to the four parallel-to-serial shift regsiter ICs. Other signals
are used throughout the system (EG:X-mask, Y-mask (TV border periods) Sync for TV,
Vertical Retrace etc). My SX28 source code for the video controller is here.
A video address bus is formed by the output of two 74HC4040 counter ICs
plus several bits direct from the SX28. Three such bits are the 3 LSBs of the
scanline number -I made those directly controllable for the vertical hardware
scroll - they can be set to a 0-7 line offset at the start of the frame
and incremented each line down. The MSB of these three is fed to the clock
input of the upper 74HC4040 IC so when the count wraps, it increments the
"coarse" video address.
The lowest 5 bits of other 74HC4040 IC hold the X-direction byte count (0-31) as the
raster sweeps the screen horizontally, it is clocked by output 7 of a 74HC4017 decade
counter. The sequential outputs of this IC are used to latch data from four bit-planes
into the 74HC597 parallel-to-serial shift registers (each bitplane is addressed by the
SX as it controls Video Address lines A13-A14 directly). The only other component is a 74HC157
data selector IC which channels the outputs of the X or Y hardware scroll
latches to the SX28 (I ran out of SX input ports, so multiplexed these two 3-bit values).
PCB 2: Palette and output PCB. A small SRAM chip holds the palette table.
Two 74HC157 IC switch its address bus between the Z80 (write only)
and the video controller (read only). To keep things simple the palette
can only be updated when the raster is off screen (either horizontal or
vertical masking periods). The first 4 bits of the palette SRAM's
address bus come from the video controller, the 2nd 4 bits from the
output of the sprite controller, the other address lines are held
low. The SRAM's data bus is directed to a either a 74HC574 latch (who's
outputs are connected to resistors to form 3 DACs for the red, green and blue
lines) or the Z80's data bus through a 74HC245 buffer IC for palette updates.
PCB 3: Video RAM board 1 (Buffer 0).. Has a 32KB SRAM IC to hold the display
data in linear bitmap format. Four 4-bit data selector ICs (74HC157)
switch the SRAM's address bus between the CPU and the video controller.
Two 74HC245 ICs direct the SRAM's data bus to the Z80 (inputs) or
video controller (outputs) at appropriate times. A circuit based on
five standard 74HC logic ICs coordinate the switching between busses
and provide the Z80's WAIT input (the Z80 is locked out of video memory
during each active raster line - hence the desirability of two buffers).
PCB 4: Video RAM board 2 (Buffer 1). Pretty much as above, but selected when
buffer 0 is not.
PCB 5: Main interface to Z80 bus. Decodes the Z80's address lines to make chip
select signals for video RAM, palette RAM etc. Also has two 8bit data latches
(74HC574) to hold the hardware scroll and bitplane / buffer select values.
Two 4 bit (74HC193) counter ICs latch and count down the raster line sync'd
interrupt position. A 74HC245 buffer allows the Z80 to read lines such as
vertical sync, video_busy etc. A 74HC74 flip/flop holds the video IRQ flag.
Phew! Hope some of that was interesting / useful to someone... There's a
video clip of my graphics system in action on this page BTW.
After finishing the graphics system my attention turned to other aspects of the Z80 Project (Keyboard interface, Operating System,
Sound board etc). I eventually returned
to it for one more addition - a hardware sprite PCB