James Fitzjohn
Published © GPL3+

Raspberry Pi to Z80 interface

Connect a Z80 CPU to your Raspberry Pi and run machine code on it!

ExpertFull instructions providedOver 1 day3,024
Raspberry Pi to Z80 interface

Story

Read more

Schematics

Wiring Diagram

How to connect your GPIO pins to the Z80

Code

Z80 CPU to Raspberry Pi interface

Python
This Python script allows you to interface with the CPU and load Z80 binary's into pseudo RAM
import board
import busio
import time
import curses
import os
import atexit
import array
import sys
from datetime import datetime
from adafruit_mcp230xx.mcp23017 import MCP23017

#setup output
screen = curses.initscr()
curses.noecho()
screen.nodelay(1)
screen.keypad(1)
curses.start_color()
curses.init_pair(1,curses.COLOR_YELLOW,curses.COLOR_BLACK)
curses.init_pair(2,curses.COLOR_GREEN,curses.COLOR_BLACK)
curses.init_pair(3,curses.COLOR_RED,curses.COLOR_BLACK)

#**************************a few functions to make the code easier to read*******************************
def opcodetoram(code):
    bitstring = "{0:08b}".format(int(code,16))
    BYTE = [bool(int(bitstring[7])),bool(int(bitstring[6])),bool(int(bitstring[5])),bool(int(bitstring[4])),bool(int(bitstring[3])),bool(int(bitstring[2])),bool(int(bitstring[1])),bool(int(bitstring[0]))]
    return BYTE

def bool8str(d7,d6,d5,d4,d3,d2,d1,d0): #checking each bit is a little faster than + ing string or .join ing them
    line = ""
    if d7 == True: line = line + "1"
    else: line = line + "0"
    if d6 == True: line = line + "1"
    else: line = line + "0"
    if d5 == True: line = line + "1"
    else: line = line + "0"
    if d4 == True: line = line + "1"
    else: line = line + "0"
    if d3 == True: line = line + "1"
    else: line = line + "0"
    if d2 == True: line = line + "1"
    else: line = line + "0"
    if d1 == True: line = line + "1"
    else: line = line + "0"
    if d0 == True: line = line + "1"
    else: line = line + "0"
    return line

def bool16str(a15,a14,a13,a12,a11,a10,a9,a8,a7,a6,a5,a4,a3,a2,a1,a0):
    line = ""
    if a15 == True: line = line + "1"
    else: line = line + "0"
    if a14 == True: line = line + "1"
    else: line = line + "0"
    if a13 == True: line = line + "1"
    else: line = line + "0"
    if a12 == True: line = line + "1"
    else: line = line + "0"
    if a11 == True: line = line + "1"
    else: line = line + "0"
    if a10 == True: line = line + "1"
    else: line = line + "0"
    if a9 == True: line = line + "1"
    else: line = line + "0"
    if a8 == True: line = line + "1"
    else: line = line + "0"
    if a7 == True: line = line + "1"
    else: line = line + "0"
    if a6 == True: line = line + "1"
    else: line = line + "0"
    if a5 == True: line = line + "1"
    else: line = line + "0"
    if a4 == True: line = line + "1"
    else: line = line + "0"
    if a3 == True: line = line + "1"
    else: line = line + "0"
    if a2 == True: line = line + "1"
    else: line = line + "0"
    if a1 == True: line = line + "1"
    else: line = line + "0"
    if a0 == True: line = line + "1"
    else: line = line + "0"
    return line

def notoascii(number):
    letter = ""
   
    if number == 9: letter = "\t"
    #if number == 10: letter = "\r"
    if number == 13: letter = "\n"
    if number == 32: letter = " "
    if number == 33: letter = "!"
    if number == 34: letter = "\""
    if number == 35: letter = "#"
    if number == 36: letter = "$"
    if number == 37: letter = "%"
    if number == 38: letter = "&"
    if number == 39: letter = "'"
    if number == 40: letter = "("
    if number == 41: letter = ")"
    if number == 42: letter = "*"
    if number == 43: letter = "+"
    if number == 44: letter = ","
    if number == 45: letter = "-"
    if number == 46: letter = "."
    if number == 47: letter = "/"
    if number == 48: letter = "0"
    if number == 49: letter = "1"
    if number == 50: letter = "2"
    if number == 51: letter = "3"
    if number == 52: letter = "4"
    if number == 53: letter = "5"
    if number == 54: letter = "6"
    if number == 55: letter = "7"
    if number == 56: letter = "8"
    if number == 57: letter = "9"
    if number == 58: letter = ":"
    if number == 59: letter = ";"
    if number == 60: letter = "<"
    if number == 61: letter = "="
    if number == 62: letter = ">"
    if number == 63: letter = "?"
    if number == 64: letter = "@"
    if number == 65: letter = "A"
    if number == 66: letter = "B"
    if number == 67: letter = "C"
    if number == 68: letter = "D"
    if number == 69: letter = "E"
    if number == 70: letter = "F"
    if number == 71: letter = "G"
    if number == 72: letter = "H"
    if number == 73: letter = "I"
    if number == 74: letter = "J"
    if number == 75: letter = "K"
    if number == 76: letter = "L"
    if number == 77: letter = "M"
    if number == 78: letter = "N"
    if number == 79: letter = "O"
    if number == 80: letter = "P"
    if number == 81: letter = "Q"
    if number == 82: letter = "R"
    if number == 83: letter = "S"
    if number == 84: letter = "T"
    if number == 85: letter = "U"
    if number == 86: letter = "V"
    if number == 87: letter = "W"
    if number == 88: letter = "X"
    if number == 89: letter = "Y"
    if number == 90: letter = "Z"
    if number == 91: letter = "["
    if number == 92: letter = "\\"
    if number == 93: letter = "]"
    if number == 94: letter = "^"
    if number == 95: letter = "_"
    if number == 96: letter = "`"
    if number == 97: letter = "a"
    if number == 98: letter = "b"
    if number == 99: letter = "c"
    if number == 100: letter = "d"
    if number == 101: letter = "e"
    if number == 102: letter = "f"
    if number == 103: letter = "g"
    if number == 104: letter = "h"
    if number == 105: letter = "i"
    if number == 106: letter = "j"
    if number == 107: letter = "k"
    if number == 108: letter = "l"
    if number == 109: letter = "m"
    if number == 110: letter = "n"
    if number == 111: letter = "o"
    if number == 112: letter = "p"
    if number == 113: letter = "q"
    if number == 114: letter = "r"
    if number == 115: letter = "s"
    if number == 116: letter = "t"
    if number == 117: letter = "u"
    if number == 118: letter = "v"
    if number == 119: letter = "w"
    if number == 120: letter = "x"
    if number == 121: letter = "y"
    if number == 122: letter = "z"
    if number == 123: letter = "{"
    if number == 124: letter = "|"
    if number == 125: letter = "}"
    if number == 126: letter = "~"
    return letter


#************************************setup ram**********************************************************
pRAM = []


def resetram():
    x = 0
    numberofbytes = 65536
    while x < numberofbytes:
        pRAM.append([False,False,False,False,False,False,False,False])
        x = x + 1

    #if we have an input file read it into RAM else load a smaple program
    filename = ""
    address = 0
    if sys.argv[1:]:
        filename = sys.argv[1]
    
        file = open(filename,"rb")
        byte = file.read(1)
        while byte:
            number = int.from_bytes(byte,byteorder='big')
            line = format(int(number),'02x')
            line = line.upper()
            pRAM[address] = opcodetoram(line)
            address = address + 1
            byte = file.read(1)
    
        file.close()
    else:
        
        pRAM[0] = opcodetoram("C3") #jmp 16
        pRAM[1] = opcodetoram("10")
        pRAM[2] = opcodetoram("00") 
    
        pRAM[16] = opcodetoram("3E")#Draw a circle in bits... i.e. hello world
        pRAM[17] = opcodetoram("18")
        pRAM[18] = opcodetoram("21")
        pRAM[19] = opcodetoram("03")
        pRAM[20] = opcodetoram("00")
        pRAM[21] = opcodetoram("77")
        pRAM[22] = opcodetoram("21")
        pRAM[23] = opcodetoram("08")
        pRAM[24] = opcodetoram("00")
        pRAM[25] = opcodetoram("77")

        pRAM[26] = opcodetoram("3E")
        pRAM[27] = opcodetoram("24")
        pRAM[28] = opcodetoram("21")
        pRAM[29] = opcodetoram("04")
        pRAM[30] = opcodetoram("00")
        pRAM[31] = opcodetoram("77")
        pRAM[32] = opcodetoram("21")
        pRAM[33] = opcodetoram("07")
        pRAM[34] = opcodetoram("00")
        pRAM[35] = opcodetoram("77")

        pRAM[36] = opcodetoram("3E")
        pRAM[37] = opcodetoram("42")
        pRAM[38] = opcodetoram("21")
        pRAM[39] = opcodetoram("05")
        pRAM[40] = opcodetoram("00")
        pRAM[41] = opcodetoram("77")
        pRAM[42] = opcodetoram("21")
        pRAM[43] = opcodetoram("06")
        pRAM[44] = opcodetoram("00")
        pRAM[45] = opcodetoram("77")

        pRAM[46] = opcodetoram("C3")
        pRAM[47] = opcodetoram("00")
        pRAM[49] = opcodetoram("00")# jump back to the start


resetram()
   
#******************************************setup the hardware and variables*********************************

#setup i2c boards
li2c = busio.I2C(board.SCL, board.SDA)
ri2c = busio.I2C(board.SCL, board.SDA)
bi2c = busio.I2C(board.SCL, board.SDA)
left = MCP23017(li2c, address=0x20)
right = MCP23017(ri2c, address=0x21)
bottom = MCP23017(bi2c, address=0x22)


#the left i2c GPIO extender handles the address bus
a0 = left.get_pin(0)
a1 = left.get_pin(1)
a2 = left.get_pin(2)
a3 = left.get_pin(3)
a4 = left.get_pin(4)
a5 = left.get_pin(5)
a6 = left.get_pin(6)
a7 = left.get_pin(7)
a8 = left.get_pin(8)
a9 = left.get_pin(9)
a10 = left.get_pin(10)
a11 = left.get_pin(11)
a12 = left.get_pin(12)
a13 = left.get_pin(13)
a14 = left.get_pin(14)
a15 = left.get_pin(15)

#The right i2c GPIO extender handles the data bus
d0 = right.get_pin(0)
d1 = right.get_pin(1)
d2 = right.get_pin(2)
d3 = right.get_pin(3)
d4 = right.get_pin(4)
d5 = right.get_pin(5)
d6 = right.get_pin(6)
d7 = right.get_pin(7)

#The bottom i2c GPIO extender handles the other IO
clockpin = bottom.get_pin(0)
rdpin = bottom.get_pin(1)
wrpin = bottom.get_pin(2)
reset = bottom.get_pin(3)
power = bottom.get_pin(15)
power.switch_to_output(value=True)

#set address lines to read
a0.switch_to_input()
a1.switch_to_input()
a2.switch_to_input()
a3.switch_to_input()
a4.switch_to_input()
a5.switch_to_input()
a6.switch_to_input()
a7.switch_to_input()
a8.switch_to_input()
a9.switch_to_input()
a10.switch_to_input()
a11.switch_to_input()
a12.switch_to_input()
a13.switch_to_input()
a14.switch_to_input()
a15.switch_to_input()

d0.switch_to_output(value=False)
d1.switch_to_output(value=False)
d2.switch_to_output(value=False)
d3.switch_to_output(value=False)
d4.switch_to_output(value=False)
d5.switch_to_output(value=False)
d6.switch_to_output(value=False)
d7.switch_to_output(value=False)

clockpin.switch_to_output(value=True)
rdpin.switch_to_input()
wrpin.switch_to_input()
reset.switch_to_output(value=True)


clock = True
ticks = 0
pause = False
ticklen = 0.001
running = True
plastaddrline = ""
debugline = ""
memaddr = 0
viewoffset = 1000
cleanreset = False
syncdelay = 4
reset.value = False 
resetticks = 0
oldflagline = ""
olddebugline = ""
poldoutputchar = ""
pConsoleline = []
pConsoleline.append("")
pConsoleline.append("")
pConsoleline.append("")
pConsoleline.append("")
pConsoleline.append("")
ticcounter = 0
pConsoleno = 0
frame = 0
rframe = 10 
direction = ""
physicalcpu = True 
pwr = True
prd = True

if sys.argv[1:]:
    frame = 16
    rframe = 20 

else:
    ticklen = 0.001
    rframe = 1
    viewoffset = 0


while resetticks < 5:
    resetticks = resetticks + 1
    clockpin.value = False
    time.sleep(ticklen)
    clockpin.value = True
    time.sleep(ticklen)
reset.value = True

while running == True:

        #*************************************dont render EVERY clock tick*******************************
        frame = frame + 1        
        if frame == rframe: screen.erase()

        #******************check if were paused, if not alternate the clock pin***************************
        if pause == False:
            ticks = ticks + 1
            bottom.gpio = bottom.gpio + 1 
        
        #**********************************Title**********************************************************
        line = "Z80 Interface"

        if frame == rframe: 
            screen.addstr(0,0,line)
            screen.addstr(2,0,"Total clock ticks = " + str(ticks))
 

        #********************************check the read write pins ********************************************  
        
        bot = format(bottom.gpio,'016b')
        if bot[14] == "1": prd = 1
        else: prd = 0
        if bot[13] == "1": pwr = 1
        else: pwr = 0

        if frame == rframe: screen.addstr(5,0,"Read = " + str(prd) + " Write = " + str(pwr))
        x = left.gpio
        pbinaddr = format(x,'016b')
        phexaddr = format(int(pbinaddr,2),'04X')

        if frame == rframe: screen.addstr(4,0,"**Physical Z80**",curses.color_pair(1))
 
        
        if pwr == False:#write contents of data bus to address in RAM
            if direction != "input":
                right.iodir = 65535
            direction = "input"
            databus = format(right.gpio,'08b')
            pRAM[x] = [bool(int(databus[7])),bool(int(databus[6])),bool(int(databus[5])),bool(int(databus[4])),bool(int(databus[3])),bool(int(databus[2])),bool(int(databus[1])),bool(int(databus[0]))]
            memaddr = x

        if prd == False:
            if direction != "output": 
                right.iodir = 65280
            memaddr = x
            right.gpio = int(bool8str(pRAM[x][7],pRAM[x][6],pRAM[x][5],pRAM[x][4],pRAM[x][3],pRAM[x][2],pRAM[x][1],pRAM[x][0]),2)
            direction = "output"

        if syncdelay > 0: syncdelay = syncdelay - 1


        #****************************************Whats on the busses**************************************
        #physical Z80
        if physicalcpu == True and frame == rframe:
            bindata = bool8str(pRAM[memaddr][7],pRAM[memaddr][6],pRAM[memaddr][5],pRAM[memaddr][4],pRAM[memaddr][3],pRAM[memaddr][2],pRAM[memaddr][1],pRAM[memaddr][0])
            hexdata = int(bindata,2)
            hexdata = format(hexdata,'02X')
            hexdata.rjust(4,'0')
            screen.addstr(6,0,"Data bus:" + bindata + " (" + hexdata + ")")
            if prd == False or pwr == False:#wait until the cpu is ready
                plastaddrline = "Address bus:" + pbinaddr + " (" + phexaddr + ")"               
            screen.addstr(7,0,plastaddrline)


        #********************************************Show RAM********************************************

        if frame == rframe:
            screen.addstr(9,0,"Pseudo RAM",curses.color_pair(1))
            x = viewoffset 
        
            while x < (viewoffset +16):
                #Physical RAM
                pramstring = bool8str(pRAM[x][7],pRAM[x][6],pRAM[x][5],pRAM[x][4],pRAM[x][3],pRAM[x][2],pRAM[x][1],pRAM[x][0])
                phexramstring = format(int(pramstring,2),'02X')
                pzero_addr = str(x)
                pzero_addr = pzero_addr.zfill(5)
            
                if memaddr == x: screen.addstr((10+x)-viewoffset,29,"<- PC",curses.color_pair(1))
                screen.addstr((10+x)-viewoffset,0,"Address " + pzero_addr + ": " + pramstring + " (" + phexramstring.upper() + ")")

                x = x + 1
            
        #**********************************show a few lines of the srack************************************    
        if frame == rframe:
            if physicalcpu == True:
                y = 65535
                linecount = 28
                screen.addstr(27,0,"Pseudo stack",curses.color_pair(1))
                while y > 65526:
                    pramstring = bool8str(pRAM[y][7],pRAM[y][6],pRAM[y][5],pRAM[y][4],pRAM[y][3],pRAM[y][2],pRAM[y][1],pRAM[y][0])
                    phexramstring = format(int(pramstring,2),'02x')
                    screen.addstr(linecount,0,"Address " + str(y) + ": " + pramstring + " (" + phexramstring.upper() + ")")
                    y = y - 1
                    linecount = linecount + 1
         

        #***********************************console output****************************************************
        if physicalcpu == True:        
            pcharacter = bool8str(pRAM[32][7],pRAM[32][6],pRAM[32][5],pRAM[32][4],pRAM[32][3],pRAM[32][2],pRAM[32][1],pRAM[32][0])
            if poldoutputchar != pcharacter: 
                poldoutputchar = pcharacter

                if notoascii(int(pcharacter,2)) != "\n": pConsoleline[pConsoleno] = pConsoleline[pConsoleno] + notoascii(int(pcharacter,2))
                else:
                    pConsoleno = pConsoleno + 1
                    if pConsoleno == 5: 
                        pConsoleno = 4

                        pConsoleline[0] = pConsoleline[1]
                        pConsoleline[1] = pConsoleline[2]
                        pConsoleline[2] = pConsoleline[3]
                        pConsoleline[3] = pConsoleline[4]
                        pConsoleline[4] = ""

            if frame == rframe:
                screen.addstr(38,0,"Console output at 20h",curses.color_pair(1))
                screen.addstr(39,0,pConsoleline[0])
                screen.addstr(40,0,pConsoleline[1])
                screen.addstr(41,0,pConsoleline[2])
                screen.addstr(42,0,pConsoleline[3])
                screen.addstr(43,0,pConsoleline[4])

        
       #*********************************************UI control*******************************************
        if frame == rframe: 
           input = screen.getch()
           if input == ord('r') or cleanreset == False: #reset must be active for at least 3 clock ticks
                resetticks = 0
                pRAM.clear()
                resetram()
                reset.value = False
                while resetticks < 3:
                     resetticks = resetticks + 1
                     clockpin.value = False
                     time.sleep(ticklen)
                     clockpin.value = True
                     time.sleep(ticklen)
                reset.value = True
                cleanreset = True
                syncdelay = 4
                pConsoleline[0] = ""
                pConsoleline[1] = ""
                pConsoleline[2] = ""
                pConsoleline[3] = ""
                pConsoleline[4] = ""
                pConsoleno = 0
 
           if input == ord('q'): #quit
                curses.nocbreak()
                curses.echo()
                curses.endwin()
                running = False

           #quick and easy way to scroll through ram
           if input == curses.KEY_DOWN:
                viewoffset = viewoffset + 16
                if viewoffset > 65535: viewoffset = (65535 - 16)

           if input == curses.KEY_UP:
                 viewoffset = viewoffset - 16
                 if viewoffset < 0: viewoffset = 0

           if input == curses.KEY_RIGHT:
                viewoffset = viewoffset + 1000
                if viewoffset > 65535: viewoffset = (65535 - 16)

           if input == curses.KEY_LEFT:
                viewoffset = viewoffset - 1000
                if viewoffset < 0: viewoffset = 0
        
           if input == ord('p'): pause = not pause
        
        
        if frame == rframe: 
            screen.refresh()
            frame = 0

        #we dont really need a high/low tick delay as the GPIO slows execution down way more that 1hz of 4mhz 
        if not sys.argv[1:]: time.sleep(ticklen)
        if pause == False: bottom.gpio = bottom.gpio - 1

 
def exit_handler():#clean up of curses 
    os.system('stty sane')
    power.value = False
atexit.register(exit_handler)

Credits

James Fitzjohn

James Fitzjohn

1 project • 1 follower

Comments