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
)