This is part three in my slow journey towards creating a home-brew Z80-based computer. My previous post demonstrated writing some simple code, and getting it running under an emulator. It also described my planned approach:
- Hookup a Z80 processor to an Arduino Mega.
- Run code on the Arduino to emulate RAM reads/writes and I/O.
- Profit, via the learning process.
I expect I'll have to get my hands-dirty with a breadboard and naked chips in the near future, but for the moment I decided to start with the least effort. Erturk Kocalar has a website where he sells "shields" (read: expansion-boards) which contain a Z80, and which is designed to plug into an Arduino Mega with no fuss. This is a simple design, I've seen a bunch of people demonstrate how to wire up by hand, for example this post.
Anyway I figured I'd order one of those, and get started on the easy-part, the software. There was some sample code available from Erturk, but it wasn't ideal from my point of view because it mixed driving the Z80 with doing "other stuff". So I abstracted the core code required to interface with the Z80 and packaged it as a simple library.
The end result is that I have a z80 retroshield library which uses an Arduino mega to drive a Z80 with something as simple as this:
#include <z80retroshield.h>
//
// Our program, as hex.
//
unsigned char rom[32] =
{
0x3e, 0x48, 0xd3, 0x01, 0x3e, 0x65, 0xd3, 0x01, 0x3e, 0x6c, 0xd3, 0x01,
0xd3, 0x01, 0x3e, 0x6f, 0xd3, 0x01, 0x3e, 0x0a, 0xd3, 0x01, 0xc3, 0x16,
0x00
};
//
// Our helper-object
//
Z80RetroShield cpu;
//
// RAM I/O function handler.
//
char ram_read(int address)
{
return (rom[address]) ;
}
// I/O function handler.
void io_write(int address, char byte)
{
if (address == 1)
Serial.write(byte);
}
// Setup routine: Called once.
void setup()
{
Serial.begin(115200);
//
// Setup callbacks.
//
// We have to setup a RAM-read callback, otherwise the program
// won't be fetched from RAM and executed.
//
cpu.set_ram_read(ram_read);
//
// Then we setup a callback to be executed every time an "out (x),y"
// instruction is encountered.
//
cpu.set_io_write(io_write);
//
// Configured.
//
Serial.println("Z80 configured; launching program.");
}
//
// Loop function: Called forever.
//
void loop()
{
// Step the CPU.
cpu.Tick();
}
All the logic of the program is contained in the Arduino-sketch, and all the use of pins/ram/IO is hidden away. As a recap the Z80 will make requests for memory-contents, to fetch the instructions it wants to execute. For general purpose input/output there are two instructions that are used:
IN A, (1) ; Read a character from STDIN, store in A-register.
OUT (1), A ; Write the character in A-register to STDOUT
Here 1
is the I/O address, and this is an 8 bit number. At the moment
I've just configured the callback such that any write to I/O address 1
is dumped to the serial console.
Anyway I put together a couple of examples of increasing complexity, allowing me to prove that RAM read/writes work, and that I/O reads and writes work.
I guess the next part is where I jump in complexity:
- I need to wire a physical Z80 to a board.
- I need to wire a PROM to it.
- This will contain the program to be executed - hardcoded.
- I need to provide power, and a clock to make the processor tick.
With a bunch of LEDs I'll have a Z80-system running, but it'll be isolated and hard to program. (Since I'll need to reflash the RAM/ROM-chip).
The next step would be getting it hooked up to a serial-console of some sort. And at that point I'll have a genuinely programmable standalone Z80 system.
Tags: arduino, arduino mega, computer-building, github, gitlab, retroshield, z80 No comments