Saturday, February 16, 2013

Phoenix: I/O

A computer that you can type programs into is nice, but sometimes you'd like a way to save things to a more persistent medium, and later load it again. Early home computers used cassette tapes for storage, and although I'm sure I could make something like that work with for example a phone's headset connector, I decided to use SD card storage instead. Despite a 30-year generation gap between the 8-bit Z80 and a current-day 4 GB microSD card, it's actually not that hard to get the two to talk to each other successfully.

First of all, SD cards use 3.3 volts power and logic levels, and most SD card holders are surface mount only. So I decided to use the Adafruit microSD card breakout board: this includes a voltage regulator, level shifter and microSD card holder all on a small PCB with standard .1" spacing suitable for breadboarding.

Now that the electrical hook-up was out of the way, I needed a way to make the Z80 and the microSD card talk to each other. Fortunately, the SD card standard allows for SPI communication, mostly for use with microcontrollers. The Z80 doesn't speak SPI, but it's not hard to bit-bang it.

I could have used a 82C55 parallel I/O interface chip (this was used in the original IBM PC for the parallel port, for example), but using a giant 40-pin DIP chip just for 4 data lines seems massive overkill. Instead I used a few 7400-series logic chips to create the interface:



The 74HC541 buffer (the same type I used in the video circuit) is used for data from the SD card to the Z80: when the Z80 issues an "IN" instruction it takes IORQ and RD both low, and the combination of both enables the '541 output drivers, placing the MISO signal from the SD card onto the Z80 data bus.
Note: both the keyboard controller a few posts back and this circuit have no address decoding logic, so they can't be used together, but more on that below.

For the other direction, I used a 74HC175 quad D-type flip-flop. It's essentially being used as a 4-bit register: when the Z80 issues an "OUT" instruction, IORQ and WR are both pulsed low and through the 74HC32 OR-gate generate a clock pulse to the flip-flops updating their state with the value being output by the Z80. Note that I used /Q4 to hook up the microSD enable signal, which is active-low, so that when the flip-flop is reset at startup the enable signal is high (inactive).

I've written some code to perform the SD card initialization sequence and here is what it looks like on the logic analyzer:

Note that since the protocol is SPI, there is a byte being transferred in both directions for each 8 clock transitions and it is up to the higher level protocol whether the MOSI or MISO direction is actually being used. After taking ENABLE low, the Z80 first reads a byte to verify the card is ready, then writes the sequence 0x40 0x00 0x00 0x00 0x00 0x95 which is the SD initialization command (CMD0). It then reads another byte to wait for the SD card's response being ready, which comes in the next byte: 0x01, which indicates successful initialization.

Unfortunately, this is all the software I've written so far for the SD card interface at this point. I do plan to expand it so I can load and save programs on the SD card, just like I used to do on tape with my ZX Spectrum. I'll miss the loading sounds though.

Now, back to the keyboard controller: given that the ATtiny85 used for the keyboard controller can do SPI as well, and there still are a few lines available on the 74HC541 and 74HC175, we can actually eliminate the 74HC595 shift register and hook up the ATtiny85 similar to the SD card.

The keyboard protocol is now changed as follows: the ATtiny's SPI hardware is initialized in slave mode; when a key is ready the ATtiny puts the scan code into the SPI output register and takes the PB0 line high. Once the Z80 sees this line has gone high, it toggles the PB2 line 8 times which causes the SPI hardware in the ATtiny to shift out the bits.

I've updated the code on GitHub for the ATtiny keyboard controller program as well as the Z80 machine monitor program to use the new protocol, which looks like this on the logic analyzer when the "a" key is pressed:

Thursday, February 7, 2013

Phoenix says "Hello, world."


Now that Phoenix has a CPU, memory, display and a keyboard, it's time to actually make it do something. The first "real" program I wrote was loosely inspired by the Apple 1's Woz Monitor: a simple text-based program that can be used to inspect and modify memory contents, useful for debugging during the development of more advanced programs.

Since Phoenix display hardware is graphics based, the first thing I needed was a way to display characters on the screen. Using 8x8-pixel characters on the 256x192-pixel screen gives 24 lines of 32 characters each. Not spectacular, but the same as used in the ZX Spectrum, for example.

It's fairly straightforward to display 8x8-pixel characters: the framebuffer is organized as bytes of 8 pixels each. A line of pixels is 32 bytes, and so a line of text is 8 * 32 = 256 bytes. This makes cursor addressing especially easy: converting from (row,column) in a register pair to the location in memory just requires adding the start address of the framebuffer.

In addition to just drawing the characters, we need to keep track of the current position and increment  it after each character, handle newlines and scroll the screen when the bottom is reached. The latter can be done easily using the Z80 "LDIR" (Load, Increment, Repeat) instruction.

Since the monitor deals with memory addresses and contents, I also wrote some functions to print and parse hexadecimal values.

The monitor program (as before, available on GitHub) consists of a main loop reading a character from the keyboard using the routine described in the previous post, and interpreting it as one of the commands listed below. There's a separate function implementing each of these commands, reading additional arguments as needed and then executing it, followed by jumping back to the start of the loop.

The commands supported are:

  • e value -- enable/disable keyboard [e]cho: whether keyboard input is shown on the screen.
  • d address size -- [d]ump the contents of size number of bytes at address.
  • l address size byte... -- [l]oad size number of bytes into memory starting at address.
  • j address -- [j]ump to address.
And here is a demonstration of the monitor being used to enter a simple Z80 machine code program, verify its contents, and run it:


Sunday, February 3, 2013

Phoenix: Keyboard


Compared to the graphics subsystem, Phoenix's keyboard interface is much more straightforward. Home computers of the early '80s usually had a built-in keyboard, but building my own keyboard seemed a bit much. Instead I opted for an external keyboard with PS/2 connector: still widely available (albeit usually using an adapter), and easy to interface, especially compared to USB.

The PS/2 interface contains just 4 wires: ground, 5 V power, clock and data. The clock and data signals form a synchronous serial signal. The bus is bidirectional (so the computer can send data to the keyboard, e.g. to update the state of the caps lock and num lock LEDs), but in this project I'm only using only the keyboard->computer direction. As a result the caps lock indicator LED won't work.

As the controller I used an Atmel ATtiny85 microcontroller. Compared to the ATmega324 and ATmega328p used earlier in this project, the ATtiny does live up to its name: it only has 8 pins (5 of which usable as I/O pins), 8 KB of program memory, and just 512 bytes of RAM. Nevertheless that's way more than enough for a keyboard controller.

The PS/2 protocol uses a start bit, 8 data bits, parity bit and stop bit. The approach I took was very simplistic: just wait in a busy loop for transitions on the clock line, and after 9 bits (start bit + 8 data bits) are received, process the received byte. There's a simple lookup table translating the scan codes sent by the keyboard to their corresponding ASCII values.

There's also a few state variables keeping track of the state of the modifier keys (shift, ctrl, etc.) as well as to handle key release events (scan code 0xF0 followed by the scan code of the key being released).

To interface the keyboard controller with the Z80 CPU, I used a 74HC595 shift register. It  takes 3 lines to hook it up to the ATtiny (data, clock, latch). Data is written by clocking it one bit at a time, and then latched into the register by pulsing the latch pin.


When a key is pressed, the ATtiny writes the ASCII value of the key into the shift register, and when a key is released, a value of 0x00 is written, so at any point the shift register contains the ASCII value of the currently pressed key, or 0x00 if no keys are being pressed. The Z80 can poll the contents of the shift register using the "IN" instruction.

The 8 data output lines of the shift register are hooked up directly to the Z80's data bus. However, they are only enabled when the 74HC595's "G" (gate, or output enable) pin is active (low). When the Z80 "IN" instruction is executed, IORQ and RD are simultaneously active (low). By hooking up IORQ and RD to an OR-gate, the desired behavior is achieved: only when they are both low, the shift register's output will be enabled.

Note that as long as the shift register is the only device attached this way, there's no need for address decoding logic and reading from any I/O address will work.

Pressing the A key on the keyboard (scan code 0x1C). There are 11 clock cycles in the PS/2 protocol: 1 start bit, 8 data bits, a parity bit, and one stop bit. The data is sent to the shift register during the parity bit.


Zooming in: the ASCII value for the "a" key 0x61 is being sent to the shift register.


The AVR C program for the keyboard controller is available on GitHub. The Z80 code to read a key is as follows:

getchar:
  ; Wait for previous key to be released.
  LD A, (lastkey)
  LD B, A
getchar1:
  IN A, (0xFE)
  CP B
  JR Z, getchar1
  ; Wait for a key to be pressed.
getchar2:
  OR A
  JR NZ, getchar3
  IN A, (0xFE)
  JR getchar2
getchar3:
  LD (lastkey), A
  RET
lastchar:
  DB 0x00