From 983211515666d5b1f6c0e62415dcb65a63ee4ca6 Mon Sep 17 00:00:00 2001 From: Shaun Date: Thu, 2 Jan 2020 15:53:20 -0800 Subject: [PATCH 1/2] Adding RS485 support (adapted from https://github.com/goburrow/serial) adapted for upstream master by Cliff Brake --- serial.go | 32 +++++++++++++++++++++++++++++++ serial_unix.go | 48 +++++++++++++++++++++++++++++++++++++++++++++++ serial_windows.go | 9 +++++++++ 3 files changed, 89 insertions(+) diff --git a/serial.go b/serial.go index 1bf5bb9..c7e45af 100644 --- a/serial.go +++ b/serial.go @@ -6,6 +6,8 @@ package serial +import "time" + //go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go syscall_windows.go // Port is the interface for a serial Port @@ -69,6 +71,8 @@ type Mode struct { DataBits int // Size of the character (must be 5, 6, 7 or 8) Parity Parity // Parity (see Parity type for more info) StopBits StopBits // Stop bits (see StopBits type for more info) + + RS485 RS485Config // RS485 configuration } // Parity describes a serial port parity setting @@ -99,6 +103,22 @@ const ( TwoStopBits ) +// RS485Config -- platform independent RS485 config. Thie structure is ignored unless Enable is true. +type RS485Config struct { + // Enable RS485 support + Enabled bool + // Delay RTS prior to send + DelayRtsBeforeSend time.Duration + // Delay RTS after send + DelayRtsAfterSend time.Duration + // Set RTS high during send + RtsHighDuringSend bool + // Set RTS high after send + RtsHighAfterSend bool + // Rx during Tx + RxDuringTx bool +} + // PortError is a platform independent error type for serial ports type PortError struct { code PortErrorCode @@ -131,6 +151,12 @@ const ( PortClosed // FunctionNotImplemented the requested function is not implemented FunctionNotImplemented + // ReadFailed indicates the read failed + ReadFailed + // ConfigureRS485Error indicates an error configuring RS485 on the platform + ConfigureRS485Error + // NoPlatformSupportForRS485 indicates no platform support for RS485 + NoPlatformSupportForRS485 ) // EncodedErrorString returns a string explaining the error code @@ -158,6 +184,12 @@ func (e PortError) EncodedErrorString() string { return "Port has been closed" case FunctionNotImplemented: return "Function not implemented" + case ReadFailed: + return "Read failed" + case ConfigureRS485Error: + return "Error configuring RS485 on the platform" + case NoPlatformSupportForRS485: + return "Platform does not support RS485" default: return "Other error" } diff --git a/serial_unix.go b/serial_unix.go index 2e72473..d6306f3 100644 --- a/serial_unix.go +++ b/serial_unix.go @@ -14,6 +14,7 @@ import ( "strings" "sync" "sync/atomic" + "time" "unsafe" "go.bug.st/serial/unixutils" @@ -28,6 +29,22 @@ type unixPort struct { opened uint32 } +const ( + rs485Enabled = 1 << 0 + rs485RTSOnSend = 1 << 1 + rs485RTSAfterSend = 1 << 2 + rs485RXDuringTX = 1 << 4 + rs485Tiocs = 0x542f +) + +// rs485_ioctl_opts is used to configure RS485 options in the driver +type rs485_ioctl_opts struct { + flags uint32 + delay_rts_before_send uint32 + delay_rts_after_send uint32 + padding [5]uint32 +} + func (port *unixPort) Close() error { if !atomic.CompareAndSwapUint32(&port.opened, 1, 0) { return nil @@ -203,6 +220,12 @@ func nativeOpen(portName string, mode *Mode) (*unixPort, error) { port.acquireExclusiveAccess() + // Enable RS485, if requested + if err = port.enableRS485(&mode.RS485); err != nil { + port.Close() + return nil, err + } + // This pipe is used as a signal to cancel blocking Read pipe := &unixutils.Pipe{} if err := pipe.Open(); err != nil { @@ -416,3 +439,28 @@ func (port *unixPort) acquireExclusiveAccess() error { func (port *unixPort) releaseExclusiveAccess() error { return ioctl(port.handle, unix.TIOCNXCL, 0) } + +// enableRS485 enables RS485 functionality of driver via an ioctl if the config says so +func (port *unixPort) enableRS485(config *RS485Config) error { + if !config.Enabled { + return nil + } + rs485 := rs485_ioctl_opts{ + rs485Enabled, + uint32(config.DelayRtsBeforeSend / time.Millisecond), + uint32(config.DelayRtsAfterSend / time.Millisecond), + [5]uint32{0, 0, 0, 0, 0}, + } + + if config.RtsHighDuringSend { + rs485.flags |= rs485RTSOnSend + } + if config.RtsHighAfterSend { + rs485.flags |= rs485RTSAfterSend + } + if config.RxDuringTx { + rs485.flags |= rs485RXDuringTX + } + + return ioctl(port.handle, rs485Tiocs, uintptr(unsafe.Pointer(&rs485))) +} diff --git a/serial_windows.go b/serial_windows.go index e2c40f5..cf89d9b 100644 --- a/serial_windows.go +++ b/serial_windows.go @@ -449,3 +449,12 @@ func nativeOpen(portName string, mode *Mode) (*windowsPort, error) { return port, nil } + +// enableRS485 enables RS485 functionality of driver via an ioctl if the config says so +func (port *windowsPort) enableRS485(config *RS485Config) error { + if !config.Enabled { + return &PortError{code: NoPlatformSupportForRS485, causedBy: nil} + } + + return nil +} From 776b896ceccb9fed3e72c59200a804137c538de2 Mon Sep 17 00:00:00 2001 From: Cliff Brake Date: Thu, 7 May 2020 12:37:33 -0400 Subject: [PATCH 2/2] clean up linting issues --- serial_unix.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/serial_unix.go b/serial_unix.go index d6306f3..864f2b8 100644 --- a/serial_unix.go +++ b/serial_unix.go @@ -38,11 +38,11 @@ const ( ) // rs485_ioctl_opts is used to configure RS485 options in the driver -type rs485_ioctl_opts struct { - flags uint32 - delay_rts_before_send uint32 - delay_rts_after_send uint32 - padding [5]uint32 +type rs485IoctlOpts struct { + flags uint32 + delayRtsBeforeSend uint32 + delayRtsAfterSend uint32 + padding [5]uint32 } func (port *unixPort) Close() error { @@ -445,7 +445,7 @@ func (port *unixPort) enableRS485(config *RS485Config) error { if !config.Enabled { return nil } - rs485 := rs485_ioctl_opts{ + rs485 := rs485IoctlOpts{ rs485Enabled, uint32(config.DelayRtsBeforeSend / time.Millisecond), uint32(config.DelayRtsAfterSend / time.Millisecond),