Well, here is something like a partial answer - although the question about the use of bash is still open. I tried to look a little bit in some C code solutions - and that, it seems, isn't trivial either! :)
First, let's see what possibly doesn't work for this case - below is an example from "between write and read:serial port. - C":
// from: between write and read:serial port. - C - http://www.daniweb.com/forums/thread286634.html
// gcc -o sertest -Wall -g sertest.c
#include <stdio.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
int main(int argc, char *argv[])
{
char line[1024];
int chkin;
char input[1024];
char msg[1024];
char serport[24];
// argv[1] - serial port
// argv[2] - file or echo
sprintf(serport, "%s", argv[1]);
int file= open(serport, O_RDWR | O_NOCTTY | O_NDELAY);
if (file == 0)
{
sprintf(msg, "open_port: Unable to open %s.
", serport);
perror(msg);
}
else
fcntl(file, F_SETFL, FNDELAY); //fcntl(file, F_SETFL, 0);
while (1)
{
printf("enter input data:
");
scanf("%s",&input[0]);
chkin=write(file,input,sizeof input);
if (chkin<0)
{
printf("cannot write to port
");
}
//chkin=read(file,line,sizeof line);
while ((chkin=read(file,line,sizeof line))>=0)
{
if (chkin<0)
{
printf("cannot read from port
");
}
else
{
printf("bytes: %d, line=%s
",chkin, line);
}
}
/*CODE TO EXIT THE LOOP GOES HERE*/
if (input[0] == 'q') break;
}
close(file);
return 0;
}
The problem with the above code is that it doesn't explicitly initialize the serial port for character ("raw") operation; so depending on how the port was set previously, a session may look like this:
$ ./sertest /dev/ttyUSB0
enter input data:
t1
enter input data:
t2
enter input data:
t3
enter input data:
^C
... in other words, there is no echoing of the input data. However, if the serial port is set up properly, we can get a session like:
$ ./sertest /dev/ttyUSB0
enter input data:
t1
enter input data:
t2
bytes: 127, line=t1
enter input data:
t3
bytes: 127, line=t2
enter input data:
t4
bytes: 127, line=t3
enter input data:
^C
... (but even then, this sertest
code fails on input words greater than 3 characters.)
Finally, through some online digging, I managed to find "(SOLVED) Serial Programming, Write-Read Issue", which offers a writeread.cpp
example. However, for this byte-by-byte "duplex" case, not even that was enough - namely, "Serial Programming HOWTO" notes: "Canonical Input Processing ... is the normal processing mode for terminals ... which means that a read will only return a full line of input. A line is by default terminated by a NL (ASCII LF) ..." ; and thus we have to explicitly set the serial port to "non-canonical" (or "raw") mode in our code via ICANON
(in other words, just setting O_NONBLOCK
via open
is not enough) - an example for that is given at "3.2 How can I read single characters from the terminal? - Unix Programming Frequently Asked Questions - 3. Terminal I/O". Once that is done, calling writeread
will "correctly" set the serial port for the serport
example (above), as well.
So I changed some of that writeread
code back to C, added the needed initialization stuff, as well as time measurement, possibility to send strings or files, and additional output stream (for 'piping' the read serial data to a separate file). The code is below as writeread.c
and serial.h
, and with it, I can do something like in the following Bash session:
$ ./writeread /dev/ttyUSB0 2000000 writeread.c 3>myout.txt
stdalt opened; Alternative file descriptor: 3
Opening port /dev/ttyUSB0;
Got speed 2000000 (4107/0x100b);
Got file/string 'writeread.c'; opened as file (4182).
+++DONE+++
Wrote: 4182 bytes; Read: 4182 bytes; Total: 8364 bytes.
Start: 1284422340 s 443302 us; End: 1284422347 s 786999 us; Delta: 7 s 343697 us.
2000000 baud for 8N1 is 200000 Bps (bytes/sec).
Measured: write 569.47 Bps, read 569.47 Bps, total 1138.94 Bps.
$ diff writeread.c myout.txt
$ ./writeread /dev/ttyUSB0 2000000 writeread.c 3>/dev/null
stdalt opened; Alternative file descriptor: 3
Opening port /dev/ttyUSB0;
Got speed 2000000 (4107/0x100b);
Got file/string 'writeread.c'; opened as file (4182).
+++DONE+++
Wrote: 4182 bytes; Read: 4182 bytes; Total: 8364 bytes.
Start: 1284422380 s -461710 us; End: 1284422388 s 342977 us; Delta: 8 s 804687 us.
2000000 baud for 8N1 is 200000 Bps (bytes/sec).
Measured: write 474.97 Bps, read 474.97 Bps, total 949.95 Bps.
Well:
- First surprise - it goes faster if I'm writing to a file, than if I'm piping to
/dev/null
!
- Also, getting around 1000 Bps - whereas the device is apparently set for 200000 BPS!!
At this point, I'm thinking that the slowdown is because after each written byte in writeread.c
, we wait for a flag to be cleared by the read interrupt, before we proceed to read the serial buffer. Possibly, if the reading and writing were separate threads, then both reading and writing could try to use bigger blocks of bytes in single read
or write
calls, and so bandwidth would be used better ?! (Or, maybe the interrupt handler does act, in some sense, like a "thread" running in parallel - so maybe something similar could be achieved by moving all read related functions to the interrupt handler ?!)
Ah well - at this point, I am very open to suggestions / links for existing code like writeread.c
, but multithreaded :) And, of course, for any other possible Linux tools, or possibly Bash methods (although it seems Bash will not be able to exert this kind of control...)
Cheers!
writeread.c:
/*
writeread.c - based on writeread.cpp
[SOLVED] Serial Programming, Write-Read Issue - http://www.linuxquestions.org/questions/programming-9/serial-programming-write-read-issue-822980/
build with: gcc -o writeread -Wall -g writeread.c
*/
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <sys/time.h>
#include "serial.h"
int serport_fd;
void usage(char **argv)
{
fprintf(stdout, "Usage:
");
fprintf(stdout, "%s port baudrate file/string
", argv[0]);
fprintf(stdout, "Examples:
");
fprintf(stdout, "%s /dev/ttyUSB0 115200 /path/to/somefile.txt
", argv[0]);
fprintf(stdout, "%s /dev/ttyUSB0 115200 "some text test"
", argv[0]);
}
int main( int argc, char **argv )
{
if( argc != 4 ) {
usage(argv);
return 1;
}
char *serport;
char *serspeed;
speed_t serspeed_t;
char *serfstr;
int serf_fd; // if < 0, then serfstr is a string
int bytesToSend;
int sentBytes;
char byteToSend[2];
int readChars;
int recdBytes, totlBytes;
char sResp[11];
struct timeval timeStart, timeEnd, timeDelta;
float deltasec;
/* Re: connecting alternative output stream to terminal -
* http://coding.derkeiler.com/Archive/C_CPP/comp.lang.c/2009-01/msg01616.html
* send read output to file descriptor 3 if open,
* else just send to stdout
*/
FILE *stdalt;
if(dup2(3, 3) == -1) {
fprintf(stdout, "stdalt not opened; ");
stdalt = fopen("/dev/tty", "w");
} else {
fprintf(stdout, "stdalt opened; ");
stdalt = fdopen(3, "w");
}
fprintf(stdout, "Alternative file descriptor: %d
", fileno(stdalt));
// Get the PORT name
serport = argv[1];
fprintf(stdout, "Opening port %s;
", serport);
// Get the baudrate
serspeed = argv[2];
serspeed_t = string_to_baud(serspeed);
fprintf(stdout, "Got speed %s (%d/0x%x);
", serspeed, serspeed_t, serspeed_t);
//Get file or command;
serfstr = argv[3];
serf_fd = open( serfstr, O_RDONLY );
fprintf(stdout, "Got file/string '%s'; ", serfstr);
if (serf_fd < 0) {
bytesToSend = strlen(serfstr);
fprintf(stdout, "interpreting as string (%d).
", bytesToSend);
} else {
struct stat st;
stat(serfstr, &st);
bytesToSend = st.st_size;
fprintf(stdout, "opened as file (%d).
", bytesToSend);
}
// Open and Initialise port
serport_fd = open( serport, O_RDWR | O_NOCTTY | O_NONBLOCK );
if ( serport_fd < 0 ) { perror(serport); return 1; }
initport( serport_fd, serspeed_t );
sentBytes = 0; recdBytes = 0;
byteToSend[0]='x'; byteToSend[1]='';
gettimeofday( &timeStart, NULL );
// write / read loop - interleaved (i.e. will always write
// one byte at a time, before 'emptying' the read buffer )
while ( sentBytes < bytesToSend )
{
// read next byte from input...
if (serf_fd < 0) { //interpreting as string
byteToSend[0] = serfstr[sentBytes];
} else { //opened as file
read( serf_fd, &byteToSend[0], 1 );
}
if ( !writeport( serport_fd, byteToSend ) ) {
fprintf(stdout, "write failed
");
}
//~ fprintf(stdout, "written:%s
", byteToSend );
while ( wait_flag == TRUE );
if ( (readChars = readport( serport_fd, sResp, 10)) >= 0 )
{
//~ fprintf(stdout, "InVAL: (%d) %s
", readChars, sResp);
recdBytes += readChars;
fprintf(stdalt, "%s", sResp);
}
wait_flag = TRUE; // was ==
//~ usleep(50000);
sentBytes++;
}
gettimeofday( &timeEnd, NULL );
// Close the open port
close( serport_fd );
if (!(serf_fd < 0)) close( serf_fd );
fprintf(stdout, "
+++DONE+++
");
totlBytes = sentBytes + recdBytes;
timeval_subtract(&timeDelta, &timeEnd, &timeStart);
deltasec = timeDelta.tv_sec+timeDelta.tv_usec*1e-6;
fprintf(stdout, "Wrote: %d bytes; Read: %d bytes; Total: %d bytes.
", sentBytes, recdBytes, totlBytes);
fprintf(stdout, "Start: %ld s %ld us; End: %ld s %ld us; Delta: %ld s %ld us.
", timeStart.tv_sec, timeStart.tv_usec, timeEnd.tv_sec, timeEnd.tv_usec, timeDelta.tv_sec, timeDelta.tv_usec);
fprintf(stdout, "%s baud for 8N1 is %d Bps (bytes/sec).
", serspeed, atoi(serspeed)/10);
fprintf(stdout, "Measured: write %.02f Bps, read %.02f Bps, total %.02f Bps.
", sentBytes/deltasec, recdBytes/deltasec, totlBytes/deltasec);
return 0;
}
serial.h:
/* serial.h
(C) 2004-5 Captain http://www.captain.at
Helper functions for "ser"
Used fo