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;
}