Using an Arduino to run a Z80
Added 3 Apr 2017, 6:42 p.m. edited 18 Jun 2023, 7:46 p.m.
Years ago I had the bright idea of using a micro controller to support a Z80 to make a simple computer. After ordering the parts for various reasons it never got done, looking now there are a tonne of people who have had the same idea, so taking the parts out of a dusty container I decided to use the various sites as a guide and do my own take. Taking the diagram here (
http://baltazarstudios.com/arduino-zilog-z80/) as a starting point, I decided to simplify things by dispensing with the detection of the bus tri-state. As I only need memory writes and reads with the Arduino controlling the clock there is no need for wait control and we can also do without interrupts and IO ports. Because many of the inputs on the Z80 are active low, some of the pins need pulling high (I used 460K resistors as I had these handy)
NMI, INT, BUSRQ and WAIT all tied high allows the Z80 to run providing there is a clock signal.
I'm using an Arduino Mega as again its just what I happen to have at hand, as long as you avoid any special use pins you might need for other things there's no special requirements, but so I can change my mind later the first thing I tackled with the Arduino code was a bunch of defines, I prefaced all the Z80 signals with lower case z followed by the pin name used in the Zilog Z80 User Manual.
#define zCLK 13
#define zMREQ 53
#define zRD 47
#define zWR 49
#define zRESET 51
#define zD0 A8
#define zD1 A9
#define zD2 A10
#define zD3 A11
#define zD4 A12
#define zD5 A13
#define zD6 A14
#define zD7 A15
#define zA0 A0
#define zA1 A1
#define zA2 A2
#define zA3 A3
#define zA4 A4
#define zA5 A5
#define zA6 A6
#define zA7 A7
#define zA8 45
5 control signals, 8 pins for the data bus and I'm using just 9 pins for the address bus (just so I could double check byte ordering in instruction data) so really any micro controller with 20 or so pins and you're good to go.
First off we need to write and (oh the joy!) hand compile some assembly language...
byte zRAM[1024] = {
0x3e, 0x78, // ld a,#78
0xC6, 0x01, // add a,#1
0x32, 0x20, 0x01, // ld (#0120),a
0xC3, 0x02, 0x00 // jmp 0002
};
Very basic stuff, but just enough that it will only work if our emulation of RAM and support circuit actually works...
When writing or reading from any pin on an Arduino you have to set the pin into either output or input mode, in order to save resetting especially the data pins every time we read or write I've kept track of what mode the pins are in and only change them if needed
void writeZd(byte d) {
if (dIN) {
pinMode(zD0,OUTPUT);
pinMode(zD1,OUTPUT);
pinMode(zD2,OUTPUT);
pinMode(zD3,OUTPUT);
pinMode(zD4,OUTPUT);
pinMode(zD5,OUTPUT);
pinMode(zD6,OUTPUT);
pinMode(zD7,OUTPUT);
dIN = false;
}
digitalWrite(zD0, (d & 1) ? HIGH : LOW);
digitalWrite(zD1, (d & (1<<1)) ? HIGH : LOW);
digitalWrite(zD2, (d & (1<<2)) ? HIGH : LOW);
digitalWrite(zD3, (d & (1<<3)) ? HIGH : LOW);
digitalWrite(zD4, (d & (1<<4)) ? HIGH : LOW);
digitalWrite(zD5, (d & (1<<5)) ? HIGH : LOW);
digitalWrite(zD6, (d & (1<<6)) ? HIGH : LOW);
digitalWrite(zD7, (d & (1<<7)) ? HIGH : LOW);
}
byte readZd() {
if (!dIN) {
pinMode(zD0,INPUT); digitalWrite(zD0, LOW);
pinMode(zD1,INPUT); digitalWrite(zD1, LOW);
pinMode(zD2,INPUT); digitalWrite(zD2, LOW);
pinMode(zD3,INPUT); digitalWrite(zD3, LOW);
pinMode(zD4,INPUT); digitalWrite(zD4, LOW);
pinMode(zD5,INPUT); digitalWrite(zD5, LOW);
pinMode(zD6,INPUT); digitalWrite(zD6, LOW);
pinMode(zD7,INPUT); digitalWrite(zD7, LOW);
dIN = true;
}
return digitalRead(zD0) |
(digitalRead(zD1)<<1) |
(digitalRead(zD2)<<2) |
(digitalRead(zD3)<<3) |
(digitalRead(zD4)<<4) |
(digitalRead(zD5)<<5) |
(digitalRead(zD6)<<6) |
(digitalRead(zD7)<<7);
}
These two functions allow us to read the data bus or set it while keeping track of the state of the arduinos port.
As I'm only ever reading from the address bus its just a simple bunch of bit shifted bits on a return statement, the arduino port control being set once and left.
Resetting the Z80 is required to get it to run stably, this is just a case of asserting the reset pin (LOW is active) and running the clock for at least three cycles.
The main loop is really very simple
void loop() {
if (runLimit>0) {
// runLimit--; // limit ram writes instead of clocks
} else {
return;
}
digitalWrite(zCLK,HIGH);
delayMicroseconds(1);
digitalWrite(zCLK,LOW);
if(!digitalRead(zMREQ)) { // Active low
short a = getZaddress();
if (!digitalRead(zRD)) {
byte d = zRAM[a];
writeZd(d); // put requested data on Z80 data bus
/*
sprintf(pstr, "%04x\0", a);
Serial.print(pstr);
Serial.print(" READ ");
sprintf(pstr, "%02x\0", d);
Serial.print(pstr);
Serial.println();
*/
}
if (!digitalRead(zWR)) {
byte d = readZd();
zRAM[a]=d;
sprintf(pstr, "%04x\0", a);
Serial.print(pstr);
Serial.print(" WRITE ");
sprintf(pstr, "%02x\0", d);
Serial.print(pstr);
Serial.println();
runLimit--; // stop running after n writes to ram
}
}
}
I'm limiting the Z80's session to a finite number of writes to ram this way you're not flooding the serial terminal when you're using the IDE or flashing a new version of the control software to the Arduino.
In my dusty box of neglected parts I have some static ram and even an eeprom, so I figure future experiments may well lean towards using another micro controller to program the eeprom in order that gradually less and less support functions are implemented with the Arduino. All I'm really missing is some kind of display that is reasonably in tune with a retro vibe and I should have everything I need to make my own retro machine...