gnupic@linuxhacker.org
> > This might be a stupid question, but I don't have the answer so.
> > How do you output from the terminal to a serial port in linux?
It's not a stupid question, it's tricky to do decent character I/O.
Below is my code, for Perl, that does the trick. I tried to be as
portable as possible. This code works, but I'm having one problem (see
below) with it. I see the linux side output a character occasionally
when CTS is deasserted.
My project is a PIC16F627A-based in-circuit programmer for a general
purpose PIC16F627A/28A/48A CPU card with lots of I/O (32 bits half-amp
outputs, DACs, opamps, power, etc). THe linux prog talks to the Adapter
(a '27A with just enough hardware to do the programming and timing), and
the Adapter plugs onto a 6-pin header for the target board. The Adapter
does the timing, so the linux side only needs to issue the right
commands.
I'm not sure where the problem is (my PIC code or linux code) but I am
going to move to a character-acknowledged command structure and simply
not use CTS, which is the real difficulty in making linux serial I/O
work. The rtscts kludge is also the only non-POSIX item too, good to get
rid of.
I just started a new job, so I get to work on this thing about an hour a
week now.
functions defined are:
serinit()
serout()
serin()
#############################################################
#
# Serial port specification.
my $devname= "/dev/ttyS0"; # umm, it's name
my $speed= 115200; # default bit rate
#
#
#############################################################
# Open the serial device to talk to the Model A1.
&serinit ($devname, $speed); # open and init,
&leave () if $errors; # can't continue.
# Sample calls...
my $e= &serout (@_); # write out data,
&error ("SOME WEIRD ERROR WRITING TO PROGRAMMER ADAPTER!")
if not defined $e; # oops
&error ("WRITE TO PROGRAMMER ADAPTER FAILED!")
if not $e; # didn't write all chars.
my $c= &serin (1); # get the ack char,
&error ("SOME WEIRD ERROR WAITING FOR COMMAND ACKNOWLEDGE "
. "FROM PROGRAMMER ADAPTER!")
if not defined $c;
#############################################################
#
# serinit (devname, bit rate);
#
# Initialize the serial device for read/write, leave it open
# as the global glob DEV. For linux, this is all POSIX compliant,
# except for the crtscts setting for stty.
#
# See:
# man perlfunc, functions open, sysopen
# man perlopentut
# man perlfaq8
sub serinit {
# Open the device in non-blocking, raw mode first.
use Fcntl; # O_ definitions,
sysopen (DEV, "$devname", O_RDWR|O_NDELAY|O_BINARY)
or error ("Can't open serial device $devname");
# Tell Perl to autoflush.
my $foo = select (DEV); $| = 1; select ($foo); # set autoflush
# Finally, set the bit rate and enable control signals. Note that
# crtscts is non-POSIX.
my $e= "/bin/stty -F $devname $speed raw cs8 -clocal crtscts";
print "Executing: $e\n" if $verbose;
qx"$e";
}
#############################################################
#
# serout (list);
#
# Output character(s) to the serial device.
#
# This returns the number of characters written.
sub serout {
my $n; # chars written per iteration,
my $t; # end time,
my $count; # how much we need to write
# Loop until all characters are written, or we timeout. For the
# old WPS method, we output one character per iteration, otherwise
# we write as fast as the hardware will take it -- which might be
# only one per iteration, but at least it won't have all the awful
# sleeps in it.
for ($t= time + 2, $count= scalar @_; $count and (time < $t); ) {
$n= syswrite (DEV, join ('', @_), scalar @_);
# Now account for what just took place.
if (not defined $n) {
print "ERROR DURING SERIAL WRITE\n";
return undef;
}
splice (@_, 0, $n - 1, $n) # drop written chars,
if $n; # if any were,
$count -= $n; # ...
}
return $count == 0;
}
#############################################################
#
# $c= &serin ($count, $eol);
#
# Read up to $count characters from the serial port. It returns
# when $count is reached, or we timeout waiting for characters.
# If $eol is defined, stop reading when we see that character.
# (It's only for the version string, which is LF terminated.)
# We can afford one-second granularity, so generic and portable
# time-of-day works fine. Note that we wait for a delta > 1;
# resolution is 1 second, so the first time() could be made 1 uS
# before the second rolls over.
sub serin {
my $count= shift;
my $eol= shift;
my $c; # chars to return else undef
my $n; # chars read per call,
my $t; # end time
my $to; # timeout waiting
$t= time + 2; # end time,
$c= undef; # assume the worst,
while ($count) {
$n= sysread (DEV, my $foo, $count);
if ($n) {
$c= "" if not defined $c;
$c .= $foo; # more read,
$count -= $n; # adjust count,
last if defined $eol and $c =~ /$eol/;
}
if (time > $t) { # oops
print "SERIAL READ TIMEOUT\n";
return undef;
}
}
return $c;
}