Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/tarm/serial

go 1.26.2

require golang.org/x/sys v0.45.0 // indirect
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
65 changes: 33 additions & 32 deletions serial.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ compile for windows from another platform. Unfortunately goinstall
does not currently let you cross compile so you will have to do it
manually:

GOOS=windows make clean install
GOOS=windows make clean install

Currently there is very little in the way of configurability. You can
set the baud rate. Then you can Read(), Write(), or Close() the
Expand All @@ -26,32 +26,32 @@ different goroutines).

Example usage:

package main

import (
"github.com/tarm/serial"
"log"
)

func main() {
c := &serial.Config{Name: "COM5", Baud: 115200}
s, err := serial.OpenPort(c)
if err != nil {
log.Fatal(err)
}

n, err := s.Write([]byte("test"))
if err != nil {
log.Fatal(err)
}

buf := make([]byte, 128)
n, err = s.Read(buf)
if err != nil {
log.Fatal(err)
}
log.Print("%q", buf[:n])
}
package main

import (
"github.com/yagehu/serial"
"log"
)

func main() {
c := &serial.Config{Name: "COM5", Baud: 115200}
s, err := serial.OpenPort(c)
if err != nil {
log.Fatal(err)
}

n, err := s.Write([]byte("test"))
if err != nil {
log.Fatal(err)
}

buf := make([]byte, 128)
n, err = s.Read(buf)
if err != nil {
log.Fatal(err)
}
log.Print("%q", buf[:n])
}
*/
package serial

Expand Down Expand Up @@ -87,13 +87,14 @@ const (
//
// For example:
//
// c0 := &serial.Config{Name: "COM45", Baud: 115200, ReadTimeout: time.Millisecond * 500}
// c0 := &serial.Config{Name: "COM45", Baud: 115200, ReadTimeout: time.Millisecond * 500}
//
// or
// c1 := new(serial.Config)
// c1.Name = "/dev/tty.usbserial"
// c1.Baud = 115200
// c1.ReadTimeout = time.Millisecond * 500
//
// c1 := new(serial.Config)
// c1.Name = "/dev/tty.usbserial"
// c1.Baud = 115200
// c1.ReadTimeout = time.Millisecond * 500
type Config struct {
Name string
Baud int
Expand Down
159 changes: 159 additions & 0 deletions serial_freebsd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
//go:build !cgo

package serial

import (
"fmt"
"os"
"time"
"unsafe"

"golang.org/x/sys/unix"
)

func openPort(name string, baud int, databits byte, parity Parity, stopbits StopBits, readTimeout time.Duration) (p *Port, err error) {
var bauds = map[int]uint32{
50: unix.B50,
75: unix.B75,
110: unix.B110,
134: unix.B134,
150: unix.B150,
200: unix.B200,
300: unix.B300,
600: unix.B600,
1200: unix.B1200,
1800: unix.B1800,
2400: unix.B2400,
4800: unix.B4800,
9600: unix.B9600,
19200: unix.B19200,
38400: unix.B38400,
57600: unix.B57600,
115200: unix.B115200,
230400: unix.B230400,
460800: unix.B460800,
921600: unix.B921600,
}

rate, ok := bauds[baud]
if !ok {
return nil, fmt.Errorf("Unrecognized baud rate")
}

f, err := os.OpenFile(name, unix.O_RDWR|unix.O_NOCTTY|unix.O_NONBLOCK, 0666)
if err != nil {
return nil, err
}

defer func() {
if err != nil && f != nil {
f.Close()
}
}()

// Base settings
cflagToUse := unix.CREAD | unix.CLOCAL | rate
switch databits {
case 5:
cflagToUse |= unix.CS5
case 6:
cflagToUse |= unix.CS6
case 7:
cflagToUse |= unix.CS7
case 8:
cflagToUse |= unix.CS8
default:
return nil, ErrBadSize
}

// Stop bits settings
switch stopbits {
case Stop1:
// default is 1 stop bit
case Stop2:
cflagToUse |= unix.CSTOPB
default:
// Don't know how to set 1.5
return nil, ErrBadStopBits
}

// Parity settings
switch parity {
case ParityNone:
// default is no parity
case ParityOdd:
cflagToUse |= unix.PARENB
cflagToUse |= unix.PARODD
case ParityEven:
cflagToUse |= unix.PARENB
default:
return nil, ErrBadParity
}

fd := f.Fd()
vmin, vtime := posixTimeoutValues(readTimeout)

t := unix.Termios{
Iflag: unix.IGNPAR,
Cflag: cflagToUse,
Ispeed: rate,
Ospeed: rate,
}
t.Cc[unix.VMIN] = vmin
t.Cc[unix.VTIME] = vtime

if _, _, errno := unix.Syscall6(
unix.SYS_IOCTL,
uintptr(fd),
uintptr(unix.TIOCSETA),
uintptr(unsafe.Pointer(&t)),
0,
0,
0,
); errno != 0 {
return nil, errno
}

if err = unix.SetNonblock(int(fd), false); err != nil {
return
}

return &Port{f: f}, nil
}

type Port struct {
// We intentionly do not use an "embedded" struct so that we
// don't export File
f *os.File
}

func (p *Port) Read(b []byte) (n int, err error) {
return p.f.Read(b)
}

func (p *Port) Write(b []byte) (n int, err error) {
return p.f.Write(b)
}

// Discards data written to the port but not transmitted,
// or data received but not read
func (p *Port) Flush() error {
flags := int32(0)

_, _, errno := unix.Syscall(
unix.SYS_IOCTL,
uintptr(p.f.Fd()),
uintptr(unix.TIOCFLUSH),
uintptr(unsafe.Pointer(&flags)),
)

if errno == 0 {
return nil
}

return errno
}

func (p *Port) Close() (err error) {
return p.f.Close()
}