// Copyright Terence J. Boldt (c)2020-2024 // Use of this source code is governed by an MIT // license that can be found in the LICENSE file. // This file is used for communicating with the Apple II data bus via the // GPIO ports on the Raspberry Pi package a2io import ( "bytes" "errors" "fmt" "time" "github.com/stianeikeland/go-rpio/v4" ) var edgeTimeout time.Duration var outWrite rpio.Pin var outRead rpio.Pin var outReserved2 rpio.Pin var outReserved1 rpio.Pin var outBit7 rpio.Pin var outBit6 rpio.Pin var outBit5 rpio.Pin var outBit4 rpio.Pin var outBit3 rpio.Pin var outBit2 rpio.Pin var outBit1 rpio.Pin var outBit0 rpio.Pin var inWrite rpio.Pin var inRead rpio.Pin var inReserved2 rpio.Pin var inReserved1 rpio.Pin var inBit7 rpio.Pin var inBit6 rpio.Pin var inBit5 rpio.Pin var inBit4 rpio.Pin var inBit3 rpio.Pin var inBit2 rpio.Pin var inBit1 rpio.Pin var inBit0 rpio.Pin // A2Gpio is the live implementation of A2Io interface type A2Gpio struct { } // Init initializes the GPIO ports on the Raspberry Pi func (a2 A2Gpio) Init() { err := rpio.Open() if err != nil { panic("failed to open gpio") } outWrite = rpio.Pin(24) outRead = rpio.Pin(25) outReserved2 = rpio.Pin(7) //note GPIO7 and CPIO8 require extra effort to use outReserved1 = rpio.Pin(8) outBit7 = rpio.Pin(5) outBit6 = rpio.Pin(11) outBit5 = rpio.Pin(9) outBit4 = rpio.Pin(10) outBit3 = rpio.Pin(22) outBit2 = rpio.Pin(27) outBit1 = rpio.Pin(17) outBit0 = rpio.Pin(4) inWrite = rpio.Pin(23) inRead = rpio.Pin(18) inReserved2 = rpio.Pin(14) inReserved1 = rpio.Pin(15) inBit7 = rpio.Pin(12) inBit6 = rpio.Pin(16) inBit5 = rpio.Pin(20) inBit4 = rpio.Pin(21) inBit3 = rpio.Pin(26) inBit2 = rpio.Pin(19) inBit1 = rpio.Pin(13) inBit0 = rpio.Pin(6) inWrite.PullDown() inRead.PullDown() outWrite.Output() outRead.Output() outReserved2.Output() outReserved1.Output() outBit7.Output() outBit6.Output() outBit5.Output() outBit4.Output() outBit3.Output() outBit2.Output() outBit1.Output() outBit0.Output() inWrite.Input() inRead.Input() inReserved2.Input() inReserved1.Input() inBit7.Input() inBit6.Input() inBit5.Input() inBit4.Input() inBit3.Input() inBit2.Input() inBit1.Input() inBit0.Input() outReserved1.High() outReserved2.High() outRead.High() outWrite.High() outBit7.Low() outBit6.Low() outBit5.Low() outBit4.Low() outBit3.Low() outBit2.Low() outBit1.Low() outBit0.Low() edgeTimeout = time.Second } // ReadString reads a string from the Apple II via Raspberry Pi's GPIO ports func (a2 A2Gpio) ReadString() (string, error) { var inBytes bytes.Buffer for { inByte, err := a2.ReadByte() if err != nil { return "", err } if inByte == 0 { break } inBytes.WriteByte(inByte) } return inBytes.String(), nil } // WriteString writes a string to the Apple II via Raspberry Pi's GPIO ports func (a2 A2Gpio) WriteString(outString string) error { for _, character := range outString { err := a2.WriteByte(byte(character) | 128) if err != nil { fmt.Printf("Failed to write string\n") return err } } a2.WriteByte(0) return nil } // ReadByte reads a byte from the Apple II via Raspberry Pi's GPIO ports func (a2 A2Gpio) ReadByte(noDelay ...bool) (byte, error) { // let the Apple II know we are ready to read outRead.Low() // wait for the Apple II to write startTime := time.Now() lastSleepTime := time.Now() sleepDuration := 10 for inWrite.Read() == 1 { if time.Since(startTime) > edgeTimeout { outRead.High() return 0, errors.New("timed out reading byte -- write stuck high") } if time.Since(lastSleepTime) > edgeTimeout/10 { if len(noDelay) == 0 || !noDelay[0] { sleepDuration *= 3; } time.Sleep(time.Millisecond * time.Duration(sleepDuration)); lastSleepTime = time.Now() } } // get a nibble of data var data byte data = 0 bit7 := inBit7.Read() bit6 := inBit6.Read() bit5 := inBit5.Read() bit4 := inBit4.Read() bit3 := inBit3.Read() bit2 := inBit2.Read() bit1 := inBit1.Read() bit0 := inBit0.Read() if bit7 == 1 { data += 128 } if bit6 == 1 { data += 64 } if bit5 == 1 { data += 32 } if bit4 == 1 { data += 16 } if bit3 == 1 { data += 8 } if bit2 == 1 { data += 4 } if bit1 == 1 { data += 2 } if bit0 == 1 { data++ } // let the Apple II know we are done reading //fmt.Printf("let the Apple II know we are done reading\n") outRead.High() // wait for the Apple II to finish writing //fmt.Printf("wait for the Apple II to finish writing\n") startTime = time.Now() lastSleepTime = time.Now() sleepDuration = 10 for inWrite.Read() == 0 { if time.Since(startTime) > edgeTimeout { return 0, errors.New("timed out reading byte -- write stuck low") } if time.Since(lastSleepTime) > edgeTimeout/10 { if len(noDelay) == 0 || !noDelay[0] { sleepDuration *= 3; } time.Sleep(time.Millisecond * time.Duration(sleepDuration)); lastSleepTime = time.Now() } } return data, nil } // WriteByte writes a byte to the Apple II via Raspberry Pi's GPIO ports func (a2 A2Gpio) WriteByte(data byte) error { // check if the Apple II wants to send a byte to us first if inWrite.Read() == 0 { outWrite.High() return errors.New("can't write byte while byte is incoming") } // wait for the Apple II to be ready to read startTime := time.Now() lastSleepTime := time.Now() sleepDuration := 10 for inRead.Read() == 1 { if time.Since(startTime) > edgeTimeout { outWrite.High() return errors.New("timed out writing byte -- read stuck high") } if time.Since(lastSleepTime) > edgeTimeout/10 { sleepDuration *= 3; time.Sleep(time.Millisecond * time.Duration(sleepDuration)); lastSleepTime = time.Now() } } if ((data & 128) >> 7) == 1 { outBit7.High() } else { outBit7.Low() } if ((data & 64) >> 6) == 1 { outBit6.High() } else { outBit6.Low() } if ((data & 32) >> 5) == 1 { outBit5.High() } else { outBit5.Low() } if ((data & 16) >> 4) == 1 { outBit4.High() } else { outBit4.Low() } if ((data & 8) >> 3) == 1 { outBit3.High() } else { outBit3.Low() } if ((data & 4) >> 2) == 1 { outBit2.High() } else { outBit2.Low() } if ((data & 2) >> 1) == 1 { outBit1.High() } else { outBit1.Low() } if (data & 1) == 1 { outBit0.High() } else { outBit0.Low() } // let Apple II know we're writing outWrite.Low() // wait for the Apple II to finsih reading //fmt.Printf("wait for the Apple II to finsih reading\n") startTime = time.Now() lastSleepTime = time.Now() sleepDuration = 10 for inRead.Read() == 0 { if time.Since(startTime) > edgeTimeout { outWrite.High() return errors.New("timed out writing byte -- read stuck low") } if time.Since(lastSleepTime) > edgeTimeout/10 { sleepDuration *= 3; time.Sleep(time.Millisecond * time.Duration(sleepDuration)); lastSleepTime = time.Now() } } // let the Apple II know we are done writing outWrite.High() return nil } // WriteBlock writes 512 bytes to the Apple II via Raspberry Pi's GPIO ports func (a2 A2Gpio) WriteBlock(buffer []byte) error { for i := 0; i < 512; i++ { err := a2.WriteByte(buffer[i]) if err != nil { return err } } return nil } // ReadBlock reads 512 bytes from the Apple II via Raspberry Pi's GPIO ports func (a2 A2Gpio) ReadBlock(buffer []byte) error { var err error for i := 0; i < 512; i++ { buffer[i], err = a2.ReadByte() if err != nil { return err } } return nil } // WriteBuffer writes a buffer of bytes to the Apple II via Raspberry Pi's GPIO ports func (a2 A2Gpio) WriteBuffer(buffer []byte) error { bufferSize := len(buffer) for i := 0; i < bufferSize; i++ { err := a2.WriteByte(buffer[i]) if err != nil { return err } } return nil } // SendCharacter is a pass-through to vt100 implementation func (a2 A2Gpio) SendCharacter(character byte) { sendCharacter(a2, character) } // ReadCharacter is a pass-through to vt100 implementation func (a2 A2Gpio) ReadCharacter() (string, error) { return readCharacter(a2) }