package serial import ( "encoding/binary" "errors" "fmt" "io" "sync" "time" "go.bug.st/serial" ) const ( portDev = "/dev/ttyUSB0" portSpeed = 115_200 maxAddr = 1 << 17 pingResponse = 0xEA readTimeout = 2 * time.Second ) type Serial struct { portMu sync.Mutex port serial.Port } func Open() (*Serial, error) { mode := &serial.Mode{ BaudRate: 115_200, } port, err := serial.Open(portDev, mode) if err != nil { return nil, err } packet := encode(cmdPing, 0, 0) if _, err := port.Write(packet[:]); err != nil { port.Close() return nil, err } 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) } return &Serial{port: port}, nil } func (d *Serial) Close() error { d.portMu.Lock() defer d.portMu.Unlock() return d.port.Close() } 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) var ret [4]byte binary.BigEndian.PutUint32(ret[:], raw) return ret } // 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) { d.portMu.Lock() defer d.portMu.Unlock() if off+int64(len(bs)) >= maxAddr { return 0, errors.New("OOB read") } for i := range bs { packet := encode(cmdReadByte, int(off)+i, 0) if _, err := d.port.Write(packet[:]); err != nil { return i, err } d.port.SetReadTimeout(readTimeout) n, err := io.ReadFull(d.port, bs[i:i+1]) if err != nil { 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() if off+int64(len(bs)) >= maxAddr { 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 { return i, err } d.port.SetReadTimeout(readTimeout) n, err := io.ReadFull(d.port, bs[i:i+burstLen]) if err != nil { return i + n, err } } return len(bs), nil } func (d *Serial) ReadAt(bs []byte, off int64) (int, error) { return d.readBurst(bs, off) } func (d *Serial) WriteAt(bs []byte, off int64) (int, error) { d.portMu.Lock() defer d.portMu.Unlock() if off+int64(len(bs)) >= maxAddr { return 0, errors.New("OOB write") } for i, v := range bs { packet := encode(cmdWriteByte, int(off)+i, v) if _, err := d.port.Write(packet[:]); err != nil { return i, err } } return len(bs), nil }