Simple, small C program for testing serial bandwidth - c

I have two laptops with a serial port. How do I test the actual bandwidth of the serial port between the two machines using a simple, small C program?
In reality, I need to do this on an embedded Linux system which is why the utility must be a small, simple C program (because the embedded environment only has limited library support meaning it doesn't have python, perl, or any other fancy libraries).

I started this as a new question because I didn't want to take this question off topic: Serial port loopback test
That question was regarding testing the bandwidth of a serial port in loopback mode, so that you don't have to plug in an actual serial cable. The author (sdaau) put a lot of time into creating a multi-threaded serial bandwidth test program to answer his own question. I then used his simple C program and extended it to be used between two different physical machines connected with a serial cable.
It is necessary to start the "remote" side which will wait for the "initiator" side (the local side) to send a go byte, in which both will proceed to transfer data asynchronously. The program (which sdaau calls writeread.c) spawns 2 threads: one which writes data and the other which reads data. In this way, you are fully utilizing the serial port. You can pass in a datafile as a command-line argument.
As an example, here is the "remote" side:
./writeread /dev/ttyUSB0 115200 ./datafile.dat 3> output
As an example, here is the "local" (or initiator) side:
./writeread /dev/ttyUSB0 115200 ./datafile.dat -I 3> output
Note that I had some trouble redirecting the output on the remote side, meaning that 3> output didn't really work. I'm not sure whats going on with that, but my local side worked fine. Also, note the remote side's output timing is skewed because it has a timer running while it is waiting for the initiator. This means you should only trust the bandwidth printout from the local initiator side (see the original question for output results details).
Since both sides are sending the same datafile in this example, you should be able to compare the "output" file with the datafile:
diff output datafile.dat
Complie the code with:
gcc -c -Wall writeread.c
gcc writeread.o -lpthread -o writeread
Here is the modified writeread.c code:
/*
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 -lpthread -Wall -g writeread.c
*/
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <sys/time.h>
#include <pthread.h>
#include "writeread.h"
int serport_fd;
//POSIX Threads Programming - https://computing.llnl.gov/tutorials/pthreads/#PassingArguments
struct write_thread_data{
int fd;
char* comm; //string to send
int bytesToSend;
int writtenBytes;
int iator; // Initiator. 0 = False, 1 = True
};
void usage(char **argv)
{
fprintf(stdout, "Usage:\n");
fprintf(stdout, "%s port baudrate file/string [-I]\n", argv[0]);
fprintf(stdout, " The -I is for initiator. Run on the remote side which "
"will wait, then start locally with -I which will initiate "
"the test.\n");
fprintf(stdout, "Examples:\n");
fprintf(stdout, "%s /dev/ttyUSB0 115200 /path/to/somefile.txt\n", argv[0]);
fprintf(stdout, "%s /dev/ttyUSB0 115200 \"some text test\"\n", argv[0]);
}
// POSIX threads explained - http://www.ibm.com/developerworks/library/l-posix1.html
// instead of writeport
void *write_thread_function(void *arg) {
int go = 0;
int lastBytesWritten;
struct write_thread_data *my_data;
my_data = (struct write_thread_data *) arg;
fprintf(stdout, "write_thread_function spawned\n");
// Are we the initiator?
if (my_data->iator == 1) {
// We are the initiator, send the start command
go = 0xde;
write(my_data->fd, &go, 1);
} else {
// We wait for the initiator to send us the start command
fprintf(stdout, "Waiting for initiator (start other end with -I)...\n");
read(my_data->fd, &go, 1);
if (go == 0xde) {
fprintf(stdout, "Go!\n");
} else {
fprintf(stdout, "Error: Did not receive start command [0x%x]\n", go);
return NULL;
}
}
my_data->writtenBytes = 0;
while(my_data->writtenBytes < my_data->bytesToSend)
{
lastBytesWritten = write( my_data->fd, my_data->comm + my_data->writtenBytes, my_data->bytesToSend - my_data->writtenBytes );
my_data->writtenBytes += lastBytesWritten;
if ( lastBytesWritten < 0 )
{
fprintf(stdout, "write failed!\n");
return 0;
}
fprintf(stderr, " write: %d - %d\n", lastBytesWritten, my_data->writtenBytes);
}
return NULL; //pthread_exit(NULL)
}
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 sentBytes;
int readChars;
int recdBytes, totlBytes;
char* sResp;
char* sRespTotal;
struct timeval timeStart, timeEnd, timeDelta;
float deltasec, expectBps, measReadBps, measWriteBps;
struct write_thread_data wrdata;
pthread_t myWriteThread;
/* 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\n", fileno(stdalt));
// Get the PORT name
serport = argv[1];
fprintf(stdout, "Opening port %s;\n", serport);
// Get the baudrate
serspeed = argv[2];
serspeed_t = string_to_baud(serspeed);
fprintf(stdout, "Got speed %s (%d/0x%x);\n", serspeed, serspeed_t, serspeed_t);
//Get file or command;
serfstr = argv[3];
// Are we the initiator?
if (argc == 5 &&
strncmp(argv[4], "-I", 3) == 0 )
{
wrdata.iator = 1; // Initiator. 0 = False, 1 = True
} else {
wrdata.iator = 0; // Initiator. 0 = False, 1 = True
}
serf_fd = open( serfstr, O_RDONLY );
fprintf(stdout, "Got file/string '%s'; ", serfstr);
if (serf_fd < 0) {
wrdata.bytesToSend = strlen(serfstr);
wrdata.comm = serfstr; //pointer already defined
fprintf(stdout, "interpreting as string (%d).\n", wrdata.bytesToSend);
} else {
struct stat st;
stat(serfstr, &st);
wrdata.bytesToSend = st.st_size;
wrdata.comm = (char *)calloc(wrdata.bytesToSend, sizeof(char));
read(serf_fd, wrdata.comm, wrdata.bytesToSend);
fprintf(stdout, "opened as file (%d).\n", wrdata.bytesToSend);
}
sResp = (char *)calloc(wrdata.bytesToSend, sizeof(char));
sRespTotal = (char *)calloc(wrdata.bytesToSend, sizeof(char));
// 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 );
wrdata.fd = serport_fd;
sentBytes = 0; recdBytes = 0;
gettimeofday( &timeStart, NULL );
// start the thread for writing..
if ( pthread_create( &myWriteThread, NULL, write_thread_function, (void *) &wrdata) ) {
printf("error creating thread.");
abort();
}
// run read loop
while ( recdBytes < wrdata.bytesToSend )
{
while ( wait_flag == TRUE );
if ( (readChars = read( serport_fd, sResp, wrdata.bytesToSend)) >= 0 )
{
//~ fprintf(stdout, "InVAL: (%d) %s\n", readChars, sResp);
// binary safe - add sResp chunk to sRespTotal
memmove(sRespTotal+recdBytes, sResp+0, readChars*sizeof(char));
/* // text safe, but not binary:
sResp[readChars] = '\0';
fprintf(stdalt, "%s", sResp);
*/
recdBytes += readChars;
} else {
if ( errno == EAGAIN )
{
fprintf(stdout, "SERIAL EAGAIN ERROR\n");
return 0;
}
else
{
fprintf(stdout, "SERIAL read error: %d = %s\n", errno , strerror(errno));
return 0;
}
}
fprintf(stderr, " read: %d\n", recdBytes);
wait_flag = TRUE; // was ==
//~ usleep(50000);
}
if ( pthread_join ( myWriteThread, NULL ) ) {
printf("error joining thread.");
abort();
}
gettimeofday( &timeEnd, NULL );
// binary safe - dump sRespTotal to stdalt
fwrite(sRespTotal, sizeof(char), recdBytes, stdalt);
// Close the open port
close( serport_fd );
if (!(serf_fd < 0)) {
close( serf_fd );
free(wrdata.comm);
}
free(sResp);
free(sRespTotal);
fprintf(stdout, "\n+++DONE+++\n");
sentBytes = wrdata.writtenBytes;
totlBytes = sentBytes + recdBytes;
timeval_subtract(&timeDelta, &timeEnd, &timeStart);
deltasec = timeDelta.tv_sec+timeDelta.tv_usec*1e-6;
expectBps = atoi(serspeed)/10.0f;
measWriteBps = sentBytes/deltasec;
measReadBps = recdBytes/deltasec;
fprintf(stdout, "Wrote: %d bytes; Read: %d bytes; Total: %d bytes. \n", sentBytes, recdBytes, totlBytes);
fprintf(stdout, "Start: %ld s %ld us; End: %ld s %ld us; Delta: %ld s %ld us. \n", 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).\n", serspeed, (int)expectBps);
fprintf(stdout, "Measured: write %.02f Bps (%.02f%%), read %.02f Bps (%.02f%%), total %.02f Bps.\n", measWriteBps, (measWriteBps/expectBps)*100, measReadBps, (measReadBps/expectBps)*100, totlBytes/deltasec);
return 0;
}
And here is the .h file which I have renamed from the original question to writeread.h:
/* writeread.h
(C) 2004-5 Captain http://www.captain.at
Helper functions for "ser"
Used for testing the PIC-MMC test-board
http://www.captain.at/electronic-index.php
*/
#include <stdio.h> /* Standard input/output definitions */
#include <string.h> /* String function definitions */
#include <unistd.h> /* UNIX standard function definitions */
#include <fcntl.h> /* File control definitions */
#include <errno.h> /* Error number definitions */
#include <termios.h> /* POSIX terminal control definitions */
#include <sys/signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#define TRUE 1
#define FALSE 0
int wait_flag = TRUE; // TRUE while no signal received
// Definition of Signal Handler
void DAQ_signal_handler_IO ( int status )
{
//~ fprintf(stdout, "received SIGIO signal %d.\n", status);
wait_flag = FALSE;
}
int writeport( int fd, char *comm )
{
int len = strlen( comm );
int n = write( fd, comm, len );
if ( n < 0 )
{
fprintf(stdout, "write failed!\n");
return 0;
}
return n;
}
int readport( int fd, char *resp, size_t nbyte )
{
int iIn = read( fd, resp, nbyte );
if ( iIn < 0 )
{
if ( errno == EAGAIN )
{
fprintf(stdout, "SERIAL EAGAIN ERROR\n");
return 0;
}
else
{
fprintf(stdout, "SERIAL read error: %d = %s\n", errno , strerror(errno));
return 0;
}
}
if ( resp[iIn-1] == '\r' )
resp[iIn-1] = '\0';
else
resp[iIn] = '\0';
return iIn;
}
int getbaud( int fd )
{
struct termios termAttr;
int inputSpeed = -1;
speed_t baudRate;
tcgetattr( fd, &termAttr );
// Get the input speed
baudRate = cfgetispeed( &termAttr );
switch ( baudRate )
{
case B0: inputSpeed = 0; break;
case B50: inputSpeed = 50; break;
case B110: inputSpeed = 110; break;
case B134: inputSpeed = 134; break;
case B150: inputSpeed = 150; break;
case B200: inputSpeed = 200; break;
case B300: inputSpeed = 300; break;
case B600: inputSpeed = 600; break;
case B1200: inputSpeed = 1200; break;
case B1800: inputSpeed = 1800; break;
case B2400: inputSpeed = 2400; break;
case B4800: inputSpeed = 4800; break;
case B9600: inputSpeed = 9600; break;
case B19200: inputSpeed = 19200; break;
case B38400: inputSpeed = 38400; break;
case B115200: inputSpeed = 115200; break;
case B2000000: inputSpeed = 2000000; break; //added
}
return inputSpeed;
}
/* ser.c
(C) 2004-5 Captain http://www.captain.at
Sends 3 characters (ABC) via the serial port (/dev/ttyS0) and reads
them back if they are returned from the PIC.
Used for testing the PIC-MMC test-board
http://www.captain.at/electronic-index.php
*/
int initport( int fd, speed_t baudRate )
{
struct termios options;
struct sigaction saio; // Definition of Signal action
// Install the signal handler before making the device asynchronous
saio.sa_handler = DAQ_signal_handler_IO;
saio.sa_flags = 0;
saio.sa_restorer = NULL;
sigaction( SIGIO, &saio, NULL );
// Allow the process to receive SIGIO
fcntl( fd, F_SETOWN, getpid() );
// Make the file descriptor asynchronous (the manual page says only
// O_APPEND and O_NONBLOCK, will work with F_SETFL...)
fcntl( fd, F_SETFL, FASYNC );
//~ fcntl( fd, F_SETFL, FNDELAY); //doesn't work; //fcntl(file, F_SETFL, 0);
// Get the current options for the port...
tcgetattr( fd, &options );
/*
// Set port settings for canonical input processing
options.c_cflag = BAUDRATE | CRTSCTS | CLOCAL | CREAD;
options.c_iflag = IGNPAR | ICRNL;
//options.c_iflag = IGNPAR;
options.c_oflag = 0;
options.c_lflag = ICANON;
//options.c_lflag = 0;
options.c_cc[VMIN] = 0;
options.c_cc[VTIME] = 0;
*/
/* ADDED - else 'read' will not return, unless it sees LF '\n' !!!!
* From: Unix Programming Frequently Asked Questions - 3. Terminal I/O -
* http://www.steve.org.uk/Reference/Unix/faq_4.html
*/
/* Disable canonical mode, and set buffer size to 1 byte */
options.c_lflag &= (~ICANON);
options.c_cc[VTIME] = 0;
options.c_cc[VMIN] = 1;
// Set the baud rates to...
cfsetispeed( &options, baudRate );
cfsetospeed( &options, baudRate );
// Enable the receiver and set local mode...
options.c_cflag |= ( CLOCAL | CREAD );
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
// Flush the input & output...
tcflush( fd, TCIOFLUSH );
// Set the new options for the port...
tcsetattr( fd, TCSANOW, &options );
return 1;
}
/*
ripped from
http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/stty.c
*/
#define STREQ(a, b) (strcmp((a), (b)) == 0)
struct speed_map
{
const char *string; /* ASCII representation. */
speed_t speed; /* Internal form. */
unsigned long int value; /* Numeric value. */
};
static struct speed_map const speeds[] =
{
{"0", B0, 0},
{"50", B50, 50},
{"75", B75, 75},
{"110", B110, 110},
{"134", B134, 134},
{"134.5", B134, 134},
{"150", B150, 150},
{"200", B200, 200},
{"300", B300, 300},
{"600", B600, 600},
{"1200", B1200, 1200},
{"1800", B1800, 1800},
{"2400", B2400, 2400},
{"4800", B4800, 4800},
{"9600", B9600, 9600},
{"19200", B19200, 19200},
{"38400", B38400, 38400},
{"exta", B19200, 19200},
{"extb", B38400, 38400},
#ifdef B57600
{"57600", B57600, 57600},
#endif
#ifdef B115200
{"115200", B115200, 115200},
#endif
#ifdef B230400
{"230400", B230400, 230400},
#endif
#ifdef B460800
{"460800", B460800, 460800},
#endif
#ifdef B500000
{"500000", B500000, 500000},
#endif
#ifdef B576000
{"576000", B576000, 576000},
#endif
#ifdef B921600
{"921600", B921600, 921600},
#endif
#ifdef B1000000
{"1000000", B1000000, 1000000},
#endif
#ifdef B1152000
{"1152000", B1152000, 1152000},
#endif
#ifdef B1500000
{"1500000", B1500000, 1500000},
#endif
#ifdef B2000000
{"2000000", B2000000, 2000000},
#endif
#ifdef B2500000
{"2500000", B2500000, 2500000},
#endif
#ifdef B3000000
{"3000000", B3000000, 3000000},
#endif
#ifdef B3500000
{"3500000", B3500000, 3500000},
#endif
#ifdef B4000000
{"4000000", B4000000, 4000000},
#endif
{NULL, 0, 0}
};
static speed_t
string_to_baud (const char *arg)
{
int i;
for (i = 0; speeds[i].string != NULL; ++i)
if (STREQ (arg, speeds[i].string))
return speeds[i].speed;
return (speed_t) -1;
}
/* http://www.gnu.org/software/libtool/manual/libc/Elapsed-Time.html
Subtract the `struct timeval' values X and Y,
storing the result in RESULT.
Return 1 if the difference is negative, otherwise 0. */
int timeval_subtract (struct timeval *result, struct timeval *x, struct timeval *y)
{
/* Perform the carry for the later subtraction by updating y. */
if (x->tv_usec < y->tv_usec) {
int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
y->tv_usec -= 1000000 * nsec;
y->tv_sec += nsec;
}
if (x->tv_usec - y->tv_usec > 1000000) {
int nsec = (x->tv_usec - y->tv_usec) / 1000000;
y->tv_usec += 1000000 * nsec;
y->tv_sec -= nsec;
}
/* Compute the time remaining to wait.
tv_usec is certainly positive. */
result->tv_sec = x->tv_sec - y->tv_sec;
result->tv_usec = x->tv_usec - y->tv_usec;
/* Return 1 if result is negative. */
return x->tv_sec < y->tv_sec;
}

Related

V4L2 VIDIOC_REQBUFS cannot allocate enough memory

I am writing a MIPI driver without using the I2C functionality, since it is not possible for me to use it. I am writing this for the Google Coral.
To write this new driver I looked at this example and adjusted it to only use the MMAP functionality.
My new code is this:
/*
* V4L2 video capture example
*
* This program can be used and distributed without restrictions.
*
* This program is provided with the V4L2 API
* see http://linuxtv.org/docs.php for more information
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <getopt.h> /* getopt_long() */
#include <fcntl.h> /* low-level i/o */
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#define CLEAR(x) memset(&(x), 0, sizeof(x))
#ifndef V4L2_PIX_FMT_H264
#define V4L2_PIX_FMT_H264 v4l2_fourcc('H', '2', '6', '4') /* H264 with start codes */
#endif
struct buffer {
void *start;
size_t length;
};
static char *dev_name;
static int fd = -1;
struct buffer *buffers;
static unsigned int n_buffers;
static int out_buf;
static int force_format;
static int frame_count = 200;
static int frame_number = 0;
static void errno_exit(const char *s)
{
fprintf(stderr, "%s error %d, %s\n", s, errno, strerror(errno));
exit(EXIT_FAILURE);
}
static int xioctl(int fh, int request, void *arg)
{
int r;
do {
r = ioctl(fh, request, arg);
} while (-1 == r && EINTR == errno);
return r;
}
static void process_image(const void *p, int size)
{
printf("processing image\n");
frame_number++;
char filename[15];
sprintf(filename, "frame-%d.raw", frame_number); // filename becomes frame-x.raw
FILE *fp=fopen(filename,"wb");
if (out_buf)
fwrite(p, size, 1, fp); // write data to file fp
fflush(fp);
fclose(fp);
}
static int read_frame(void)
{
printf("reading frame\n");
struct v4l2_buffer buf;
unsigned int i;
CLEAR(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf)) {
switch (errno) {
case EAGAIN:
return 0;
case EIO:
/* Could ignore EIO, see spec. */
/* fall through */
default:
errno_exit("VIDIOC_DQBUF");
}
}
assert(buf.index < n_buffers);
process_image(buffers[buf.index].start, buf.bytesused);
if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
errno_exit("VIDIOC_QBUF");
return 1;
}
static void mainloop(void)
{
printf("mainloop\n");
unsigned int count;
count = frame_count;
while (count-- > 0) {
printf("count number = %d\n", count );
for (;;) {
fd_set fds;
struct timeval tv;
int r;
FD_ZERO(&fds); // clear file descriptor
FD_SET(fd, &fds); // set file descriptors to the descriptor fd
/* Timeout. */
tv.tv_sec = 2;
tv.tv_usec = 0;
r = select(fd + 1, &fds, NULL, NULL, &tv); // select uses a timeout, allows program to monitor file descriptors waiting untill files becomes "ready"
// returns the number of file descriptors changed. This maybe zero if timeout expires.
// probably watching reafds descriptor to change?
if (-1 == r) {
if (EINTR == errno)
continue;
errno_exit("select");
}
if (0 == r) {
fprintf(stderr, "select timeout\n");
exit(EXIT_FAILURE);
}
if (read_frame()) // if one of the descriptors is set, a frame can be read.
break;
/* EAGAIN - continue select loop. */
}
}
printf("mainloop ended\n");
}
static void stop_capturing(void)
{
printf("stop capturing\n");
enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type))
errno_exit("VIDIOC_STREAMOFF");
printf("capturing stopped\n");
}
static void start_capturing(void)
{
printf("initiating capturing\n");
unsigned int i;
enum v4l2_buf_type type;
for (i = 0; i < n_buffers; ++i) {
struct v4l2_buffer buf;
CLEAR(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
errno_exit("VIDIOC_QBUF");
}
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (-1 == xioctl(fd, VIDIOC_STREAMON, &type)){
errno_exit("VIDIOC_STREAMON");
}
printf("capturing initiated\n");
}
static void uninit_device(void)
{
unsigned int i;
for (i = 0; i < n_buffers; ++i){
if (-1 == munmap(buffers[i].start, buffers[i].length)){
errno_exit("munmap");
}
}
free(buffers);
}
static void init_mmap(void)
{
printf("initiating mmap buffer\n");
struct v4l2_requestbuffers req; //struct with details of the buffer to compose
CLEAR(req);
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { // initiate buffer
if (EINVAL == errno) {
fprintf(stderr, "%s does not support "
"memory mapping\n", dev_name);
exit(EXIT_FAILURE);
} else {
errno_exit("VIDIOC_REQBUFS");
}
}
printf("memory allocated\n");
if (req.count < 2) {
fprintf(stderr, "Insufficient buffer memory on %s\n",
dev_name);
exit(EXIT_FAILURE);
}
buffers = calloc(req.count, sizeof(*buffers)); // make the amount of buffers available
if (!buffers) {
fprintf(stderr, "Out of memory\n");
exit(EXIT_FAILURE);
}
for (n_buffers = 0; n_buffers < req.count; ++n_buffers) { // go through buffers and adjust struct in it
struct v4l2_buffer buf;
CLEAR(buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = n_buffers;
if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf))
errno_exit("VIDIOC_QUERYBUF");
buffers[n_buffers].length = buf.length;
buffers[n_buffers].start =
mmap(NULL /* start anywhere */,
buf.length,
PROT_READ | PROT_WRITE /* required */,
MAP_SHARED /* recommended */,
fd, buf.m.offset);
if (MAP_FAILED == buffers[n_buffers].start)
errno_exit("mmap");
}
printf("mmap buffer initiated\n");
}
static void init_device(void)
{
printf("initiating device\n");
struct v4l2_capability cap;
struct v4l2_cropcap cropcap;
struct v4l2_crop crop;
struct v4l2_format fmt;
unsigned int min;
if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap)) { // gets information about driver and harware capabilities
if (EINVAL == errno) { // driver is not compatible with specifications
fprintf(stderr, "%s is no V4L2 device\n",
dev_name);
exit(EXIT_FAILURE);
} else {
errno_exit("VIDIOC_QUERYCAP");
}
}
if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
fprintf(stderr, "%s is no video capture device\n",
dev_name);
exit(EXIT_FAILURE);
}
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
fprintf(stderr, "%s does not support streaming i/o\n",
dev_name);
exit(EXIT_FAILURE);
}
/* Select video input, video standard and tune here. */
CLEAR(cropcap);
cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (0 == xioctl(fd, VIDIOC_CROPCAP, &cropcap)) { // used to get cropping limits, pixel aspects, ... fill in type field and get all this information back
crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
crop.c = cropcap.defrect; /* reset to default */
if (-1 == xioctl(fd, VIDIOC_S_CROP, &crop)) { // get cropping rectangle
switch (errno) {
case EINVAL:
printf("EINVAL in VIDIOC_S_CROP\n");
/* Cropping not supported. */
break;
default:
printf("other error in VIDIOC_S_CROP\n");
/* Errors ignored. */
break;
}
}
} else {
/* Errors ignored. */
}
CLEAR(fmt); // set the format of the v4l2 video
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (force_format) {
fprintf(stderr, "Set H264\r\n");
fmt.fmt.pix.width = 640; //replace
fmt.fmt.pix.height = 480; //replace
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_H264; //replace
fmt.fmt.pix.field = V4L2_FIELD_ANY;
if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt))
errno_exit("VIDIOC_S_FMT");
/* Note VIDIOC_S_FMT may change width and height. */
} else {
/* Preserve original settings as set by v4l2-ctl for example */
if (-1 == xioctl(fd, VIDIOC_G_FMT, &fmt))
errno_exit("VIDIOC_G_FMT");
}
/* Buggy driver paranoia. */
min = fmt.fmt.pix.width * 2;
if (fmt.fmt.pix.bytesperline < min)
fmt.fmt.pix.bytesperline = min;
min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
if (fmt.fmt.pix.sizeimage < min)
fmt.fmt.pix.sizeimage = min;
init_mmap();
printf("device inititiated\n");
}
static void close_device(void)
{
printf("closing device\n");
if (-1 == close(fd))
errno_exit("close");
fd = -1;
printf("device closed\n");
}
/*
struct stat {
dev_t st_dev; ID of device containing file
ino_t st_ino; Inode number
mode_t st_mode; File type and mode
nlink_t st_nlink; Number of hard links
uid_t st_uid; User ID of owner
gid_t st_gid; Group ID of owner
dev_t st_rdev; Device ID (if special file)
off_t st_size; Total size, in bytes
blksize_t st_blksize; Block size for filesystem I/O
blkcnt_t st_blocks; Number of 512B blocks allocated
Since Linux 2.6, the kernel supports nanosecond
precision for the following timestamp fields.
For the details before Linux 2.6, see NOTES.
struct timespec st_atim; Time of last access
struct timespec st_mtim; Time of last modification
struct timespec st_ctim; Time of last status change
#define st_atime st_atim.tv_sec Backward compatibility
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
*/
static void open_device(void)
{
printf("openening device\n");
struct stat st;
if (-1 == stat(dev_name, &st)) { // stat() returns info about file into struct
fprintf(stderr, "Cannot identify '%s': %d, %s\n",
dev_name, errno, strerror(errno));
exit(EXIT_FAILURE);
}
if (!S_ISCHR(st.st_mode)) {
fprintf(stderr, "%s is no device\n", dev_name);
exit(EXIT_FAILURE);
}
fd = open(dev_name, O_RDWR /* required */ | O_NONBLOCK, 0); // open the file dev/video0, returns a file descriptor
// if fd == -1, the file could not be opened.
if (-1 == fd) {
fprintf(stderr, "Cannot open '%s': %d, %s\n",
dev_name, errno, strerror(errno));
exit(EXIT_FAILURE);
}
printf("device opened\n");
}
int main(int argc, char **argv)
{
printf("main begins\n");
dev_name = "/dev/video0";
for (;;) {
printf("back here\n");
break;
}
open_device();
init_device();
start_capturing();
mainloop();
stop_capturing();
uninit_device();
close_device();
fprintf(stderr, "\n");
return 0;
}
However I get the following error:
VIDIOC_REQBUFS error 12, Cannot allocate memory
The entire output is:
main begins
back here
openening device
device opened
initiating device
initiating mmap buffer
VIDIOC_REQBUFS error 12, Cannot allocate memory
make: *** [makefile:3: all] Error 1
In the above code this is caused by:
if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req)) { // initiate buffer
if (EINVAL == errno) {
fprintf(stderr, "%s does not support "
"memory mapping\n", dev_name);
exit(EXIT_FAILURE);
} else {
errno_exit("VIDIOC_REQBUFS");
}
}
Thus the ioctl(fd, VIDIOC_REQBUFS, &req) causes this error.
I have already looked on StackOverflow and found 1 other person with the same mistake.
He suggested to change CONFIG_CMA_SIZE_MBYTES to 32 from 16. I tried this by looking where I could find this setting. I found it in: boot/config-4.14.98-imx . However, it was already 320. (yes tenfold). I am now rather stuck on this. Is there a problem in my code, or do I need to change the setting from 320 to 32 (which seems counterintuitive).
With kind regards.

Why is epoll_wait not responding to terminal input?

The following code, when ran, does not respond to terminal input. I have ran this under Debian 10.5.0 and CentOS 7 with the same results. What am I missing?
#include <sys/epoll.h>
#include <stdio.h>
#include <unistd.h>
int main(void) {
char buffer[4096];
int fd = epoll_create1(EPOLL_CLOEXEC);
struct epoll_event event;
int ctl_ret;
event.events = EPOLLIN;
event.data.fd = STDIN_FILENO;
ctl_ret = epoll_ctl(fd, EPOLL_CTL_ADD, STDIN_FILENO, &event);
if (ctl_ret) {
perror("epoll_ctl");
return 1;
}
int nr_event;
for (;;) {
fprintf(stderr, "Starting epoll_wait\n");
nr_event = epoll_wait(fd, &event, 1, -1);
if (nr_event < 0) {
perror("epoll_wait");
return 1;
}
fprintf(stderr, "Reading: %d\n", event.data.fd);
printf("%ld\n", read(0, buffer, sizeof(buffer)));
}
}
As Basile Starynkevitch alluded to, I needed to update the terminal configuration via termios(3).
I have provided the revised code below for reference. For the changes required, please see the code between the // TERMINAL SETUP and // EPOLL SETUP comments.
#include <errno.h>
#include <linux/input.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/epoll.h>
#include <termios.h>
#include <unistd.h>
int main(void) {
// TERMINAL SETUP
struct termios tty_cfg, tty_cfg_cache;
// Check that fd is a TTY
if (!isatty(STDIN_FILENO)) {
fprintf(stderr, "Standard input is not a terminal.\n");
return EXIT_FAILURE;
}
// Save old terminal configuration
if (tcgetattr(STDIN_FILENO, &tty_cfg_cache) == -1 ||
tcgetattr(STDIN_FILENO, &tty_cfg) == -1) {
fprintf(stderr, "Cannot get terminal settings: %s.\n", strerror(errno));
return EXIT_FAILURE;
}
// Set new terminal configuration
tty_cfg.c_iflag &= ~(IGNBRK | BRKINT | PARMRK);
tty_cfg.c_lflag &= ~(ICANON | ISIG | ECHO | IEXTEN | TOSTOP);
tty_cfg.c_cc[VMIN] = 0;
tty_cfg.c_cc[VTIME] = 0;
tty_cfg.c_cc[VSTART] = 0;
tty_cfg.c_cc[VSTOP] = 0;
if (tcsetattr(STDIN_FILENO, TCSANOW, &tty_cfg) == -1) {
const int saved_errno = errno;
tcsetattr(STDIN_FILENO, TCSANOW, &tty_cfg_cache);
fprintf(stderr, "Cannot set terminal settings: %s.\n", strerror(saved_errno));
return EXIT_FAILURE;
}
// EPOLL SETUP
// Create epoll
int epfd = epoll_create1(EPOLL_CLOEXEC);
// Add stdin control interface to epoll
struct epoll_event event;
int ctl_ret;
event.events = EPOLLIN;
event.data.fd = STDIN_FILENO;
ctl_ret = epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &event);
if (ctl_ret) {
perror("epoll_ctl");
return 1;
}
// LOOP AND MONITOR EPOLL
int nr_event;
unsigned char keys[16];
ssize_t n;
size_t i, done;
done = 0;
while (!done) {
// Start epoll_wait
nr_event = epoll_wait(epfd, &event, 1, -1);
if (nr_event < 0) {
perror("epoll_wait");
return 1;
}
// Read STDIN_FILENO
n = read(STDIN_FILENO, keys, sizeof keys);
if (n > 0) {
for (i = 0; i < n; i++) {
// Quit if 'q' or 'Q' is pressed
if (keys[i] == 'q' || keys[i] == 'Q')
done = 1;
if (keys[i] >= 32 && keys[i] <= 126)
printf("Key '%c' = 0x%02x = %u pressed\n", keys[i], keys[i], keys[i]);
else
if (keys[i])
printf("Key '\\%03o' = 0x%02x = %u pressed\n", keys[i], keys[i], keys[i]);
else
printf("NUL key (0) pressed\n");
}
fflush(stdout);
}
}
// RESTORE TERMINAL SETTINGS
tcsetattr(STDIN_FILENO, TCSAFLUSH, &tty_cfg_cache);
return EXIT_SUCCESS;
}

How to read serial with interrupt serial?

I'm trying to read NMEA message in Linux. But I can't get a completely message:
54.441,V,,,,,0.00,0.00,010720,,,N*42
$GPVTG,0.00,T,,M,0.00,N,0.00,K,N*32
$GPGGA,020954.441,,,,,0,0,,,M,,M,,*43
$GPGSA,A,1,,,,,,,,,,,,,,,*1E
$GPGSA,A,1,,,,,,,,,,,,,,,*1E
$GPGSV,1,1,00*79
$GLGSV,1,1,00*65
$GPGLL,,,,,020954.441,V,N*71
$GP
The first line and last line is the one message but it have been splited. I thing, It's cause by sleep 1 second. And It's not right at all. I think I should use interrupt serial.
My idea is when data in come, interrupt serial will run a function which read serial and handle it. After that, system will sleep until next message. I searched some material but It's doesn't help.
This is my new code and it's not working:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <errno.h>
#include <termios.h>
void signal_handler_IO ();
int fd;
int connected;
struct termios termAttr;
struct sigaction saio;
int main(int argc, char *argv[])
{
fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1)
{
perror("open_port: Unable to open port\n");
exit(1);
}
saio.sa_handler = signal_handler_IO;
saio.sa_flags = 0;
saio.sa_restorer = NULL;
sigaction(SIGIO,&saio,NULL);
fcntl(fd, F_SETFL, FNDELAY);
fcntl(fd, F_SETOWN, getpid());
fcntl(fd, F_SETFL, O_ASYNC );
tcgetattr(fd,&termAttr);
cfsetispeed(&termAttr,B9600);
cfsetospeed(&termAttr,B9600);
termAttr.c_cflag &= ~PARENB;
termAttr.c_cflag &= ~CSTOPB;
termAttr.c_cflag &= ~CSIZE;
termAttr.c_cflag |= CS8;
termAttr.c_cflag |= (CLOCAL | CREAD);
termAttr.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
termAttr.c_iflag &= ~(IXON | IXOFF | IXANY);
termAttr.c_oflag &= ~OPOST;
tcsetattr(fd,TCSANOW,&termAttr);
printf("UART1 configured....\n");
while(1){
sleep(1);
}
close(fd);
exit(0);
}
void signal_handler_IO ()
{
FILE *csv;
char buff [1024];
int n = read(fd, &buff, sizeof(buff));
char * token = strtok(buff, ",");
csv=fopen("csvfile.csv","w");
while( token != NULL ) {
fprintf(csv,"%s\n",token);
token = strtok(NULL, ",");
}
fclose(csv);
}
What should I do now ?
NMEA message are lines, ending with a '\n'.
Change read() to fgets() (open using fopen()) and read as a line into a string for later strtok() processing.
See also #Craig Estey ideas.
This is prefaced by my top comment.
thank you for your positive response. Did you mean I should use read() function like my old code ? And actually, I have never working with select before. But I very interesting with your idea. And I hope you can show me more the way which apply on my case.
Okay, here's a simple [and untested] version that does not use a signal handler. And, I'm using poll instead of select [they are similar] because it's easier to use.
Note that you opened the TTY device file with O_NDELAY, so the read call is non-blocking.
Also note that the open device will not produce an EOF condition either the way you did it or the way I'm doing it.
So, you'll have to have code that looks for a line that signifies the last line (e.g. $GP).
Or, after an initial wait the first time in the loop, a subsequent timeout should indicate no more input
Anyway, here's the code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <errno.h>
#include <termios.h>
#if 1
#include <poll.h>
#endif
void signal_handler_IO(); /* definition of signal handler */
int fd;
struct termios termAttr;
struct sigaction saio;
struct pollfd fdpoll;
int
main(int argc, char *argv[])
{
int timout;
FILE *fout = NULL;
int buf_has_GP = 0;
int lastchr = -1;
int curchr;
int err;
int rlen;
int idx;
char buf[1000];
fd = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) {
perror("open_port: Unable to open port\n");
exit(1);
}
#if 0
saio.sa_handler = signal_handler_IO;
saio.sa_flags = 0;
saio.sa_restorer = NULL;
sigaction(SIGIO, &saio, NULL);
#endif
fcntl(fd, F_SETFL, FNDELAY);
fcntl(fd, F_SETOWN, getpid());
fcntl(fd, F_SETFL, O_ASYNC);
tcgetattr(fd, &termAttr);
cfsetispeed(&termAttr, B9600);
cfsetospeed(&termAttr, B9600);
termAttr.c_cflag &= ~PARENB;
termAttr.c_cflag &= ~CSTOPB;
termAttr.c_cflag &= ~CSIZE;
termAttr.c_cflag |= CS8;
termAttr.c_cflag |= (CLOCAL | CREAD);
termAttr.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
termAttr.c_iflag &= ~(IXON | IXOFF | IXANY);
termAttr.c_oflag &= ~OPOST;
tcsetattr(fd, TCSANOW, &termAttr);
printf("UART1 configured....\n");
fout = fopen("csvfile.csv","w");
fdpoll.fd = fd;
fdpoll.events = POLLIN;
timout = 10000;
while (1) {
err = poll(&fdpoll,1,timout);
// timeout
if (err == 0)
break;
// error
if (err < 0) {
fprintf(stderr,"error -- %s\n",strerror(errno));
break;
}
// err will always be _one_ because poll's second arg is 1
while (1) {
rlen = read(fd,buf,sizeof(buf));
if (rlen <= 0)
break;
fwrite(buf,1,rlen,fout);
// need to check buf looking for last line (e.g. $GP)
// to know when to stop
// since read is _not_ line oriented we have to check for G followed
// by P [and they may or may not occur in the same read call]
// FIXME -- this is quite crude -- just to illustrate
for (idx = 0; idx < rlen; ++idx) {
curchr = buf[idx];
buf_has_GP = ((lastchr == 'G') && (curchr == 'P'));
if (buf_has_GP)
break;
lastchr = curchr;
}
if (buf_has_GP)
break;
}
if (buf_has_GP)
break;
timout = 1000;
#if 0
sleep(1);
#endif
}
close(fd);
fclose(fout);
exit(0);
}
void
signal_handler_IO()
{
FILE *csv;
FILE *ff;
char buff[1024];
ff = fopen("/dev/ttyUSB0", "r");
// int n = read(fd, &buff, sizeof(buff));
fgets(buff, sizeof(buff), ff);
char *token = strtok(buff, ",");
csv = fopen("csvfile.csv", "w");
while (token != NULL) {
fprintf(csv, "%s\n", token);
token = strtok(NULL, ",");
}
sleep(0.2);
fclose(csv);
}
UPDATE:
Thank you so much for spend your time for me. I compiled it without error. Unfortunately, I get nothing from output and empty file.
This may have been because of the simple/crude EOF string detection code. I think it could have stopped prematurely. I've added more robust checking.
I've also added debug printing (if -d is given).
Because I don't have access to a real ttyUSB device, I've added test code using a PTY [pseudo TTY]. To use this, put the sample NMEA data into a file (e.g. input.txt) and add -pinput.txt to the arguments.
This was how I was able to debug the general program flow.
I've turned off any unnecessary fcntl options.
If, after trying this, you still have issues, you may wish to test your device interface with a terminal program (e.g. minicom) to verify that the remote device is, indeed, sending data.
If minicom produces output, but your program doesn't, you may have to modify some of the termios options.
Some usbtty/uart devices need RTS/CTS [I've actually used such a device for work]. minicom has a config option to deal with this.
In the program [although I suspect it's off by default], you may need to disable RTS/CTS hardware so that the port doesn't get hung up. And/or ensure that XON/XOFF flow control is disabled.
Or, the remote device needs RTS/CTS support [you have to somehow force the remote device to see CTS high]. Although unlikely, this might have to be done in the cable itself.
Anyway, here's the updated code:
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <errno.h>
#include <termios.h>
#if 1
#include <poll.h>
#include <pty.h>
#include <sys/wait.h>
#include <time.h>
#endif
#ifndef RAWOUT
#define RAWOUT 1
#endif
void signal_handler_IO(); /* definition of signal handler */
const char *ttydev = "/dev/ttyUSB0";
int fd;
int opt_d; // 1=debug print
char *opt_pty; // PTY input file
int ptypid;
#define PTYSLP 1
FILE *fout = NULL;
struct termios termAttr;
struct sigaction saio;
struct pollfd fdpoll;
int linelen;
char linebuf[1000];
#define SHOWPOLL(_msk) \
if (events & _msk) \
bp += sprintf(bp,"/" #_msk)
typedef long long tsc_t;
tsc_t
tscget(void)
{
struct timespec ts;
tsc_t tsc;
static tsc_t tsczero = 0;
clock_gettime(CLOCK_REALTIME,&ts);
tsc = ts.tv_sec;
tsc *= 1000000000;
tsc += ts.tv_nsec;
if (tsczero == 0)
tsczero = tsc;
tsc -= tsczero;
return tsc;
}
double
tscsec(tsc_t tsc)
{
double sec;
sec = tsc;
sec /= 1e9;
return sec;
}
void
tscprt(void)
{
tsc_t tsc;
tsc = tscget();
printf("%.9f ",tscsec(tsc));
}
#define dbgprt(_fmt...) \
do { \
if (! opt_d) \
break; \
int sverr = errno; \
tscprt(); \
printf(_fmt); \
errno = sverr; \
} while (0)
// dopty -- generate pseudo TTY test device
void
dopty(void)
{
int fdm;
int fdtxt;
int rlen;
int wlen;
int off;
char buf[1000];
#if 0
fdm = open("/dev/pts/ptmx",O_RDWR | O_NDELAY);
#else
fdm = getpt();
#endif
if (fdm < 0) {
perror("dopty/open");
exit(1);
}
dbgprt("dopty: GETPT fdm=%d\n",fdm);
ttydev = ptsname(fdm);
dbgprt("dopty: PTSNAME ttydev=%s\n",ttydev);
grantpt(fdm);
unlockpt(fdm);
dbgprt("dopty: INPUT opt_pty=%s\n",opt_pty);
do {
ptypid = fork();
if (ptypid != 0) {
close(fdm);
break;
}
// open sample test data file
fdtxt = open(opt_pty,O_RDONLY);
if (fdtxt < 0) {
perror("dopty/open");
exit(1);
}
sleep(PTYSLP);
while (1) {
rlen = read(fdtxt,buf,sizeof(buf));
if (rlen <= 0)
break;
dbgprt("dopty: READ rlen=%d\n",rlen);
for (off = 0; off < rlen; off += wlen) {
wlen = rlen - off;
wlen = write(fdm,&buf[off],wlen);
dbgprt("dopty: WRITE wlen=%d\n",wlen);
}
}
sleep(PTYSLP);
dbgprt("dopty: CLOSE\n");
close(fdtxt);
close(fdm);
sleep(PTYSLP);
dbgprt("dopty: EXIT\n");
exit(0);
} while (0);
}
char *
showpoll(short events)
{
char *bp;
static char buf[1000];
bp = buf;
bp += sprintf(bp,"%4.4X",events);
SHOWPOLL(POLLIN);
SHOWPOLL(POLLPRI);
SHOWPOLL(POLLOUT);
SHOWPOLL(POLLRDHUP);
SHOWPOLL(POLLERR);
SHOWPOLL(POLLHUP);
return buf;
}
// lineadd -- add character to line buffer
void
lineadd(int chr)
{
char *bp;
char buf[10];
if (opt_d) {
bp = buf;
*bp = 0;
if ((chr >= 0x20) && (chr <= 0x7E))
bp += sprintf(bp," '%c'",chr);
dbgprt("showchr: CHR chr=%2.2X%s\n",chr,buf);
}
linebuf[linelen++] = chr;
linebuf[linelen] = 0;
}
// eoftst -- decide if current line is the last line
int
eoftst(void)
{
static char *eofstr = "$GP\n";
static int eoflen = 0;
int stopflg = 0;
if (eoflen == 0)
eoflen = strlen(eofstr);
stopflg = ((linelen == eoflen) && (memcmp(linebuf,eofstr,eoflen) == 0));
dbgprt("eoftst: %s\n",stopflg ? "STOP" : "CONT");
return stopflg;
}
int
main(int argc, char **argv)
{
int timout;
int buf_has_eof = 0;
int curchr;
int err;
int rlen;
int idx;
char buf[1000];
--argc;
++argv;
setlinebuf(stdout);
setlinebuf(stderr);
for (; argc > 0; --argc, ++argv) {
char *cp = *argv;
if (*cp != '-')
break;
cp += 2;
switch (cp[-1]) {
case 'd':
opt_d = ! opt_d;
break;
case 'p':
opt_pty = (*cp != 0) ? cp : "input.txt";
break;
}
}
do {
// create test device
if (opt_pty != NULL) {
dopty();
break;
}
if (argc > 0) {
ttydev = *argv;
--argc;
++argv;
}
} while (0);
dbgprt("main: TTYDEV ttydev=%s\n",ttydev);
fd = open(ttydev, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1) {
perror("open_port: Unable to open port\n");
exit(1);
}
#if 0
saio.sa_handler = signal_handler_IO;
saio.sa_flags = 0;
saio.sa_restorer = NULL;
sigaction(SIGIO, &saio, NULL);
#endif
// not needed unless doing signal handler
#if 0
fcntl(fd, F_SETFL, FNDELAY);
fcntl(fd, F_SETOWN, getpid());
fcntl(fd, F_SETFL, O_ASYNC);
#endif
#if 1
tcgetattr(fd, &termAttr);
#endif
#if 1
cfsetispeed(&termAttr, B9600);
cfsetospeed(&termAttr, B9600);
#endif
// force immediate return from device read if no chars available
#if 1
dbgprt("main: CC VMIN=%d VTIME=%d\n",
termAttr.c_cc[VMIN],termAttr.c_cc[VTIME]);
termAttr.c_cc[VMIN] = 0;
termAttr.c_cc[VTIME] = 0;
#endif
termAttr.c_cflag &= ~PARENB;
termAttr.c_cflag &= ~CSTOPB;
termAttr.c_cflag &= ~CSIZE;
termAttr.c_cflag |= CS8;
termAttr.c_cflag |= (CLOCAL | CREAD);
// FIXME -- you may need to handle this
#if 1
termAttr.c_cflag &= ~CRTSCTS;
#else
termAttr.c_cflag |= CRTSCTS;
#endif
termAttr.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
termAttr.c_iflag &= ~(IXON | IXOFF | IXANY);
termAttr.c_oflag &= ~OPOST;
#if 1
tcsetattr(fd, TCSANOW, &termAttr);
#endif
printf("UART1 configured....\n");
// open output file
fout = fopen("csvfile.csv","w");
if (fout == NULL) {
perror("main/fopen");
exit(1);
}
fdpoll.fd = fd;
fdpoll.events = POLLIN;
fdpoll.revents = 0;
// set initial timeout of 10 seconds
timout = 10000;
// NOTE: iter is just for testing to prevent infinite looping if failure to
// read or match the EOF string
for (int iter = 1; iter < 10; ++iter) {
dbgprt("main: POLL iter=%d events=%s timout=%d\n",
iter,showpoll(fdpoll.events),timout);
err = poll(&fdpoll,1,timout);
dbgprt("main: POLL revents=%s err=%d\n",showpoll(fdpoll.revents),err);
// timeout
if (err == 0)
break;
// error
if (err < 0) {
fprintf(stderr,"error -- %s\n",strerror(errno));
break;
}
// err will always be _one_ because poll's second arg is 1
// process all data in current chunk
while (1) {
rlen = read(fd,buf,sizeof(buf));
dbgprt("main: READ iter=%d rlen=%d\n",iter,rlen);
if (rlen <= 0)
break;
// send data to output file
#if RAWOUT
fwrite(buf,1,rlen,fout);
#endif
// need to check buf looking for last line (e.g. $GP)
// to know when to stop
// since read is _not_ line oriented we have to check for G followed
// by P [and they may or may not occur in the same read call]
// FIXME -- this is quite crude -- just to illustrate
for (idx = 0; idx < rlen; ++idx) {
curchr = buf[idx];
// add to line buffer
lineadd(curchr);
// wait for newline
if (curchr != '\n')
continue;
// decide if this is the last line of the current NMEA message
buf_has_eof = eoftst();
#if (! RAWOUT)
// do processing on line buffer ...
#endif
// reset line buffer index/length for next line
linelen = 0;
if (buf_has_eof)
break;
}
if (buf_has_eof)
break;
}
if (buf_has_eof)
break;
// set 1 second timeout for subsequent reads
timout = 1000;
#if 0
sleep(1);
#endif
}
close(fd);
fclose(fout);
// reap any child processes [only if doing PTY mode]
while (opt_pty != NULL) {
pid_t pid = wait(NULL);
dbgprt("main: WAIT pid=%d\n",pid);
if (pid <= 0)
break;
}
exit(0);
}
void
signal_handler_IO()
{
FILE *csv;
FILE *ff;
char buff[1024];
ff = fopen("/dev/ttyUSB0", "r");
// int n = read(fd, &buff, sizeof(buff));
fgets(buff, sizeof(buff), ff);
char *token = strtok(buff, ",");
csv = fopen("csvfile.csv", "w");
while (token != NULL) {
fprintf(csv, "%s\n", token);
token = strtok(NULL, ",");
}
sleep(0.2);
fclose(csv);
}

libssh2: libssh2_channel_write() doesn't seem to write data on the channel

I am trying to execute a command on a router via ssh. After the login, when I execute the command on the device, it asks for an additional password. I am not able to send the password using libssh2_channel_write(). Here is the code snippet (modified the ssh2_exec.c that comes with the library). This is a snippet where the device is authenticated and the command has been issued. This loop just tries to get read the output of the executed command:
for( ;; )
{
/* loop until we block */
int rc;
do
{
char buffer[0x4000];
rc = libssh2_channel_read( channel, buffer, sizeof(buffer) );
if( rc > 0 )
{
int i;
char *enable = "stic-isr2951-t1";
int ret;
bytecount += rc;
fprintf(stderr, "We read [%d] bytes:\n", bytecount);
for( i=0; i < rc; ++i )
fputc( buffer[i], stderr);
**if ( strstr(buffer, "assword:") != NULL ){
fprintf(stderr, "Sending the additional password now\n");
ret = libssh2_channel_write(channel, enable, strlen(enable));
fprintf(stderr, "Wrote [%d] bytes\n", ret);
}**
}
else {
if( rc != LIBSSH2_ERROR_EAGAIN )
/* no need to output this for the EAGAIN case */
fprintf(stderr, "libssh2_channel_read returned %d\n", rc);
}
}
while( rc > 0 );
/* this is due to blocking that would occur otherwise so we loop on
this condition */
if( rc == LIBSSH2_ERROR_EAGAIN )
{
waitsocket(sock, session);
}
else
break;
}
In the snippet above, the code that detects that the device is posting a password prompt is:
if ( strstr(buffer, "assword:") != NULL ){
fprintf(stderr, "Sending the additional password now\n");
ret = libssh2_channel_write(channel, enable, strlen(enable));
fprintf(stderr, "Wrote [%d] bytes\n", ret);
}
That's where I have a problem. The password being sent on the channel isn't working as the device continues to timeout expecting the password. There is no indication that libssh2_channel_write() failed as the return value says it wrote the password properly.
Am I missing something?
EDIT:
The problem with the continuous timeout password prompted was because the password didn't have \n at the end. I was expecting the lib to take care of it but it didn't.
Now that I am able to send the password to the remote device, I run into another issue. After I send the password via libssh2_channel_write(), subsequent libssh2_channel_read() fails with
LIBSSH2_ERROR_SOCKET_RECV
I am not sure why is this happening. Logic was to check if the libssh2_channel_write() was successful by doing a subsequent read() (which would give the command prompt on the remote device) and then issue the command to be executed on the remote device followed by a subsequent read to get the command output. Am I doing something wrong? This doesn't seem to be working. Here's the complete code snippet:
/*
* Sample showing how to use libssh2 to execute a command remotely.
*
* The sample code has fixed values for host name, user name, password
* and command to run.
*
* Run it like this:
*
* $ ./ssh2_exec 127.0.0.1 user password "uptime"
*
*/
#include "libssh2_config.h"
#include <libssh2.h>
#include <string.h>
#ifdef HAVE_WINSOCK2_H
#include <winsock2.h>
#endif
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h>
#endif
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#include <sys/time.h>
#include <sys/types.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <ctype.h>
static int waitsocket(int socket_fd, LIBSSH2_SESSION *session)
{
struct timeval timeout;
int rc;
fd_set fd;
fd_set *writefd = NULL;
fd_set *readfd = NULL;
int dir;
timeout.tv_sec = 10;
timeout.tv_usec = 0;
FD_ZERO(&fd);
FD_SET(socket_fd, &fd);
/* now make sure we wait in the correct direction */
dir = libssh2_session_block_directions(session);
if(dir & LIBSSH2_SESSION_BLOCK_INBOUND)
readfd = &fd;
if(dir & LIBSSH2_SESSION_BLOCK_OUTBOUND)
writefd = &fd;
rc = select(socket_fd + 1, readfd, writefd, NULL, &timeout);
return rc;
}
int main(int argc, char *argv[])
{
const char *hostname = "10.10.10.10";
const char *commandline = "show version";
const char *username = "user1";
const char *password = "password1";
unsigned long hostaddr;
int flag = 0;
int sock;
struct sockaddr_in sin;
const char *fingerprint;
LIBSSH2_SESSION *session;
LIBSSH2_CHANNEL *channel;
int rc;
int exitcode;
char *exitsignal=(char *)"none";
int bytecount = 0;
size_t len;
LIBSSH2_KNOWNHOSTS *nh;
int type;
if (argc > 1)
/* must be ip address only */
hostname = argv[1];
if (argc > 2) {
username = argv[2];
}
if (argc > 3) {
password = argv[3];
}
if (argc > 4) {
commandline = argv[4];
}
rc = libssh2_init (0);
if (rc != 0) {
fprintf (stderr, "libssh2 initialization failed (%d)\n", rc);
return 1;
}
hostaddr = inet_addr(hostname);
printf("host address is: %ld\n", hostaddr);
/* Ultra basic "connect to port 22 on localhost"
* Your code is responsible for creating the socket establishing the
* connection
*/
sock = socket(AF_INET, SOCK_STREAM, 0);
sin.sin_family = AF_INET;
sin.sin_port = htons(22);
sin.sin_addr.s_addr = hostaddr;
if (connect(sock, (struct sockaddr*)(&sin),
sizeof(struct sockaddr_in)) != 0) {
fprintf(stderr, "failed to connect!\n");
return -1;
}
/* Create a session instance */
session = libssh2_session_init();
if (!session)
return -1;
//libssh2_trace(session, LIBSSH2_TRACE_AUTH|LIBSSH2_TRACE_SOCKET);
/* tell libssh2 we want it all done non-blocking */
libssh2_session_set_blocking(session, 0);
/* ... start it up. This will trade welcome banners, exchange keys,
* and setup crypto, compression, and MAC layers
*/
while ((rc = libssh2_session_handshake(session, sock)) ==
LIBSSH2_ERROR_EAGAIN);
if (rc) {
fprintf(stderr, "Failure establishing SSH session: %d\n", rc);
return -1;
}
nh = libssh2_knownhost_init(session);
if(!nh) {
/* eeek, do cleanup here */
return 2;
}
/* read all hosts from here */
libssh2_knownhost_readfile(nh, "known_hosts",
LIBSSH2_KNOWNHOST_FILE_OPENSSH);
/* store all known hosts to here */
libssh2_knownhost_writefile(nh, "dumpfile",
LIBSSH2_KNOWNHOST_FILE_OPENSSH);
fingerprint = libssh2_session_hostkey(session, &len, &type);
if(fingerprint) {
struct libssh2_knownhost *host;
#if LIBSSH2_VERSION_NUM >= 0x010206
/* introduced in 1.2.6 */
int check = libssh2_knownhost_checkp(nh, hostname, 22,
fingerprint, len,
LIBSSH2_KNOWNHOST_TYPE_PLAIN|
LIBSSH2_KNOWNHOST_KEYENC_RAW,
&host);
#else
/* 1.2.5 or older */
int check = libssh2_knownhost_check(nh, hostname,
fingerprint, len,
LIBSSH2_KNOWNHOST_TYPE_PLAIN|
LIBSSH2_KNOWNHOST_KEYENC_RAW,
&host);
#endif
fprintf(stderr, "Host check: %d, key: %s\n", check,
(check <= LIBSSH2_KNOWNHOST_CHECK_MISMATCH)?
host->key:"<none>");
/*****
* At this point, we could verify that 'check' tells us the key is
* fine or bail out.
*****/
}
else {
/* eeek, do cleanup here */
return 3;
}
libssh2_knownhost_free(nh);
if ( strlen(password) != 0 ) {
/* We could authenticate via password */
while ((rc = libssh2_userauth_password(session, username, password)) ==
LIBSSH2_ERROR_EAGAIN);
if (rc) {
fprintf(stderr, "Authentication by password failed.\n");
goto shutdown;
}
}
else {
/* Or by public key */
while ((rc = libssh2_userauth_publickey_fromfile(session, username,
"/home/user/"
".ssh/id_rsa.pub",
"/home/user/"
".ssh/id_rsa",
password)) ==
LIBSSH2_ERROR_EAGAIN);
if (rc) {
fprintf(stderr, "\tAuthentication by public key failed\n");
goto shutdown;
}
}
#if 1
//libssh2_trace(session, ~0 );
#endif
/* Exec non-blocking on the remove host */
while( (channel = libssh2_channel_open_session(session)) == NULL &&
libssh2_session_last_error(session,NULL,NULL,0) ==
LIBSSH2_ERROR_EAGAIN )
{
waitsocket(sock, session);
}
if( channel == NULL )
{
fprintf(stderr,"Error\n");
exit( 1 );
}
while( (rc = libssh2_channel_exec(channel, commandline)) ==
LIBSSH2_ERROR_EAGAIN )
{
waitsocket(sock, session);
}
if( rc != 0 )
{
fprintf(stderr,"Error\n");
exit( 1 );
}
for( ;; )
{
/* loop until we block */
int rc;
do
{
char buffer[0x4000];
rc = libssh2_channel_read( channel, buffer, sizeof(buffer) );
if( rc > 0 )
{
int i;
char *enable = "check-password\n";
int ret;
bytecount += rc;
fprintf(stderr, "We read [%d] bytes:\n", bytecount);
fputc('[', stderr);
for( i=0; i < rc; ++i )
fputc( buffer[i], stderr);
fputc(']', stderr);
if ( strstr(buffer, "Password:") != NULL ){
fprintf(stderr, "Sending the password now\n");
while((ret = libssh2_channel_write(channel, enable, strlen(enable))) == LIBSSH2_ERROR_EAGAIN) {
printf("ERROR_EAGAIN - sending password again\n");
}
fprintf(stderr, "Wrote [%d] bytes: \n", ret);
flag = 1;
continue;
}
if (!flag){ // start
char *cmd = "show clock\n";
int ret;
fprintf(stderr, "THIS is Fetching show clock command now\n");
while((ret = libssh2_channel_write(channel, cmd, strlen(cmd))) == LIBSSH2_ERROR_EAGAIN) {
printf("ERROR_EAGAIN - sending show clock again\n");
}
flag = 1;
} // end
}
else {
if(rc != LIBSSH2_ERROR_EAGAIN)
fprintf(stderr, "libssh2_channel_read returned [%d]:\n ", rc);
}
}
while( rc > 0 );
/* this is due to blocking that would occur otherwise so we loop on
this condition */
if( rc == LIBSSH2_ERROR_EAGAIN )
{
int check;
check = waitsocket(sock, session);
}
else
break;
}
exitcode = 127;
while( (rc = libssh2_channel_close(channel)) == LIBSSH2_ERROR_EAGAIN )
waitsocket(sock, session);
if( rc == 0 )
{
exitcode = libssh2_channel_get_exit_status( channel );
libssh2_channel_get_exit_signal(channel, &exitsignal,
NULL, NULL, NULL, NULL, NULL);
}
if (exitsignal)
fprintf(stderr, "\nGot signal: %s\n", exitsignal);
else
fprintf(stderr, "\nEXIT: %d bytecount: %d\n", exitcode, bytecount);
libssh2_channel_free(channel);
channel = NULL;
shutdown:
libssh2_session_disconnect(session,
"Normal Shutdown, Thank you for playing");
libssh2_session_free(session);
#ifdef WIN32
closesocket(sock);
#else
close(sock);
#endif
fprintf(stderr, "all done\n");
libssh2_exit();
return 0;
}
Any thoughts?

Serial port loopback/duplex test, in Bash or C? (process substitution)

I have a serial device set up as loopback (meaning it will simply echo back any character it receives), and I'd like to measure effective throughput speed. For this, I hoped I could use time, as in
time bash -c '...'
where '...' would be some command I could run.
Now, the first problem is that I want to use the device at 2000000 bps, so I cannot use ttylog or screen (they both seem to go up to 115200 bps only). However, working with /dev/ttyUSB0 as a file (using file redirection and cat) seems to work fine:
# initialize serial port
stty 2000000 -ixon icanon </dev/ttyUSB0
# check settings
stty -a -F /dev/ttyUSB0
# in one terminal - read from serial port
while (true) do cat -A /dev/ttyUSB0 ; done
# in other terminal - write to serial port
echo "1234567890" > /dev/ttyUSB0
# back to first terminal, I now have:
# $ while (true) do cat -A /dev/ttyUSB0 ; done
# 1234567890$
# ...
Now, I'd like to do something similar - I'd like to cat a file to a serial port, and have the serial port read back - but from a single terminal command (so I could use it as argument to time).
I thought that I could use a Bash process substitution, to have the "writing" and "reading" part go, sort of, in "parallel" - if I try it with named pipes, it works:
# mkfifo my.pipe # same as below:
$ mknod my.pipe p
$ comm <(echo -e "test\ntest\ntest\n" > my.pipe) <(cat my.pipe)
test
test
test
comm: file 2 is not in sorted order
Up there, I'm not using comm for any other purpose, than to (sort of) merge the two processes into a single command (I guess, I could have just as well used echo instead).
Unfortunately, that trick does not seem to work with a serial port, because when I try it, I sometimes get:
$ comm <(echo "1234567890" > /dev/ttyUSB0) <(while (true) do cat -A /dev/ttyUSB0 ; done)
cat: /dev/ttyUSB0: Invalid argument
..., however, usually I just get no output whatsoever. This tells me that: either there is no control of which process starts first, and so cat may start reading before the port is ready (however, that doesn't seem to be a problem in the first example above); or in Linux/Bash, you cannot both read and write to a serial port at the same time, and so the "Invalid argument" would occur in those moments when both read and write seem to happen at the same time.
So my questions are:
Is there a way to do something like this (cat a file to a serial port configured as loopback; read it back and see how long it takes) only in Bash, without resorting to writing a dedicated C program?
If I need a dedicated C program, any source examples out there on the net I could use?
Thanks a lot for any responses,
Cheers!
EDIT: I am aware that the while loop written above does not exit; that command line was for preliminary testing, and I interrupt it using Ctrl-C. ( I could in principle interrupt it with something like timeout -9 0.1 bash -c 'while (true) do echo AA ; done', but that would defeat the purpose of time, then :) )
The reason that while is there, is that for the time being, reading via cat from the device exits immediately; at times, I have set up the device, so that when cat is issued, it in fact blocks and waits for incoming data; but I cannot as of yet figure what's going on (and partially that is why I'm looking for a way to test from the command line).
In case I didn't use the while, I imagine for timing, I'd use something like:
time bash -c 'comm <(echo "1234567890" > /dev/ttyUSB0) <(cat -A /dev/ttyUSB0)'
... however for this to be working, sort of, assumes that cat -A /dev/ttyUSB0 starts first and blocks; then the echo writes to the serial port (and exits); and then cat -A outputs whatever it read from the serial port - and then exits. (And I'm not really sure neither if a serial port can behave this way at all, nor if cat can be made to block and exit arbitrarily like that).
The exact method really doesn't matter; if at all possible, I'd just like to avoid coding my own C program to do this kind of testing - which is why my primary interest is if it is somehow possible to run such a "full-duplex test" using basic Bash/Linux (i.e. coreutils); (and if not, if there is a ready-made code I can use for something like this).
EDIT2: Also possibly relevant:
Parallel processes in Bash - Ubuntu Forums
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.\n", serport);
perror(msg);
}
else
fcntl(file, F_SETFL, FNDELAY); //fcntl(file, F_SETFL, 0);
while (1)
{
printf("enter input data:\n");
scanf("%s",&input[0]);
chkin=write(file,input,sizeof input);
if (chkin<0)
{
printf("cannot write to port\n");
}
//chkin=read(file,line,sizeof line);
while ((chkin=read(file,line,sizeof line))>=0)
{
if (chkin<0)
{
printf("cannot read from port\n");
}
else
{
printf("bytes: %d, line=%s\n",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:\n");
fprintf(stdout, "%s port baudrate file/string\n", argv[0]);
fprintf(stdout, "Examples:\n");
fprintf(stdout, "%s /dev/ttyUSB0 115200 /path/to/somefile.txt\n", argv[0]);
fprintf(stdout, "%s /dev/ttyUSB0 115200 \"some text test\"\n", 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\n", fileno(stdalt));
// Get the PORT name
serport = argv[1];
fprintf(stdout, "Opening port %s;\n", serport);
// Get the baudrate
serspeed = argv[2];
serspeed_t = string_to_baud(serspeed);
fprintf(stdout, "Got speed %s (%d/0x%x);\n", 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).\n", bytesToSend);
} else {
struct stat st;
stat(serfstr, &st);
bytesToSend = st.st_size;
fprintf(stdout, "opened as file (%d).\n", 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]='\0';
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\n");
}
//~ fprintf(stdout, "written:%s\n", byteToSend );
while ( wait_flag == TRUE );
if ( (readChars = readport( serport_fd, sResp, 10)) >= 0 )
{
//~ fprintf(stdout, "InVAL: (%d) %s\n", 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, "\n+++DONE+++\n");
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. \n", sentBytes, recdBytes, totlBytes);
fprintf(stdout, "Start: %ld s %ld us; End: %ld s %ld us; Delta: %ld s %ld us. \n", 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).\n", serspeed, atoi(serspeed)/10);
fprintf(stdout, "Measured: write %.02f Bps, read %.02f Bps, total %.02f Bps.\n", 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 for testing the PIC-MMC test-board
http://www.captain.at/electronic-index.php
*/
#include <stdio.h> /* Standard input/output definitions */
#include <string.h> /* String function definitions */
#include <unistd.h> /* UNIX standard function definitions */
#include <fcntl.h> /* File control definitions */
#include <errno.h> /* Error number definitions */
#include <termios.h> /* POSIX terminal control definitions */
#include <sys/signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#define TRUE 1
#define FALSE 0
int wait_flag = TRUE; // TRUE while no signal received
// Definition of Signal Handler
void DAQ_signal_handler_IO ( int status )
{
//~ fprintf(stdout, "received SIGIO signal %d.\n", status);
wait_flag = FALSE;
}
int writeport( int fd, char *comm )
{
int len = strlen( comm );
int n = write( fd, comm, len );
if ( n < 0 )
{
fprintf(stdout, "write failed!\n");
return 0;
}
return n;
}
int readport( int fd, char *resp, size_t nbyte )
{
int iIn = read( fd, resp, nbyte );
if ( iIn < 0 )
{
if ( errno == EAGAIN )
{
fprintf(stdout, "SERIAL EAGAIN ERROR\n");
return 0;
}
else
{
fprintf(stdout, "SERIAL read error: %d = %s\n", errno , strerror(errno));
return 0;
}
}
if ( resp[iIn-1] == '\r' )
resp[iIn-1] = '\0';
else
resp[iIn] = '\0';
return iIn;
}
int getbaud( int fd )
{
struct termios termAttr;
int inputSpeed = -1;
speed_t baudRate;
tcgetattr( fd, &termAttr );
// Get the input speed
baudRate = cfgetispeed( &termAttr );
switch ( baudRate )
{
case B0: inputSpeed = 0; break;
case B50: inputSpeed = 50; break;
case B110: inputSpeed = 110; break;
case B134: inputSpeed = 134; break;
case B150: inputSpeed = 150; break;
case B200: inputSpeed = 200; break;
case B300: inputSpeed = 300; break;
case B600: inputSpeed = 600; break;
case B1200: inputSpeed = 1200; break;
case B1800: inputSpeed = 1800; break;
case B2400: inputSpeed = 2400; break;
case B4800: inputSpeed = 4800; break;
case B9600: inputSpeed = 9600; break;
case B19200: inputSpeed = 19200; break;
case B38400: inputSpeed = 38400; break;
case B115200: inputSpeed = 115200; break;
case B2000000: inputSpeed = 2000000; break; //added
}
return inputSpeed;
}
/* ser.c
(C) 2004-5 Captain http://www.captain.at
Sends 3 characters (ABC) via the serial port (/dev/ttyS0) and reads
them back if they are returned from the PIC.
Used for testing the PIC-MMC test-board
http://www.captain.at/electronic-index.php
*/
int initport( int fd, speed_t baudRate )
{
struct termios options;
struct sigaction saio; // Definition of Signal action
// Install the signal handler before making the device asynchronous
saio.sa_handler = DAQ_signal_handler_IO;
saio.sa_flags = 0;
saio.sa_restorer = NULL;
sigaction( SIGIO, &saio, NULL );
// Allow the process to receive SIGIO
fcntl( fd, F_SETOWN, getpid() );
// Make the file descriptor asynchronous (the manual page says only
// O_APPEND and O_NONBLOCK, will work with F_SETFL...)
fcntl( fd, F_SETFL, FASYNC );
//~ fcntl( fd, F_SETFL, FNDELAY); //doesn't work; //fcntl(file, F_SETFL, 0);
// Get the current options for the port...
tcgetattr( fd, &options );
/*
// Set port settings for canonical input processing
options.c_cflag = BAUDRATE | CRTSCTS | CLOCAL | CREAD;
options.c_iflag = IGNPAR | ICRNL;
//options.c_iflag = IGNPAR;
options.c_oflag = 0;
options.c_lflag = ICANON;
//options.c_lflag = 0;
options.c_cc[VMIN] = 0;
options.c_cc[VTIME] = 0;
*/
/* ADDED - else 'read' will not return, unless it sees LF '\n' !!!!
* From: Unix Programming Frequently Asked Questions - 3. Terminal I/O -
* http://www.steve.org.uk/Reference/Unix/faq_4.html
*/
/* Disable canonical mode, and set buffer size to 1 byte */
options.c_lflag &= (~ICANON);
options.c_cc[VTIME] = 0;
options.c_cc[VMIN] = 1;
// Set the baud rates to...
cfsetispeed( &options, baudRate );
cfsetospeed( &options, baudRate );
// Enable the receiver and set local mode...
options.c_cflag |= ( CLOCAL | CREAD );
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE;
options.c_cflag |= CS8;
// Flush the input & output...
tcflush( fd, TCIOFLUSH );
// Set the new options for the port...
tcsetattr( fd, TCSANOW, &options );
return 1;
}
/*
ripped from
http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/stty.c
*/
#define STREQ(a, b) (strcmp((a), (b)) == 0)
struct speed_map
{
const char *string; /* ASCII representation. */
speed_t speed; /* Internal form. */
unsigned long int value; /* Numeric value. */
};
static struct speed_map const speeds[] =
{
{"0", B0, 0},
{"50", B50, 50},
{"75", B75, 75},
{"110", B110, 110},
{"134", B134, 134},
{"134.5", B134, 134},
{"150", B150, 150},
{"200", B200, 200},
{"300", B300, 300},
{"600", B600, 600},
{"1200", B1200, 1200},
{"1800", B1800, 1800},
{"2400", B2400, 2400},
{"4800", B4800, 4800},
{"9600", B9600, 9600},
{"19200", B19200, 19200},
{"38400", B38400, 38400},
{"exta", B19200, 19200},
{"extb", B38400, 38400},
#ifdef B57600
{"57600", B57600, 57600},
#endif
#ifdef B115200
{"115200", B115200, 115200},
#endif
#ifdef B230400
{"230400", B230400, 230400},
#endif
#ifdef B460800
{"460800", B460800, 460800},
#endif
#ifdef B500000
{"500000", B500000, 500000},
#endif
#ifdef B576000
{"576000", B576000, 576000},
#endif
#ifdef B921600
{"921600", B921600, 921600},
#endif
#ifdef B1000000
{"1000000", B1000000, 1000000},
#endif
#ifdef B1152000
{"1152000", B1152000, 1152000},
#endif
#ifdef B1500000
{"1500000", B1500000, 1500000},
#endif
#ifdef B2000000
{"2000000", B2000000, 2000000},
#endif
#ifdef B2500000
{"2500000", B2500000, 2500000},
#endif
#ifdef B3000000
{"3000000", B3000000, 3000000},
#endif
#ifdef B3500000
{"3500000", B3500000, 3500000},
#endif
#ifdef B4000000
{"4000000", B4000000, 4000000},
#endif
{NULL, 0, 0}
};
static speed_t
string_to_baud (const char *arg)
{
int i;
for (i = 0; speeds[i].string != NULL; ++i)
if (STREQ (arg, speeds[i].string))
return speeds[i].speed;
return (speed_t) -1;
}
/* http://www.gnu.org/software/libtool/manual/libc/Elapsed-Time.html
Subtract the `struct timeval' values X and Y,
storing the result in RESULT.
Return 1 if the difference is negative, otherwise 0. */
int timeval_subtract (struct timeval *result, struct timeval *x, struct timeval *y)
{
/* Perform the carry for the later subtraction by updating y. */
if (x->tv_usec < y->tv_usec) {
int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1;
y->tv_usec -= 1000000 * nsec;
y->tv_sec += nsec;
}
if (x->tv_usec - y->tv_usec > 1000000) {
int nsec = (x->tv_usec - y->tv_usec) / 1000000;
y->tv_usec += 1000000 * nsec;
y->tv_sec -= nsec;
}
/* Compute the time remaining to wait.
tv_usec is certainly positive. */
result->tv_sec = x->tv_sec - y->tv_sec;
result->tv_usec = x->tv_usec - y->tv_usec;
/* Return 1 if result is negative. */
return x->tv_sec < y->tv_sec;
}
Well, I managed to put writeread.c in a threaded version using pthread (code is below - I don't think serial.h changed much; it's not used that much in the threaded version anyways). I have also lowered the speed to 115200, and now I can confirm these measurements with the device, in the sample command line session below:
$ ./writeread /dev/ttyUSB0 115200 writeread.c 3>myout.txt
stdalt opened; Alternative file descriptor: 3
Opening port /dev/ttyUSB0;
Got speed 115200 (4098/0x1002);
Got file/string 'writeread.c'; opened as file (6131).
write_thread_function spawned
write: 6131
read: 18
read: 64
read: 110
read: 156
read: 202
...
read: 6066
read: 6089
read: 6123
read: 6131
+++DONE+++
Wrote: 6131 bytes; Read: 6131 bytes; Total: 12262 bytes.
Start: 1284462824 s 141104 us; End: 1284462824 s 682598 us; Delta: 0 s 541494 us.
115200 baud for 8N1 is 11520 Bps (bytes/sec).
Measured: write 11322.38 Bps (98.28%), read 11322.38 Bps (98.28%), total 22644.76 Bps.
$ diff writeread.c myout.txt
$
Well, measurements now report up to 99% of the expected baud rate, so I guess that means that the profiling aspect of this program should work. Notice:
For this device, the write is executed in a single chunk (as the PC should be able to handle the sequencing to packets, if necessary),
while the read goes on in smaller chunks (probably indicating that the device doesn't wait for the entire chunk to arrive - instead it starts sending back smaller chunks as soon as it has received enough)
Well, I guess this is what I needed originally; I also guess it is probably not possible to arrange cat and echo via process substitution to execute in this, let's call it "threaded", manner :) (Now, I do have a problem with doing the same at 2000000 baud, but that indicates a problem with the programming of the device).
Cheers!
writeread.c - threaded version
/*
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 -lpthread -Wall -g writeread.c
*/
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <sys/time.h>
#include <pthread.h>
#include "serial.h"
int serport_fd;
//POSIX Threads Programming - https://computing.llnl.gov/tutorials/pthreads/#PassingArguments
struct write_thread_data{
int fd;
char* comm; //string to send
int bytesToSend;
int writtenBytes;
};
void usage(char **argv)
{
fprintf(stdout, "Usage:\n");
fprintf(stdout, "%s port baudrate file/string\n", argv[0]);
fprintf(stdout, "Examples:\n");
fprintf(stdout, "%s /dev/ttyUSB0 115200 /path/to/somefile.txt\n", argv[0]);
fprintf(stdout, "%s /dev/ttyUSB0 115200 \"some text test\"\n", argv[0]);
}
// POSIX threads explained - http://www.ibm.com/developerworks/library/l-posix1.html
// instead of writeport
void *write_thread_function(void *arg) {
int lastBytesWritten;
struct write_thread_data *my_data;
my_data = (struct write_thread_data *) arg;
fprintf(stdout, "write_thread_function spawned\n");
my_data->writtenBytes = 0;
while(my_data->writtenBytes < my_data->bytesToSend)
{
lastBytesWritten = write( my_data->fd, my_data->comm + my_data->writtenBytes, my_data->bytesToSend - my_data->writtenBytes );
my_data->writtenBytes += lastBytesWritten;
if ( lastBytesWritten < 0 )
{
fprintf(stdout, "write failed!\n");
return 0;
}
fprintf(stderr, " write: %d - %d\n", lastBytesWritten, my_data->writtenBytes);
}
return NULL; //pthread_exit(NULL)
}
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 sentBytes;
int readChars;
int recdBytes, totlBytes;
char* sResp;
char* sRespTotal;
struct timeval timeStart, timeEnd, timeDelta;
float deltasec, expectBps, measReadBps, measWriteBps;
struct write_thread_data wrdata;
pthread_t myWriteThread;
/* 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\n", fileno(stdalt));
// Get the PORT name
serport = argv[1];
fprintf(stdout, "Opening port %s;\n", serport);
// Get the baudrate
serspeed = argv[2];
serspeed_t = string_to_baud(serspeed);
fprintf(stdout, "Got speed %s (%d/0x%x);\n", 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) {
wrdata.bytesToSend = strlen(serfstr);
wrdata.comm = serfstr; //pointer already defined
fprintf(stdout, "interpreting as string (%d).\n", wrdata.bytesToSend);
} else {
struct stat st;
stat(serfstr, &st);
wrdata.bytesToSend = st.st_size;
wrdata.comm = (char *)calloc(wrdata.bytesToSend, sizeof(char));
read(serf_fd, wrdata.comm, wrdata.bytesToSend);
fprintf(stdout, "opened as file (%d).\n", wrdata.bytesToSend);
}
sResp = (char *)calloc(wrdata.bytesToSend, sizeof(char));
sRespTotal = (char *)calloc(wrdata.bytesToSend, sizeof(char));
// 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 );
wrdata.fd = serport_fd;
sentBytes = 0; recdBytes = 0;
gettimeofday( &timeStart, NULL );
// start the thread for writing..
if ( pthread_create( &myWriteThread, NULL, write_thread_function, (void *) &wrdata) ) {
printf("error creating thread.");
abort();
}
// run read loop
while ( recdBytes < wrdata.bytesToSend )
{
while ( wait_flag == TRUE );
if ( (readChars = read( serport_fd, sResp, wrdata.bytesToSend)) >= 0 )
{
//~ fprintf(stdout, "InVAL: (%d) %s\n", readChars, sResp);
// binary safe - add sResp chunk to sRespTotal
memmove(sRespTotal+recdBytes, sResp+0, readChars*sizeof(char));
/* // text safe, but not binary:
sResp[readChars] = '\0';
fprintf(stdalt, "%s", sResp);
*/
recdBytes += readChars;
} else {
if ( errno == EAGAIN )
{
fprintf(stdout, "SERIAL EAGAIN ERROR\n");
return 0;
}
else
{
fprintf(stdout, "SERIAL read error: %d = %s\n", errno , strerror(errno));
return 0;
}
}
fprintf(stderr, " read: %d\n", recdBytes);
wait_flag = TRUE; // was ==
//~ usleep(50000);
}
if ( pthread_join ( myWriteThread, NULL ) ) {
printf("error joining thread.");
abort();
}
gettimeofday( &timeEnd, NULL );
// binary safe - dump sRespTotal to stdalt
fwrite(sRespTotal, sizeof(char), recdBytes, stdalt);
// Close the open port
close( serport_fd );
if (!(serf_fd < 0)) {
close( serf_fd );
free(wrdata.comm);
}
free(sResp);
free(sRespTotal);
fprintf(stdout, "\n+++DONE+++\n");
sentBytes = wrdata.writtenBytes;
totlBytes = sentBytes + recdBytes;
timeval_subtract(&timeDelta, &timeEnd, &timeStart);
deltasec = timeDelta.tv_sec+timeDelta.tv_usec*1e-6;
expectBps = atoi(serspeed)/10.0f;
measWriteBps = sentBytes/deltasec;
measReadBps = recdBytes/deltasec;
fprintf(stdout, "Wrote: %d bytes; Read: %d bytes; Total: %d bytes. \n", sentBytes, recdBytes, totlBytes);
fprintf(stdout, "Start: %ld s %ld us; End: %ld s %ld us; Delta: %ld s %ld us. \n", 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).\n", serspeed, (int)expectBps);
fprintf(stdout, "Measured: write %.02f Bps (%.02f%%), read %.02f Bps (%.02f%%), total %.02f Bps.\n", measWriteBps, (measWriteBps/expectBps)*100, measReadBps, (measReadBps/expectBps)*100, totlBytes/deltasec);
return 0;
}

Resources