[go-nuts] Serial port configuration using stty

1,227 views
Skip to first unread message

Stetson

unread,
May 22, 2010, 11:42:51 PM5/22/10
to golang-nuts
Hi all, I've begun work on a project that handles communication with
an industrial computer via a com port and then provides certain
network services to communicate with the device.

My original project was in Java but now I've decided to try out Go,
mostly because it should be able to run on a much lower spec machine
than that Java implementation.

The first step is to get serial communications up and running with the
device, and I'm wrestling with my unfamiliarity with how Unix systems
deal with serial port configuration and also with how to implement
this with Go.

I searched and found a few references with using the command line tool
stty, run from within my Go application using exec.Run()

Here's what I wrote as a simple test:

--------------------------------

package main

import "fmt"
import "os"
import "exec"
import "./idec"

const devicePath = "/dev/tty.usbserial"

func main() {

// Open the serial port for PLC communication
serial, err := os.Open(devicePath, os.O_RDWR | os.O_NOCTTY |
os.O_NDELAY, 0666)
if err != nil {
fmt.Printf("Error: %s\n", err.String())
return
}
defer serial.Close()
fmt.Printf("Port \"%s\" opened\n", devicePath)


// Set up serial port options using stty
args := []string{"9600", "cs7", "-parodd", "-cstopb", "crtscts", "-
echo", "-f /dev/tty.usbserial"}
_, err = exec.Run("/bin/stty", args, os.Environ(), "",exec.Pipe,
exec.Pipe, exec.Pipe)
if err != nil {
fmt.Printf("Error: %s\n", err.String())
return
}

kind := idec.OutputBit
operand := "0006"
data := 1

// Get query formatted to manufacturer specs
query := idec.WriteBit(kind, operand, data)

fmt.Printf("Query: %s\n", query)

for i:=0; i <10000; i++ {
serial.Write(query)
}

return
}

--------------------------------

The program executes successfully but there is no response from the
device, so I think my problem lies with getting the serial port
configured correctly. The device is set up to use:

baud rate: 9600
parity: even
data bits: 7
stop bits: 1

If anyone has experience in this type of thing I would really
appreciate some pointers.

Thanks,
Stetson

Dr Code

unread,
May 24, 2010, 12:12:56 AM5/24/10
to Stetson, golang-nuts
On Sat, May 22, 2010 at 10:42 PM, Stetson <stetson...@gmail.com> wrote:
> Hi all, I've begun work on a project that handles communication with
> an industrial computer via a com port and then provides certain
> network services to communicate with the device.

>        serial, err := os.Open(devicePath, os.O_RDWR | os.O_NOCTTY | os.O_NDELAY, 0666)

This is opening the tty device whether or not there's a device attached.
You could set it up so that this works without the NDELAY so that it
only successfully opens when your tty device is turned on. It would
also depend on the tty lines being correctly wired.

Another side affect of NDELAY is that you'll have to check the return
values of write() so that all your data is written and repeat the write()
until all your data is drained.

>        args := []string{"9600", "cs7", "-parodd", "-cstopb", "crtscts", "-
> echo", "-f /dev/tty.usbserial"}

There are usually no default tty parameters, or if there are, they are
almost certainly not what you want. I would recommend that you
explicitly set all the parameters you want. At a minimum you will
want to add 'parenb' to generate the parity bit.

One nice feature of stty is the -g mode which outputs a set of colon
separated numbers which almost matches the termios struct. You may
want to consider using it instead of the strings.

You'll also want to set 'raw' so that the chars get sent and received
without the line discipline modifying the output and input.

R.

Giles Lean

unread,
May 24, 2010, 12:56:54 AM5/24/10
to Stetson, golang-nuts

> Hi all, I've begun work on a project that handles
> communication with an industrial computer via a com port and
> then provides certain network services to communicate with
> the device.

That takes me back ... *shudder*.

> The first step is to get serial communications up and
> running with the device, and I'm wrestling with my
> unfamiliarity with how Unix systems deal with serial port
> configuration and also with how to implement this with Go.

As well as running stty (crude, but if it works, go for it,
and the previous response had some good advice about adding
'parenb' to your list of parameters. (Mostly, for varying
values of "mostly", serial comms have used 8 bits, no parity,
and one stop bit, but I'm sure you know what your industrial
hardware wants.)

A second (and more difficult, but in the end possibly cleaner)
approach would be via cgo and using tcgetattr() and
tcsetattr().

Myself, having done (a long long time ago) comms programming
in C would write C code that takes a file descriptor and
twiddles it to the state you want, and then call that function
from Go. (And an equivalent reverse operation to put things
back when you're done, if that matters.)

You could also call tcgetattr() and tcsetattr() via cgo, but I
think doing as much in C as possible would be easier. cgo is
(frankly, and no offence to those who've worked on it)
immature and I hit its limits most times I try to use it.
It's handling of complex #include files is particularly weak
(which isn't a surprise: that's the hardest part of what it's
doing). I tend to declare only what I want to use, and not
include the system include files where I can avoid it.

Oh -- I'd also use devicePath in the stty command, not hard
code it. ;-)

I suppose a third solution would be to talk to the device in
C, and have Go talk to the C program via a pipe or socket.

Good luck,

Giles

David Leimbach

unread,
May 24, 2010, 2:21:41 PM5/24/10
to golang-nuts


On May 23, 9:56 pm, Giles Lean <giles.l...@pobox.com> wrote:
> > Hi all, I've begun work on a project that handles
> > communication with an industrial computer via a com port and
> > then provides certain network services to communicate with
> > the device.
>
> That takes me back ... *shudder*.

I'm doing almost exactly this same application right now.

>
> > The first step is to get serial communications up and
> > running with the device, and I'm wrestling with my
> > unfamiliarity with how Unix systems deal with serial port
> > configuration and also with how to implement this with Go.
>
> As well as running stty (crude, but if it works, go for it,
> and the previous response had some good advice about adding
> 'parenb' to your list of parameters.  (Mostly, for varying
> values of "mostly", serial comms have used 8 bits, no parity,
> and one stop bit, but I'm sure you know what your industrial
> hardware wants.)
>

Try GNU screen or socat if you want also. Either of those can do
serial handling, and socat is ridiculously flexible. I've tunneled
UDP via TCP over ssh tunnels before to make SNMP work through a
firewall over really long distances with socat :-).

> A second (and more difficult, but in the end possibly cleaner)
> approach would be via cgo and using tcgetattr() and
> tcsetattr().

I don't think this is cleaner than asking another small purposeful
program to do the work for you.

For one you're doing something someone already made perfectly good
tool to do (socat and screen). Chances are, this software is used a
lot more than what you're about to write, and may have fewer defects.

Just ask yourself, "Do I want to be solving the problem of serial
communications that might already be solved for me, or should I just
get to work on the meat of my problems?"

If where you work is anything like where I work, the time to solution
is more important than doing it all yourself.

>
> Myself, having done (a long long time ago) comms programming
> in C would write C code that takes a file descriptor and
> twiddles it to the state you want, and then call that function
> from Go.  (And an equivalent reverse operation to put things
> back when you're done, if that matters.)
>
> You could also call tcgetattr() and tcsetattr() via cgo, but I
> think doing as much in C as possible would be easier. cgo is
> (frankly, and no offence to those who've worked on it)
> immature and I hit its limits most times I try to use it.
> It's handling of complex #include files is particularly weak
> (which isn't a surprise: that's the hardest part of what it's
> doing).  I tend to declare only what I want to use, and not
> include the system include files where I can avoid it.
>
> Oh -- I'd also use devicePath in the stty command, not hard
> code it. ;-)
>
> I suppose a third solution would be to talk to the device in
> C, and have Go talk to the C program via a pipe or socket.

This is closer to what I was thinking too, but those programs already
exist :-). Go look at socat. http://www.dest-unreach.org/socat/. The
whole point of socat is solving plumbing problems, and you have a
plumbing problem :-)

You might find that, for some reason, this is too slow, but I suspect
you're not at the point where optimization is needed, especially with
serial communications involved.

Dave

>
> Good luck,
>
> Giles

Stetson

unread,
May 24, 2010, 5:27:12 PM5/24/10
to golang-nuts
Through a combination of the suggestions here I finally got this to
work. Part of my problem turned out to be the usb-serial converter I
was using. When I switched over to running remotely on the linux
server with a dedicated com port that is my actual destination
platform I started to get results.

Here's what I ended up with:

----------------------

package main

import "fmt"
import "os"
import "exec"
import "idec"

const devicePath = "/dev/ttyS0"

func main() {

// Open the serial port for PLC communication
serial, err := os.Open(devicePath, os.O_RDWR | os.O_NOCTTY |
os.O_NDELAY, 0666)
if err != nil {
fmt.Printf("Error: %s\n", err.String())
return
}
defer serial.Close()
fmt.Printf("Port \"%s\" opened\n", devicePath)


// Set up serial port options using stty
args := []string{"9600", "cs7", "parenb", "raw", "-parodd", "-
cstopb", "crtscts", "-echo", ("-F " + devicePath)}
_, err = exec.Run("/bin/stty", args, os.Environ(), "",exec.Pipe,
exec.Pipe, exec.Pipe)
if err != nil {
fmt.Printf("Error: %s\n", err.String())
return
}

kind := idec.OutputBit
operand := "0006"
data := 1

// Get query formatted to manufacturer specs
query := idec.WriteBit(kind, operand, data)

fmt.Printf("Query: %s\n", query)

serial.Write(query)

return
}

Giles Lean

unread,
May 24, 2010, 5:44:45 PM5/24/10
to David Leimbach, Stetson, golang-nuts

David Leimbach <lei...@gmail.com> wrote:

> If where you work is anything like where I work, the time to solution
> is more important than doing it all yourself.

OK, I"m (in the Australian vernacular) "sprung bad".

When I did my work of this type, there was no commercial Internet
in Australia. Indeed, I installed the first nework (local only)
in the office I was working in, and learned Unix serieal programming
from printed IBM manuals.

Yeah, that dates me.

> This is closer to what I was thinking too, but those programs already
> exist :-). Go look at socat. http://www.dest-unreach.org/socat/. The
> whole point of socat is solving plumbing problems, and you have a
> plumbing problem :-)

Stetson, that sounds like your answer. RS-232 serial comms is
definitely a "buy, not build" if you have the option.

(We had bought for our previous system: on our "new" IBM RS/6000 we
didn't have that option, so it was build time. And I was young, my
salary wasn't that much, and I had free time as we waited for a deal to
come through.)

Now I'm well and truly off topic for golang-nuts so I'll shut up!

Giles

Reply all
Reply to author
Forward
0 new messages