Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things

Lower-Level Serial Port Access on *NIX

by haukex (Bishop)
on Mar 01, 2017 at 15:07 UTC ( #1183290=CUFP: print w/replies, xml ) Need Help??

Dear Monks,

Most likely, everyone who's needed to access a serial port on *NIX systems has used, or at least come across, Device::SerialPort. It's nice because it provides a decent level of portability, being designed to be a replacement for Win32::SerialPort. However, it's always bugged me a little bit that the module is a bit unwieldy, with a lot of configuration and functions I never use, several documented as being experimental, and that its filehandle interface is tied instead of native. So, I'd like to present an alternative that has been working well for me over the past months, IO::Termios. It's a subclass of IO::Handle, and the handles can be used directly in IO::Select loops, which can be used to implement nonblocking I/O and timeouts, or for example a POE POE::Wheel::ReadWrite, just to mention two possibilities. (Note: I'm not saying IO::Termios is "better" than Device::SerialPort, just that so far it has been a viable alternative.)

Here's a basic example:

use IO::Termios (); my $handle = IO::Termios->open('/tmp/fakepty', '4800,8,n,1') or die "IO::Termios->open: $!"; while (<$handle>) { # read the port line-by-line chomp; print time." <$_>\n"; # write something to the port print {$handle} "Three!\n" if /3/; } close $handle;

An Aside: Fake Serial Ports on *NIX

You may have noticed that in the above example, instead of the usual device names like e.g. /dev/ttyAMA*, /dev/ttyS*, or /dev/ttyUSB*, I used "/tmp/fakepty". I created this for testing using the versatile tool socat, here are two examples:

# connect the fake pty to a process that generates output $ socat pty,raw,echo=0,link=/tmp/fakepty \ exec:'perl -e "$|=1;while(1){print q{Foo },$x++,qq{\n};sleep 2}"' # connect the fake pty to the current terminal $ socat pty,raw,echo=0,link=/tmp/fakepty -,icanon=0,min=1

Update 2020-03-19: I posted a slightly more complex example as part of my node Logging Serial Ports with Mojolicious. /Update

More Fine-Grained Control

It's also possible to use sysopen for the ports, if you want to have control over the exact flags used to open the port. Also, if you need to set some stty modes, you can do so with IO::Stty. I've found that for several of the USB-to-Serial converters I've used that it's necessary to set the mode -echo for them to work correctly, and raw is necessary for binary data streams.

use Fcntl qw/:DEFAULT/; use IO::Termios (); use IO::Stty (); sysopen my $fh, '/tmp/fakepty', O_RDWR or die "sysopen: $!"; my $handle = IO::Termios->new($fh) or die "IO::Termios->new: $!"; $handle->set_mode('4800,8,n,1'); IO::Stty::stty($handle, qw/ raw -echo /); my $tosend = "Hello, World!\n"; $handle->syswrite($tosend) == length($tosend) or die "syswrite"; for (1..3) { my $toread = 1; $handle->sysread(my $in, $toread) == $toread or die "sysread"; print "Read $_: <$in>\n"; } $handle->close;

My error checking in the above example is a little simplistic, but I just wanted to demonstrate that using sysread and syswrite is possible like on any other handle.

I've noticed that there is some interaction between IO::Termios and IO::Stty - for example, when I had to connect to a serial device using 7-bit and even parity, I hat to set the termios mode to 4800,7,e,1 and set the stty modes cs7 parenb -parodd raw -echo for things to work correctly.

I have written a module that wraps an IO::Termios handle and provides read timeout, flexible readline, signal handling support, and a few other things. However, I need to point out that while I've been using the module successfully in several data loggers over the past few months in a research environment, it should not yet be considered production quality! The major reason is that it's not (yet?) a real CPAN distro, and it has zero tests! But if you're still curious, for example how I implemented a read timeout with IO::Select, you can find the code here.

Update: Added mention of some /dev/* device names.

Replies are listed 'Best First'.
Re: Lower-Level Serial Port Access on *NIX
by holyghost (Beadle) on Mar 02, 2017 at 03:57 UTC
    This is why I am reading the Arduino cookbook. It just needs a serial card, an old connection or an AMD motherboard with serial ports on it. +1

      First, ++ to haukex for the post as it is most informative and detailed.

      Now, which Arduino board are you using? Most (if not all) Arduino boards have their serial port connected directly through the USB connector. In fact, it's the same serial port one uploads new "sketches" to the Arduino with :)

      You can literally plug your Arduino via USB cable (micro USB) to any USB port on a computer (Windows or *nix), and communicate with the Arduino over it using UART (Serial). USB is, after all, the Universal Serial Bus.

      If you have further questions regarding the serial comms on the Arduino specifically, I'd be glad to try to help out. Just reply. Sounds like haukex has some understanding of serial comms in general, particularly using Perl, so that's great too.

        I have used Device::SerialPort::Arduino to talk to my chipKIT UNO and Fubarino boards via USB. I'm not sure that it's still on CPAN anymore, but I have a hard copy of it.


        There's never enough time to do it right, but always enough time to do it over...

Log In?

What's my password?
Create A New User
Node Status?
node history
Node Type: CUFP [id://1183290]
Front-paged by Arunbear
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others rifling through the Monastery: (9)
As of 2020-12-03 20:22 GMT
Find Nodes?
    Voting Booth?
    How often do you use taint mode?

    Results (57 votes). Check out past polls.