Posts
Search
Contact
Cookies
About
RSS

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...