Realizziamo una shield per Arduino con uno Z80! Schema elettrico e test su breadboard

Creiamo insieme una versione ridotta ma che permette tante sperimentazioni del nostro sistema basato su microprocessore Z80. Lo chiameremo ALEX80 µ. Vediamo nel dettaglio lo schema elettrico, il codice per Arduino e verifichiamo il funzionamento su breadboard.

👉 Funzionamento shift register: https://youtu.be/szU_IyQelfo

👉Demultiplexer: https://youtu.be/aDTcSDCQh4E

/*****************************************************
Sketch per Arduino UNO utilizzato nel test 
su breadboard del sistema
ALEX80µ presentato nel video YouTube:

https://youtu.be/Q2Qd3GqFMbY

Paolo Godino
Alexa Academy
2025

******************************************************/
#include "Arduino.h"

#define DEBUG_PRINT

#define Z80_CLK   2

#define Z80_D0    3
#define Z80_D1    4
#define Z80_D2    5
#define Z80_D3    6
#define Z80_D4    7
#define Z80_D5    8
#define Z80_D6    9
#define Z80_D7    10

#define Z80_INT     A0
#define Z80_NMI     A1
#define Z80_RST     A2  
#define Z80_BUSREQ  A3
//#define Z80_WAIT    A4
#define ERAM_DATA   A5

#define SERIAL_IN   11
#define SHIFT_CLK   12
//#define SHIFT_LD    13

#define A_DEMUX   A4
#define B_DEMUX   13

#define STOP_CLOCK     B00000000
#define PRESCALER_1    B00000001
#define PRESCALER_8    B00000010
#define PRESCALER_64   B00000011
#define PRESCALER_256  B00000100
#define PRESCALER_1024 B00000101

#define DEMUX_ST_NONE 0
#define DEMUX_ST_SHIFT_LD 1
#define DEMUX_ST_ERAM_CS 2
#define DEMUX_ST_WAIT 3

// Questo codice su Z80 accende in sequenza i bit del 74HC273 con un loop infinito
byte ROM[] = {0xAF, 0xD3, 0x00, 0x3C, 0xC3, 0x01, 0x00 };

volatile bool performReset = false;
volatile unsigned int tick = 0;
volatile unsigned int cycle = 0;

volatile bool z80_busak;
volatile bool z80_rfsh;
volatile bool z80_m1;
volatile bool z80_wr;
volatile bool z80_rd;
volatile bool z80_ioreq;
volatile bool z80_mreq;
volatile bool z80_halt;
volatile uint16_t z80_add;

unsigned int freq;

void calcOCR(unsigned int freq, unsigned int* ocr, byte* prescalerMask) {
  long desiredFreq = freq;
  long prescaler;

  if (freq < 2) {
    prescaler = 256;
    *prescalerMask = PRESCALER_256;
  } else if (freq < 20) {
    prescaler = 64;
    *prescalerMask = PRESCALER_64;
  } else if (freq < 150) {
    prescaler = 8;
    *prescalerMask = PRESCALER_8;
  } else if (freq < 30000) {
    prescaler = 1;
    *prescalerMask = PRESCALER_1;
  } else {
    desiredFreq = 30000;
    prescaler = 1;
    *prescalerMask = PRESCALER_1;
  }
  
  *ocr = 16000000L / (prescaler * desiredFreq * 2) + 1;
}

void startClock(unsigned int f) {
  unsigned int ocr;
  byte prescalerMask;

  freq = f;

  calcOCR(f, &ocr, &prescalerMask);

  cli();

  TCCR1A = 0;
  TCCR1B = TCCR1B & B11100000 | B00001000 | prescalerMask;
  TCNT1 = 0;  // Inizializza il counter
  OCR1A = ocr;
  TIMSK1 = 0b00000010; // Abilita interrupt su conteggio OCR1A

  sei();
}

void setDataBusOutput(bool isOutput) {
  pinMode(Z80_D0, isOutput?OUTPUT:INPUT);
  pinMode(Z80_D1, isOutput?OUTPUT:INPUT);
  pinMode(Z80_D2, isOutput?OUTPUT:INPUT);
  pinMode(Z80_D3, isOutput?OUTPUT:INPUT);
  pinMode(Z80_D4, isOutput?OUTPUT:INPUT);
  pinMode(Z80_D5, isOutput?OUTPUT:INPUT);
  pinMode(Z80_D6, isOutput?OUTPUT:INPUT);
  pinMode(Z80_D7, isOutput?OUTPUT:INPUT);
}

void writeData(byte b) {
  digitalWrite(Z80_D0, bitRead(b, 0));
  digitalWrite(Z80_D1, bitRead(b, 1));
  digitalWrite(Z80_D2, bitRead(b, 2));
  digitalWrite(Z80_D3, bitRead(b, 3));
  digitalWrite(Z80_D4, bitRead(b, 4));
  digitalWrite(Z80_D5, bitRead(b, 5));
  digitalWrite(Z80_D6, bitRead(b, 6));
  digitalWrite(Z80_D7, bitRead(b, 7));
}

byte readData() {
  bool d7 = digitalRead(Z80_D7);
  bool d6 = digitalRead(Z80_D6);
  bool d5 = digitalRead(Z80_D5);
  bool d4 = digitalRead(Z80_D4);
  bool d3 = digitalRead(Z80_D3);
  bool d2 = digitalRead(Z80_D2);
  bool d1 = digitalRead(Z80_D1);
  bool d0 = digitalRead(Z80_D0);

  byte data_bus = d7<<7 | d6<<6 | d5<<5 | d4<<4 | d3<<3 | d2<<2 | d1<<1 | d0;

  return data_bus;
}

void doReset() {
  digitalWrite(Z80_RST, LOW);
  performReset = true;
  cycle=0;
}

// Clock cycle
ISR(TIMER1_COMPA_vect) {
  tick++;
  if (tick%2 == 0) {
    digitalWrite(Z80_CLK, LOW);
    cycle++;
    readShiftRegisters();
  } else {
    digitalWrite(Z80_CLK, HIGH);
  }

  if (performReset && cycle > 7) {
    performReset = false;
    digitalWrite(Z80_RST, HIGH);
  }
}

void ClockTrigger() {
  if (z80_mreq == LOW && z80_rd == LOW) {
    if (z80_add >= 0 && z80_add < sizeof(ROM)) {
      setDataBusOutput(true);
      //writeData(ROM[z80_add]);
      writeData(readByte(z80_add));
    } else {
      setDataBusOutput(true);
      writeData(0);
    }
  } else {
    setDataBusOutput(false);
  }

#ifdef DEBUG_PRINT
  Serial.print("Clock #");
  Serial.print(cycle);

  Serial.print("  Indirizzo: ");
  Serial.print(z80_add, HEX);

  Serial.print("  Dati: ");
  bool d7 = digitalRead(Z80_D7);
  bool d6 = digitalRead(Z80_D6);
  bool d5 = digitalRead(Z80_D5);
  bool d4 = digitalRead(Z80_D4);
  bool d3 = digitalRead(Z80_D3);
  bool d2 = digitalRead(Z80_D2);
  bool d1 = digitalRead(Z80_D1);
  bool d0 = digitalRead(Z80_D0);

  Serial.print(d7);
  Serial.print(d6);
  Serial.print(d5);
  Serial.print(d4);
  Serial.print(d3);
  Serial.print(d2);
  Serial.print(d1);
  Serial.print(d0);

  byte data_bus = d7<<7 | d6<<6 | d5<<5 | d4<<4 | d3<<3 | d2<<2 | d1<<1 | d0; 
  Serial.print(" (");
  Serial.print(data_bus, HEX);
  Serial.print(")  ");  

  Serial.print("   R:");
  Serial.print(z80_rd);
  
  Serial.print("   W:");
  Serial.print(z80_wr);
  
  Serial.print("   MREQ:");
  Serial.print(z80_mreq);

  Serial.print("   IOREQ:");
  Serial.print(z80_ioreq);
 
  Serial.print("   RFSH:");
  Serial.print(z80_rfsh);
 
  Serial.print("   M1:");
  Serial.print(z80_m1);

  Serial.println("");
#endif
}

void readShiftRegisters() {
    uint32_t data = 0;

    //digitalWrite(SHIFT_LD, LOW);  // Carica i dati paralleli negli shift register
    setDemux(DEMUX_ST_SHIFT_LD);
    delayMicroseconds(5);
    //digitalWrite(SHIFT_LD, HIGH); // Disabilita il parallel load
    setDemux(DEMUX_ST_NONE);

    for (int i = 0; i < 24; i++) {
        data <<= 1;
        if (digitalRead(SERIAL_IN)) {
            data |= 1;
        }
        digitalWrite(SHIFT_CLK, HIGH);
        delayMicroseconds(5);
        digitalWrite(SHIFT_CLK, LOW);
        delayMicroseconds(5);
    }

    z80_add = data&0xffff;
    byte b = data>>16;

    z80_busak = bitRead(b, 7);
    z80_rfsh = bitRead(b, 6);
    z80_m1 = bitRead(b, 5);
    z80_wr = bitRead(b, 4);
    z80_rd = bitRead(b, 3);
    z80_ioreq = bitRead(b, 2);
    z80_mreq = bitRead(b, 1);
    z80_halt = bitRead(b, 0);
}

void setDemux(int state) {
  switch(state) {
    case DEMUX_ST_NONE:
      digitalWrite(A_DEMUX, LOW);
      digitalWrite(B_DEMUX, LOW);
      break;
    case DEMUX_ST_SHIFT_LD:
      digitalWrite(A_DEMUX, HIGH);
      digitalWrite(B_DEMUX, LOW);
      break;
    case DEMUX_ST_ERAM_CS:
      digitalWrite(A_DEMUX, LOW);
      digitalWrite(B_DEMUX, HIGH);
      break;
    case DEMUX_ST_WAIT:
    digitalWrite(A_DEMUX, HIGH);
      digitalWrite(B_DEMUX, HIGH);
      break;
  }
}

void setup() {
  Serial.begin(9600);

  setDataBusOutput(false);

  pinMode(ERAM_DATA, INPUT);

  pinMode(Z80_INT, INPUT);
  pinMode(Z80_NMI, INPUT);
  pinMode(Z80_RST, OUTPUT);
  pinMode(Z80_BUSREQ, OUTPUT);
  //pinMode(Z80_WAIT, INPUT);
  //pinMode(Z80_BUSACK, INPUT);

  pinMode(Z80_CLK, OUTPUT);

  pinMode(SERIAL_IN, INPUT);
  pinMode(SHIFT_CLK, OUTPUT);
  //pinMode(SHIFT_LD, OUTPUT);

  pinMode(A_DEMUX, OUTPUT);
  pinMode(B_DEMUX, OUTPUT);

  setDemux(DEMUX_ST_NONE);

  attachInterrupt(digitalPinToInterrupt(Z80_CLK), ClockTrigger, RISING);

  digitalWrite(Z80_BUSREQ, HIGH);

  digitalWrite(Z80_CLK, LOW);
  digitalWrite(Z80_RST, HIGH);

  digitalWrite(SHIFT_CLK, LOW);
  //digitalWrite(SHIFT_LD, HIGH);

  doReset();

  for (int i=0; i<sizeof(ROM); ++i) writeByte(i, ROM[i]);

  Serial.println("Letto: ");
  Serial.println(readByte(0x0000), HEX);
  Serial.println(readByte(0x0001), HEX); 
  Serial.println(readByte(0x0002), HEX);
  Serial.println(readByte(0x0003), HEX);
  Serial.println(readByte(0x0004), HEX);
  Serial.println(readByte(0x0005), HEX);
  Serial.println(readByte(0x0006), HEX);

  startClock(4);
}

void loop() {

}




void writeByte(uint32_t addr, byte val) {
  //digitalWrite(CS_PIN, LOW);
  setDemux(DEMUX_ST_ERAM_CS);
  sendByte(0x02); // WRITE command
  sendByte((addr >> 16) & 0xFF);
  sendByte((addr >> 8) & 0xFF);
  sendByte(addr & 0xFF);
  sendByte(val);
  //digitalWrite(CS_PIN, HIGH);
  setDemux(DEMUX_ST_NONE);
}

byte readByte(uint32_t addr) {
  //digitalWrite(CS_PIN, LOW);
  setDemux(DEMUX_ST_ERAM_CS);
  sendByte(0x03); // READ command
  sendByte((addr >> 16) & 0xFF);
  sendByte((addr >> 8) & 0xFF);
  sendByte(addr & 0xFF);
  byte result = receiveByte();
  //digitalWrite(CS_PIN, HIGH);
  setDemux(DEMUX_ST_NONE);
  return result;
}

void sendByte(byte b) {
  pinMode(ERAM_DATA, OUTPUT);
  for (int i = 7; i >= 0; i--) {
    digitalWrite(ERAM_DATA, (b >> i) & 1);
    digitalWrite(SHIFT_CLK, HIGH);
    delayMicroseconds(1);
    digitalWrite(SHIFT_CLK, LOW);
  }
}

byte receiveByte() {
  byte b = 0;
  pinMode(ERAM_DATA, INPUT);
  for (int i = 7; i >= 0; i--) {
    digitalWrite(SHIFT_CLK, HIGH);
    delayMicroseconds(1);
    b |= (digitalRead(ERAM_DATA) << i);
    digitalWrite(SHIFT_CLK, LOW);
  }
  return b;
}
Avatar Paolo Godino