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
This commit is contained in:
Oliver Schmidt 2022-08-18 18:06:46 +02:00 committed by Terence Boldt
parent 0ef9d567aa
commit 4c2e938474
3 changed files with 170 additions and 5 deletions

View File

@ -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)
}

View File

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

View File

@ -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
)