2024-09-17 00:43:36 +02:00
|
|
|
package serial
|
|
|
|
|
|
|
|
import (
|
2024-09-19 06:31:34 +02:00
|
|
|
"encoding/binary"
|
2024-09-17 00:43:36 +02:00
|
|
|
"errors"
|
2024-09-19 06:31:34 +02:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2024-09-17 00:43:36 +02:00
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"go.bug.st/serial"
|
|
|
|
)
|
|
|
|
|
2024-09-19 06:31:34 +02:00
|
|
|
const (
|
|
|
|
portDev = "/dev/ttyUSB0"
|
2024-09-20 06:25:04 +02:00
|
|
|
portSpeed = 1_382_400 // 1.3Mbps
|
2024-09-19 06:31:34 +02:00
|
|
|
maxAddr = 1 << 17
|
|
|
|
pingResponse = 0xEA
|
|
|
|
readTimeout = 2 * time.Second
|
|
|
|
)
|
2024-09-17 00:43:36 +02:00
|
|
|
|
|
|
|
type Serial struct {
|
|
|
|
portMu sync.Mutex
|
|
|
|
port serial.Port
|
|
|
|
}
|
|
|
|
|
|
|
|
func Open() (*Serial, error) {
|
|
|
|
mode := &serial.Mode{
|
2024-09-20 06:25:04 +02:00
|
|
|
BaudRate: portSpeed,
|
2024-09-17 00:43:36 +02:00
|
|
|
}
|
|
|
|
port, err := serial.Open(portDev, mode)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-09-19 06:31:34 +02:00
|
|
|
packet := encode(cmdPing, 0, 0)
|
|
|
|
if _, err := port.Write(packet[:]); err != nil {
|
|
|
|
port.Close()
|
|
|
|
return nil, err
|
|
|
|
}
|
2024-09-20 06:25:04 +02:00
|
|
|
|
2024-09-19 06:31:34 +02:00
|
|
|
port.SetReadTimeout(readTimeout)
|
|
|
|
if _, err := io.ReadFull(port, packet[:1]); err != nil {
|
|
|
|
port.Close()
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if packet[0] != pingResponse {
|
|
|
|
port.Close()
|
|
|
|
return nil, fmt.Errorf("wrong ping response: got %x, want %x", packet[0], pingResponse)
|
|
|
|
}
|
|
|
|
|
2024-09-17 00:43:36 +02:00
|
|
|
return &Serial{port: port}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Serial) Close() error {
|
|
|
|
d.portMu.Lock()
|
|
|
|
defer d.portMu.Unlock()
|
|
|
|
return d.port.Close()
|
|
|
|
}
|
|
|
|
|
2024-09-19 06:31:34 +02:00
|
|
|
const (
|
|
|
|
cmdReadByte = 1
|
|
|
|
cmdWriteByte = 2
|
|
|
|
cmdReadRange = 3
|
|
|
|
cmdPing = 0x7F
|
|
|
|
)
|
|
|
|
|
|
|
|
func encode(cmd int, addr int, data byte) [4]byte {
|
|
|
|
cmd = cmd & (1<<7 - 1)
|
|
|
|
addr = addr & (1<<17 - 1)
|
|
|
|
raw := (uint32(cmd) << 25) + (uint32(addr) << 8) + uint32(data)
|
2024-09-17 00:43:36 +02:00
|
|
|
var ret [4]byte
|
2024-09-19 06:31:34 +02:00
|
|
|
binary.BigEndian.PutUint32(ret[:], raw)
|
2024-09-17 00:43:36 +02:00
|
|
|
return ret
|
|
|
|
}
|
|
|
|
|
2024-09-19 06:31:34 +02:00
|
|
|
// readSlow reads memory one byte at a time, and is 10x slower than
|
|
|
|
// burst reading. The code is preserved here just because it's a
|
|
|
|
// simpler implementation in the hardware, and is thus sometimes
|
|
|
|
// useful for debugging.
|
|
|
|
func (d *Serial) readSlow(bs []byte, off int64) (int, error) {
|
2024-09-17 00:43:36 +02:00
|
|
|
d.portMu.Lock()
|
|
|
|
defer d.portMu.Unlock()
|
2024-09-20 06:25:04 +02:00
|
|
|
if off+int64(len(bs)) > maxAddr {
|
2024-09-17 00:43:36 +02:00
|
|
|
return 0, errors.New("OOB read")
|
|
|
|
}
|
|
|
|
for i := range bs {
|
2024-09-19 06:31:34 +02:00
|
|
|
packet := encode(cmdReadByte, int(off)+i, 0)
|
2024-09-17 00:43:36 +02:00
|
|
|
if _, err := d.port.Write(packet[:]); err != nil {
|
|
|
|
return i, err
|
|
|
|
}
|
2024-09-19 06:31:34 +02:00
|
|
|
d.port.SetReadTimeout(readTimeout)
|
|
|
|
n, err := io.ReadFull(d.port, bs[i:i+1])
|
2024-09-17 00:43:36 +02:00
|
|
|
if err != nil {
|
2024-09-19 06:31:34 +02:00
|
|
|
return i + n, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return len(bs), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d *Serial) readBurst(bs []byte, off int64) (int, error) {
|
|
|
|
d.portMu.Lock()
|
|
|
|
defer d.portMu.Unlock()
|
2024-09-20 06:25:04 +02:00
|
|
|
if off+int64(len(bs)) > maxAddr {
|
2024-09-19 06:31:34 +02:00
|
|
|
return 0, errors.New("OOB read")
|
|
|
|
}
|
|
|
|
for i := 0; i < len(bs); i += 255 {
|
|
|
|
burstLen := min(len(bs)-i, 255)
|
|
|
|
packet := encode(cmdReadRange, int(off)+i, byte(burstLen))
|
|
|
|
if _, err := d.port.Write(packet[:]); err != nil {
|
2024-09-17 00:43:36 +02:00
|
|
|
return i, err
|
|
|
|
}
|
2024-09-19 06:31:34 +02:00
|
|
|
d.port.SetReadTimeout(readTimeout)
|
|
|
|
n, err := io.ReadFull(d.port, bs[i:i+burstLen])
|
|
|
|
if err != nil {
|
|
|
|
return i + n, err
|
|
|
|
}
|
2024-09-17 00:43:36 +02:00
|
|
|
}
|
|
|
|
return len(bs), nil
|
|
|
|
}
|
|
|
|
|
2024-09-19 06:31:34 +02:00
|
|
|
func (d *Serial) ReadAt(bs []byte, off int64) (int, error) {
|
|
|
|
return d.readBurst(bs, off)
|
|
|
|
}
|
|
|
|
|
2024-09-17 00:43:36 +02:00
|
|
|
func (d *Serial) WriteAt(bs []byte, off int64) (int, error) {
|
|
|
|
d.portMu.Lock()
|
|
|
|
defer d.portMu.Unlock()
|
2024-09-20 06:25:04 +02:00
|
|
|
if off+int64(len(bs)) > maxAddr {
|
2024-09-17 00:43:36 +02:00
|
|
|
return 0, errors.New("OOB write")
|
|
|
|
}
|
|
|
|
for i, v := range bs {
|
2024-09-19 06:31:34 +02:00
|
|
|
packet := encode(cmdWriteByte, int(off)+i, v)
|
2024-09-17 00:43:36 +02:00
|
|
|
if _, err := d.port.Write(packet[:]); err != nil {
|
|
|
|
return i, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return len(bs), nil
|
|
|
|
}
|