From 4c2e93847486877af9c19321843af8f672fc01e3 Mon Sep 17 00:00:00 2001 From: Oliver Schmidt Date: Thu, 18 Aug 2022 18:06:46 +0200 Subject: [PATCH] Added another comminucation interface implementation. Use ACM CDC (or another proprietary) USB serial device for communication. Currently VID / PID of a Raspberry Pi Pico SDK CDC UART are hardcoded - see https://github.com/raspberrypi/usb-pid --- RaspberryPi/apple2driver/a2io/cdcio.go | 147 +++++++++++++++++++++++++ RaspberryPi/apple2driver/driver.go | 25 ++++- RaspberryPi/apple2driver/go.mod | 3 +- 3 files changed, 170 insertions(+), 5 deletions(-) create mode 100644 RaspberryPi/apple2driver/a2io/cdcio.go diff --git a/RaspberryPi/apple2driver/a2io/cdcio.go b/RaspberryPi/apple2driver/a2io/cdcio.go new file mode 100644 index 0000000..f923ed7 --- /dev/null +++ b/RaspberryPi/apple2driver/a2io/cdcio.go @@ -0,0 +1,147 @@ +// Copyright Terence J. Boldt (c)2020-2022 +// 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" + + "go.bug.st/serial" + "go.bug.st/serial/enumerator" +) + +var port serial.Port + +// CDCio is a live implementation of A2Io interface +type CDCio struct { +} + +// Init initializes the CDC driver on the Raspberry Pi +func (a2 CDCio) Init() { + name := "" + for { + portInfos, err := enumerator.GetDetailedPortsList() + if err != nil { + panic(err) + } + for _, portInfo := range portInfos { + if portInfo.IsUSB && portInfo.VID == "2E8A" && portInfo.PID == "000A" { + name = portInfo.Name + fmt.Printf("Found CDC port %s\n", name) + break + } + } + if name != "" { + break + } + time.Sleep(time.Millisecond) + } + + for { + var err error + port, err = serial.Open(name, &serial.Mode{}) + if err == nil { + break; + } + var portErr *serial.PortError + if !errors.As(err, &portErr) || portErr.Code() != serial.PortNotFound { + panic(err) + } + time.Sleep(time.Millisecond) + } + + err := port.SetReadTimeout(time.Second) + if err != nil { + panic(err) + } +} + +// ReadByte reads a byte from the Apple II via Raspberry Pi's CDC driver +func (a2 CDCio) ReadByte() (byte, error) { + var data [1]byte + n, err := port.Read(data[:]); + if err != nil { + return 0, err + } + if n == 0 { + return 0, errors.New("timed out reading byte") + } + return data[0], nil +} + +// WriteByte writes a byte to the Apple II via Raspberry Pi's CDC driver +func (a2 CDCio) WriteByte(data byte) error { + _, err := port.Write([]byte{data}); + return err +} + +// ReadString reads a string from the Apple II via Raspberry Pi's CDC driver +func (a2 CDCio) 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 CDC driver +func (a2 CDCio) 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 +} + +// WriteBlock writes 512 bytes to the Apple II via Raspberry Pi's CDC driver +func (a2 CDCio) WriteBlock(buffer []byte) error { + _, err := port.Write(buffer); + return err +} + +// ReadBlock reads 512 bytes from the Apple II via Raspberry Pi's CDC driver +func (a2 CDCio) 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 CDC driver +func (a2 CDCio) WriteBuffer(buffer []byte) error { + _, err := port.Write(buffer); + return err +} + +// SendCharacter is a pass-through to vt100 implementation +func (a2 CDCio) SendCharacter(character byte) { + sendCharacter(a2, character) +} + +// ReadCharacter is a pass-through to vt100 implementation +func (a2 CDCio) ReadCharacter() (string, error) { + return readCharacter(a2) +} diff --git a/RaspberryPi/apple2driver/driver.go b/RaspberryPi/apple2driver/driver.go index 9607892..7640b9e 100644 --- a/RaspberryPi/apple2driver/driver.go +++ b/RaspberryPi/apple2driver/driver.go @@ -9,12 +9,15 @@ package main import ( + "errors" "flag" "fmt" "os" "path/filepath" "time" + "go.bug.st/serial" + "github.com/tjboldt/Apple2-IO-RPi/RaspberryPi/apple2driver/a2io" "github.com/tjboldt/Apple2-IO-RPi/RaspberryPi/apple2driver/handlers" "github.com/tjboldt/Apple2-IO-RPi/RaspberryPi/apple2driver/info" @@ -32,11 +35,16 @@ const menuCommand = 8 const shellCommand = 9 func main() { - drive1, drive2 := getDriveFiles() + drive1, drive2, cdc := getFlags() fmt.Printf("Starting Apple II RPi v%s...\n", info.Version) - comm := a2io.A2Gpio{} + var comm a2io.A2Io + if cdc { + comm = a2io.CDCio{} + } else { + comm = a2io.A2Gpio{} + } handlers.SetCommunication(comm) comm.Init() @@ -68,6 +76,13 @@ func main() { case shellCommand: handlers.ShellCommand() } + // the A2Io interface should be extended in one way or another + // to encapsulate this, e.g. by a ReadByte variant / parameter + } else if cdc { + var portErr *serial.PortError + if errors.As(err, &portErr) && portErr.Code() == serial.PortClosed { + comm.Init() + } // temporary workaround for busy wait loop heating up the RPi } else if time.Since(lastCommandTime) > time.Millisecond*100 { time.Sleep(time.Millisecond * 100) @@ -75,9 +90,10 @@ func main() { } } -func getDriveFiles() (*os.File, *os.File) { +func getFlags() (*os.File, *os.File, bool) { var drive1Name string var drive2Name string + var cdc bool execName, _ := os.Executable() path := filepath.Dir(execName) @@ -87,6 +103,7 @@ func getDriveFiles() (*os.File, *os.File) { flag.StringVar(&drive1Name, "d1", "", "A ProDOS format drive image for drive 1") flag.StringVar(&drive2Name, "d2", defaultFileName, "A ProDOS format drive image for drive 2 and will be used for drive 1 if drive 1 empty") + flag.BoolVar(&cdc, "cdc", false, "Use ACM CDC serial device") flag.Parse() var drive1 *os.File @@ -125,5 +142,5 @@ func getDriveFiles() (*os.File, *os.File) { os.Exit(1) } - return drive1, drive2 + return drive1, drive2, cdc } diff --git a/RaspberryPi/apple2driver/go.mod b/RaspberryPi/apple2driver/go.mod index 7dbc292..132cc51 100644 --- a/RaspberryPi/apple2driver/go.mod +++ b/RaspberryPi/apple2driver/go.mod @@ -3,7 +3,8 @@ module github.com/tjboldt/Apple2-IO-RPi/RaspberryPi/apple2driver go 1.16 require ( - github.com/creack/pty v1.1.17 + github.com/creack/pty v1.1.18 github.com/stianeikeland/go-rpio/v4 v4.6.0 github.com/tjboldt/ProDOS-Utilities v0.3.0 + go.bug.st/serial v1.3.5 )