Related
Good day everyone !
I am unable to get past this point in the code.
I suspect it is an OS issues in terms of resource limits?
I have seen source that say RTPRIO 0 is lowest and 99 is highest.
... and others that say the opposite.
I cannot seem to find a good reference on where the kernel stands as to obtaining near real time threads in a process.
I had hoped to have a 4 core processor where one core is dedicated to the serial thread in the below code.
In addition when this serial thread makes a system call ... the dedicated core would task switch to this system call.
Any advise and help will be appreciated.
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <sys/time.h>
#include <sys/resource.h>
//-----------------------------------
// signal Handler stuff.
//-----------------------------------
static
struct sigaction mySigActTerm;
volatile
int myTerminate = 0;
void terminateHandler(int signum, siginfo_t *info, void *ptr)
{
// set a flag here and get out.
myTerminate = 1;
}
void getNowTime(char* str)
{
time_t rawtime;
time(&rawtime);
ctime_r(&rawtime, str);
// clobber the unwanted newline.
str[24] = '\0';
}
void myResLimit()
{
struct
rlimit procLimit;
char strNowTime[26];
getrlimit(RLIMIT_RTTIME, &procLimit);
getNowTime(strNowTime);
fprintf(stderr, "%s - RLIMIT_RTTIME: soft=%lld, hard=%lld\n", strNowTime, (long long) procLimit.rlim_cur, (long long)procLimit.rlim_max);
getrlimit(RLIMIT_RTPRIO, &procLimit);
getNowTime(strNowTime);
fprintf(stderr, "%s - RLIMIT_RTPRIO: soft=%lld, hard=%lld\n", strNowTime, (long long) procLimit.rlim_cur, (long long) procLimit.rlim_max);
getrlimit(RLIMIT_CPU, &procLimit);
getNowTime(strNowTime);
fprintf(stderr, "%s - RLIMIT_CPU: soft=%lld, hard=%lld\n", strNowTime, (long long) procLimit.rlim_cur, (long long) procLimit.rlim_max);
}
void* serialThread(void* arg)
{
while (1) {
}
}
//-----------------------------------
// the one and only MAIN.
//-----------------------------------
int main()
{
//-----------------------------------------------
// locals.
int rtn;
char strNowTime[26];
pthread_t serialThdID;
pthread_attr_t serialAttr;
struct
sched_param serialParam;
//-----------------------------------------------
// Log OS resource limits.
myResLimit();
//-----------------------------------------------
// initialize the signals struct.
// ... and setup signals.
memset(&mySigActTerm, 0, sizeof(mySigActTerm));
mySigActTerm.sa_sigaction = terminateHandler;
mySigActTerm.sa_flags = SA_SIGINFO;
sigaction(SIGTERM, &mySigActTerm, NULL);
//-----------------------------------------------
// set initial default pthread attr values.
if ((rtn = pthread_attr_init(&serialAttr)) != 0) {
getNowTime(strNowTime);
fprintf(stderr, "%s - main() - pthread_attr_init()\n%s\n", strNowTime, strerror(rtn));
exit(EXIT_FAILURE);
}
//-----------------------------------------------
// set for best near real time policy.
if ((rtn = pthread_attr_setschedpolicy(&serialAttr, SCHED_FIFO)) !=0) {
getNowTime(strNowTime);
fprintf(stderr, "%s - main() - pthread_attr_setschedpolicy()\n%s\n", strNowTime, strerror(rtn));
exit(EXIT_FAILURE);
}
//-----------------------------------------------
// set to explicit inherit or attr obj will be ignored.
if ((rtn = pthread_attr_setinheritsched(&serialAttr, PTHREAD_EXPLICIT_SCHED)) !=0) {
getNowTime(strNowTime);
fprintf(stderr, "%s - main() - pthread_attr_setinheritsched()\n%s\n", strNowTime, strerror(rtn));
exit(EXIT_FAILURE);
}
//-----------------------------------------------
// set to un-limited thread priority.
serialParam.sched_priority = 0;
if ((rtn = pthread_attr_setschedparam(&serialAttr, &serialParam)) !=0) {
getNowTime(strNowTime);
fprintf(stderr, "%s - main() - pthread_attr_setschedparam()\n%s\n", strNowTime, strerror(rtn));
exit(EXIT_FAILURE);
}
//-----------------------------------------------
// start the new thread.
if ((rtn = pthread_create(&serialThdID, &serialAttr, serialThread, NULL)) == 0) {
getNowTime(strNowTime);
fprintf(stderr, "%s - starting serial thread.\n", strNowTime);
}
else {
getNowTime(strNowTime);
fprintf(stderr, "%s - main() - pthread_create() returned %d\n%s\n", strNowTime, rtn, strerror(rtn));
exit(EXIT_FAILURE);
}
//-----------------------------------------------
// no need to keep this junk if pthread_create() succeeded.
if ((rtn = pthread_attr_destroy(&serialAttr)) != 0) {
getNowTime(strNowTime);
fprintf(stderr, "%s - main() - pthread_attr_destroy()\n%s\n", strNowTime, strerror(rtn));
}
while (myTerminate == 0) {
}
}
Output is:
dateTime - RLIMIT_RTTIME: soft=-1, hard=-1
dateTime - RLIMIT_RTPRIO: soft=-1, hard=-1
dateTime - RLIMIT_CPU: soft=-1, hard=-1
dateTime - main() - pthread_attr_setschedParam()
Invalid Argument
You're attempting to use scheduling priority 0 with the SCHED_FIFO scheduling policy which is invalid.
You're setting the scheduling policy to SCHED_FIFO using pthread_attr_setschedpolicy. The man page for that function states:
The supported values for policy are SCHED_FIFO, SCHED_RR, and SCHED_OTHER, with the semantics described in sched_setscheduler(2).
The man page for sched_setscheduler further states the following for SCHED_FIFO:
SCHED_FIFO can be used only with static priorities higher than 0, which means that when a SCHED_FIFO processes becomes runnable, it will always
immediately preempt any currently running SCHED_OTHER, SCHED_BATCH, or SCHED_IDLE process. SCHED_FIFO is a simple scheduling algorithm without
time slicing.
So using a value larger than 0 for serialParam.sched_priority will work.
I have written some code that tries to call a function (called worker) every x seconds (in this example, I chose 1s as the interval time). The code is a minimal working example that in reality is way more complex than this.
The code works when it is this simple, but I stumble across errors when running the more complex version for a longer period of time. Thus, I want to increase the robustness of this code an would like to get some ideas on how to do that.
Basically, the worker gets some data, processes it an writes it to a file. I open the file during every call to the worker. In the tests, after some time I get an error that the file cannot be opened anymore. In this regard I also noticed that this happens (maybe just by chance) everytime the worker execution time exceeds the interval time. Reason for this is the getter function which pulls data from remote and this can take some time, depending on the network traffic.
I've been thinking of trying a multithreaded approach, but I am not sure if this is worth the hassle. I would be grateful for any pointers on how to do this in a more robust way.
#include <unistd.h>
#include <stdbool.h>
#include <stdio.h>
#include <signal.h>
#include <stdint.h>
#include <time.h>
#define ALARM_INTERVAL_SEC 1
#define ALARM_INTERVAL_USEC 0
static bool running = true;
static struct itimerval alarm_interval;
static struct timeval previous_time;
static uint64_t loop_count = 0;
static FILE* testfile;
static void
signal_handler(int signum)
{
if (signum == SIGINT || signum == SIGTERM)
{
running = false;
}
}
static void
worker(int signum)
{
// Reset the alarm interval
if(setitimer(ITIMER_REAL, &alarm_interval, NULL) < 0)
{
perror("Error: setitimer");
raise(SIGTERM);
return;
}
struct timeval current_time;
gettimeofday(¤t_time, NULL);
printf("Loop count: %lu\n", loop_count);
printf("Loop time: %f us\n\n", (current_time.tv_sec - previous_time.tv_sec) * 1e6 +
(current_time.tv_usec - previous_time.tv_usec));
previous_time = current_time;
// convert time to human-readable format
char tmbuf[64];
char buf[64];
time_t nowtime = current_time.tv_sec;
struct tm *nowtm = localtime(&nowtime);
strftime(tmbuf, sizeof(tmbuf), "%Y-%m-%d %H:%M:%S", nowtm);
snprintf(buf, sizeof(buf), "%s.%06ld", tmbuf, current_time.tv_usec);
sleep(0.5);
// DO STH
testfile = fopen("testfile.txt", "ab+");
if(testfile == NULL)
{
printf("Error: open testfile");
raise(SIGTERM);
return;
}
fprintf(testfile, "[%s] Loop count: %lu\n", buf, loop_count);
fclose(testfile);
loop_count++;
}
int
main(int argc, char* argv[])
{
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGALRM, worker);
// Set the alarm interval
alarm_interval.it_interval.tv_sec = 0;
alarm_interval.it_interval.tv_usec = 0;
alarm_interval.it_value.tv_sec = ALARM_INTERVAL_SEC;
alarm_interval.it_value.tv_usec = ALARM_INTERVAL_USEC;
if(setitimer(ITIMER_REAL, &alarm_interval, NULL) < 0)
{
perror("Error: setitimer");
return -1;
}
gettimeofday(&previous_time, NULL);
while(running)
{
sleep(1);
}
alarm_interval.it_value.tv_sec = 0;
alarm_interval.it_value.tv_usec = 0;
if(setitimer(ITIMER_REAL, &alarm_interval, NULL) < 0)
{
perror("Error: resetting itimer failed");
return -1;
}
return 0;
}
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);
}
After three weeks I can't get past this problem.
I have the code below running on Ubuntu 18.04.3 which sends a string successfully to another device.
When the remote device receives the string ... it sends another back ... but the code below (even with 1 sec set) times out on select().
When I comment out the select() and just do the read() ... fails to see any data as well?
It was working three weeks ago ... but recent code changes broke it ... and I cannot see why.
How could a write() on a file descriptor go out the serial port ok ... but a select() and read() using the same file descriptor get nothing back.
I have a third device (a PC with putty) so I can see everything on the wire.
All three are on an RS-485 bus.
Any other issues with the code would be greatly appreciated!
Thanks!
// main.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <term.h>
#include <signal.h>
#include <sys/time.h>
#include "SER.h"
static
struct sigaction mySigActTerm;
volatile
int terminate = 0;
void terminateHandler(int signum, siginfo_t *info, void *ptr)
{
//------------------------------------------------------------
// set a flag here and get out.
terminate = 1;
}
int main()
{
int rtn;
pthread_t serialThdID;
SER* mySER;
//------------------------------------------------------------
// setup terminate signal
memset(&mySigActTerm, 0, sizeof(mySigActTerm));
mySigActTerm.sa_sigaction = terminateHandler;
mySigActTerm.sa_flags = SA_SIGINFO;
sigaction(SIGTERM, &mySigActTerm, NULL);
//------------------------------------------------------------
// initialize the serial port.
mySER = SERinit("/dev/ttyUSB0", 2);
if (mySER == NULL)
{
fprintf(stderr, "main() - SERinit() returned NULL");
exit(EXIT_FAILURE);
}
//------------------------------------------------------------
// start the serial thread.
rtn = pthread_create(&serialThdID, NULL, serialThread, mySER);
if(rtn == 0)
fprintf(stderr, "starting serial thread.\n");
else
{
fprintf(stderr, "main() - pthread_create() returned %d\n%s\n", rtn, strerror(errno));
free(mySER);
exit(EXIT_FAILURE);
}
//------------------------------------------------------------
// wait till serialThread() indicates it is running.
while (mySER->ThreadStatus != threadRuning)
{
fprintf(stderr, "waiting for thread running status.\n");
sleep(1);
}
//------------------------------------------------------------
// main loop here.
while (terminate == 0)
{
// do stuff here.
}
//------------------------------------------------------------
// tell the serial thread to stop.
mySER->ThreadCtrl = threadCtrlKill;
//------------------------------------------------------------
// verify serial thread is dead!
while (mySER->ThreadStatus != threadStopped)
{
}
//------------------------------------------------------------
// clean up.
SERclose(mySER);
free(mySER);
}
serialThread.c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/time.h>
#include <term.h>
#include <inttypes.h>
#include "SER.h"
void* serialThread(void* arg)
{
char* rtn;
SER* mySER = arg;
mySER->tid = pthread_self();
mySER->ThreadStatus = threadRuning;
// thread Loop!
while(mySER->ThreadCtrl != threadCtrlKill)
{
rtn = SERwrapperFunc(mySER);
// code to print the response here
printf("%.*s\n", 8, rtn);
sleep(30);
}
mySER->ThreadStatus = threadStopped;
pthread_exit(NULL);
}
SERmaster.c
#define responseSize 4584
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <termios.h>
//#include <linux/serial.h>
//#include <aio.h>
#include <sys/time.h>
#include "SER.h"
// array used to get termios BAUD.
const
int BAUDarray[9] = { 0, // not used.
B4800, // 208
B9600, // 104
B19200, // 52
B38400, // 26
B57600, // 17.363636
B115200, // 8.727272
B230400, // 4.363636
B460800 // 2.181818
};
// delay (in uS) per character transmitted.
// 1 start, Even parity, 7bits, 1 stop.
// bit time (times 10 bits)
// Plus one bit time between characters.
const
int BAUDdelay[9] = { 0, // not used.
2288,
1144,
572,
286,
191,
96,
48,
24
};
static
char response[4584];
static
unsigned
int respIndex;
static
struct termios newtio, oldtio;
extern
volatile
int terminate;
static
int sendRecieve(SER* mySER, const char* msgBuffer, int msgCnt, int sendFlag, int receiveFlag)
{
int rtn;
char myChar;
fd_set myfds;
struct
timeval tm_out;
if (sendFlag == true)
{
while (1)
{
rtn = write(mySER->sfd, msgBuffer, msgCnt);
if (rtn == -1)
{
if (errno == EINTR)
{
fprintf(stderr, "sendRecieve() - write() EINTR !\n");
if (terminate == 1)
break; // deal with SIGTERM !
continue; // if not SIGTERM then retry.
}
else
{
fprintf(stderr, "sendRecieve() - write()\n%s\n", strerror(errno));
return EXIT_FAILURE;
}
}
else
{
if (rtn == msgCnt)
break;
else
{
fprintf(stderr, "sendRecieve() - write() returned less than msgCnt !\n");
return EXIT_FAILURE;
}
}
}
}
if (receiveFlag == true)
{
respIndex = 0;
while (1)
{
tm_out.tv_sec = 1;
tm_out.tv_usec = mySER->BAUDmult * msgCnt;
FD_ZERO(&myfds);
FD_SET(mySER->sfd, &myfds);
rtn = select(mySER->sfd + 1, &myfds, NULL, NULL, &tm_out);
if (rtn == 0)
{
fprintf(stderr, "sendRecieve() - select() timeout!\n");
return EXIT_FAILURE;
}
if (rtn == -1)
{
if (errno == EINTR)
{
fprintf(stderr, "sendRecieve() - select() EINTR !\n");
if (terminate == 1)
break;
continue;
}
else
{
fprintf(stderr, "sendRecieve() - select()\n%s\n", strerror(errno));
return EXIT_FAILURE;
}
}
while (1)
{
rtn = read(mySER->sfd, &myChar, 1);
if (rtn == -1)
{
if (errno == EINTR)
{
fprintf(stderr, "sendRecieve() - read() EINTR !\n");
if (terminate == 1)
break;
continue;
}
else
{
fprintf(stderr, "sendRecieve() - read()\n%s\n", strerror(errno));
return EXIT_FAILURE;
}
}
else
break;
response[respIndex] = myChar;
if (respIndex < responseSize - 1)
respIndex++;
else
break;
if (myChar == '\n')
return EXIT_SUCCESS;
}
}
fprintf(stderr, "sendRecieve() - select/read while loop Dumped (response frame too big)!!\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
char* SERwrapperFunc(SER* mySER)
{
char myCharArray[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' };
int myCharArrayCountToSend = sizeof(myCharArray);
sendRecieve(mySER, myCharArray, myCharArrayCountToSend, true, true);
return response;
}
void serPrint()
{
printf("NCCS = %d OLD: NEW:\n", NCCS);
printf("c_iflag - %08x %08x\n", oldtio.c_iflag, newtio.c_iflag);
printf("c_oflag - %08x %08x\n", oldtio.c_oflag, newtio.c_oflag);
printf("c_cflag - %08x %08x\n", oldtio.c_cflag, newtio.c_cflag);
printf("c_lflag - %08x %08x\n", oldtio.c_lflag, newtio.c_lflag);
printf("c_line - %08x %08x\n", oldtio.c_line, newtio.c_line);
printf("c_ispeed - %08x %08x\n", oldtio.c_ispeed, newtio.c_ispeed);
printf("c_ospeed - %08x %08x\n", oldtio.c_ospeed, newtio.c_ospeed);
printf("\n");
printf("VINTR - %02x %02x\n", oldtio.c_cc[VINTR], newtio.c_cc[VINTR]);
printf("VQUIT - %02x %02x\n", oldtio.c_cc[VQUIT], newtio.c_cc[VQUIT]);
printf("VERASE - %02x %02x\n", oldtio.c_cc[VERASE], newtio.c_cc[VERASE]);
printf("VKILL - %02x %02x\n", oldtio.c_cc[VKILL], newtio.c_cc[VKILL]);
printf("VEOF - %02x %02x\n", oldtio.c_cc[VEOF], newtio.c_cc[VEOF]);
printf("VTIME - %02x %02x\n", oldtio.c_cc[VTIME], newtio.c_cc[VTIME]);
printf("VMIN - %02x %02x\n", oldtio.c_cc[VMIN], newtio.c_cc[VMIN]);
printf("VSWTC - %02x %02x\n", oldtio.c_cc[VSWTC], newtio.c_cc[VSWTC]);
printf("VSTART - %02x %02x\n", oldtio.c_cc[VSTART], newtio.c_cc[VSTART]);
printf("VSTOP - %02x %02x\n", oldtio.c_cc[VSTOP], newtio.c_cc[VSTOP]);
printf("VSUSP - %02x %02x\n", oldtio.c_cc[VSUSP], newtio.c_cc[VSUSP]);
printf("VEOL - %02x %02x\n", oldtio.c_cc[VEOL], newtio.c_cc[VEOL]);
printf("VREPRINT - %02x %02x\n", oldtio.c_cc[VREPRINT], newtio.c_cc[VREPRINT]);
printf("VDISCARD - %02x %02x\n", oldtio.c_cc[VDISCARD], newtio.c_cc[VDISCARD]);
printf("VWERASE - %02x %02x\n", oldtio.c_cc[VWERASE], newtio.c_cc[VWERASE]);
printf("VLNEXT - %02x %02x\n", oldtio.c_cc[VLNEXT], newtio.c_cc[VLNEXT]);
printf("VEOL2 - %02x %02x\n", oldtio.c_cc[VEOL2], newtio.c_cc[VEOL2]);
printf("\n");
printf("\n");
}
SER* SERinit(const char* strPort, int myBAUD)
{
SER* mySER;
//------------------------------------------------------------
// create the global SER struct instance.
if ((mySER = malloc(sizeof(SER))) == NULL)
{
fprintf(stderr, "SERinit() - mySER malloc()\n%s\n", strerror(errno));
return NULL;
}
memset(mySER, 0, sizeof(SER));
//------------------------------------------------------------
// setup the BAUD.
mySER->BAUDindex = myBAUD;
mySER->BAUDvalue = BAUDarray[myBAUD];
mySER->BAUDmult = BAUDdelay[myBAUD];
//------------------------------------------------------------
// open the serial port.
mySER->sfd = open(strPort, O_RDWR | O_NOCTTY);
if (mySER->sfd < 0)
{
fprintf(stderr, "SERInit() - open()\n%s\n", strerror(errno));
free(mySER);
return NULL;
}
//------------------------------------------------------------
// save old port settings for when we exit.
tcgetattr(mySER->sfd, &oldtio);
//------------------------------------------------------------
// prepare the newtio struct with current settings.
newtio = oldtio;
//------------------------------------------------------------
// set BAUD
if (cfsetspeed(&newtio, B9600) != 0)//mySER->BAUDvalue
{
fprintf(stderr, "SERInit() - cfsetspeed()\n%s\n", strerror(errno));
free(mySER);
return NULL;
}
//------------------------------------------------------------
// set for non-canonical (raw).
cfmakeraw(&newtio);
newtio.c_cflag |= (CLOCAL | CREAD);
newtio.c_cflag &= ~(CRTSCTS | CSTOB)
// read() blocks until one char or until 100 mS timeout.
newtio.c_cc[VTIME] = 1;
newtio.c_cc[VMIN] = 1;
// flush the toilet.
tcflush(mySER->sfd, TCIFLUSH);
// write new port settings.
tcsetattr(mySER->sfd, TCSANOW, &newtio);
serPrint();
return mySER;
}
void SERclose(SER* mySER)
{
// restore old port settings.
tcsetattr(mySER->sfd, TCSANOW, &oldtio);
close(mySER->sfd);
}
SER.h
#ifndef SER_H_
#define SER_H_
#define threadInit 0x00
#define threadStarting 0x01
#define threadRuning 0x02
#define threadFailed 0x03
#define threadStopped 0x0f
#define threadCtrlRestart 0xFE
#define threadCtrlKill 0xFF
#include <stdint.h>
#include <pthread.h>
typedef struct SER
{
int BAUDindex; // the BAUD rate.
int BAUDmult; // uS per character ... plus one bite time between characters.
// used as a multiplier used to calculate sleep times after write().
// (bit time x 10 bits) 71E.
int BAUDvalue; // array used to set termios BAUD and get BAUDmult.
// 4800 = 1 2080 uS
// 9600 = 2 1040
// 19,200 = 3 520
// 38,400 = 4 260
// 76,800 = 5 130
// 115,200 = 6 65
// 230,400 = 7 32.5
// 460,800 = 8 16.25
pthread_t tid; // Stores thread ID.
uint8_t ThreadStatus; // written only by thread.
uint8_t ThreadCtrl; // written only by main.
int sfd; // serial port file descriptor.
}SER;
char* SERwrapperFunc(SER* mySER);
SER* SERinit(const char* strPort, int myBAUD);
void SERclose(SER* mySER);
void* serialThread(void* arg);
#endif /* SER_H_ */
Thanks to sawdust, I found the issue, and corrected some others!
The original SERmaster.c was select()'ing and read()'ing and discarding with a final select() which of course timed out due to no additional serial data when the message ended.
I had assumed select() only fired once and timed out.
Corrected SERmaster.c
#define responseSize 4584
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <termios.h>
//#include <linux/serial.h>
//#include <aio.h>
#include <sys/time.h>
#include "SER.h"
// array used to get termios BAUD.
const
int BAUDarray[9] = { 0, // not used.
B4800, // 208
B9600, // 104
B19200, // 52
B38400, // 26
B57600, // 17.363636
B115200, // 8.727272
B230400, // 4.363636
B460800 // 2.181818
};
// delay (in uS) per character transmitted.
// 1 start, Even parity, 7bits, 1 stop.
// bit time (times 10 bits)
// Plus one bit time between characters.
const
int BAUDdelay[9] = { 0, // not used.
2288,
1144,
572,
286,
191,
96,
48,
24
};
static
char response[4584];
static
unsigned
int respIndex;
static
struct termios newtio, oldtio;
extern
volatile
int terminate;
static
int sendRecieve(SER* mySER, const char* msgBuffer, int msgCnt, int sendFlag, int receiveFlag)
{
int rtn;
char myChar;
fd_set myfds;
struct
timeval tm_out;
if (sendFlag == true)
{
while (1)
{
rtn = write(mySER->sfd, msgBuffer, msgCnt);
if (rtn == -1)
{
if (errno == EINTR)
{
fprintf(stderr, "sendRecieve() - write() EINTR !\n");
if (terminate == 1)
break; // deal with SIGTERM !
continue; // if not SIGTERM then retry.
}
else
{
fprintf(stderr, "sendRecieve() - write()\n%s\n", strerror(errno));
return EXIT_FAILURE;
}
}
else
{
if (rtn == msgCnt)
break;
else
{
fprintf(stderr, "sendRecieve() - write() returned less than msgCnt !\n");
return EXIT_FAILURE;
}
}
}
}
if (receiveFlag == true)
{
for (int i = 0; i < responseSize; i++)
response[i] = '\0';
respIndex = 0;
// set our first select() time out for (x + 2) char times where x is what we sent via write().
tm_out.tv_sec = 0;
tm_out.tv_usec = mySER->BAUDmult * (msgCnt + 2);
while (1)
{
FD_ZERO(&myfds);
FD_SET(mySER->sfd, &myfds);
rtn = select(mySER->sfd + 1, &myfds, NULL, NULL, &tm_out);
if (rtn == 0)
{
fprintf(stderr, "sendRecieve() - select() timeout!\n");
return EXIT_FAILURE;
}
if (rtn == -1)
{
if (errno == EINTR)
{
if (terminate == 1)
{
fprintf(stderr, "sendRecieve() - select() EINTR, terminating!\n");
return EXIT_FAILURE;
}
fprintf(stderr, "sendRecieve() - select() EINTR, restarting, tm_out.tv_usec = %d, remaining = %ld\n", mySER->BAUDmult * msgCnt, tm_out.tv_usec);
continue;
}
else
{
fprintf(stderr, "sendRecieve() - select()\n%s\n", strerror(errno));
return EXIT_FAILURE;
}
}
// select() indicates ready for reading !!
while (1)
{
rtn = read(mySER->sfd, &myChar, 1);
if (rtn == -1)
{
if (errno == EINTR)
{
fprintf(stderr, "sendRecieve() - read() EINTR !\n");
if (terminate == 1)
return EXIT_FAILURE;
continue;
}
else
{
fprintf(stderr, "sendRecieve() - read()\n%s\n", strerror(errno));
return EXIT_FAILURE;
}
}
if (rtn == 0)
{
fprintf(stderr, "sendRecieve() - read() returned 0 yet select() reported ready for reading ??? should never see this !\n");
return EXIT_FAILURE;
}
// break from read while() loop to process the char.
break;
}// end read() while loop
// save the new char.
response[respIndex] = myChar;
// point to the nest storage location.
respIndex++;
if (myChar == '\n')
return EXIT_SUCCESS;
// are we pointing beyond max buffer size?
if (respIndex == responseSize)
{
fprintf(stderr, "sendRecieve() - exceeded response buffer size ... before message termination char!!\n");
return EXIT_FAILURE;
}
// set our next select() time out for 2 char times based on baud rate.
tm_out.tv_sec = 0;
tm_out.tv_usec = mySER->BAUDmult * 2;
}//end select() while loop
fprintf(stderr, "sendRecieve() - select/read outer while loop Dumped, should not see this ever !!\n");
return EXIT_FAILURE;
}// end if (receiveFlag == true)
return EXIT_SUCCESS;
}
char* SERwrapperFunc(SER* mySER)
{
char myCharArray[] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' };
int myCharArrayCountToSend = sizeof(myCharArray);
sendRecieve(mySER, myCharArray, myCharArrayCountToSend, true, true);
return response;
}
void serPrint()
{
printf("NCCS = %d OLD: NEW:\n", NCCS);
printf("c_iflag - %08x %08x\n", oldtio.c_iflag, newtio.c_iflag);
printf("c_oflag - %08x %08x\n", oldtio.c_oflag, newtio.c_oflag);
printf("c_cflag - %08x %08x\n", oldtio.c_cflag, newtio.c_cflag);
printf("c_lflag - %08x %08x\n", oldtio.c_lflag, newtio.c_lflag);
printf("c_line - %08x %08x\n", oldtio.c_line, newtio.c_line);
printf("c_ispeed - %08x %08x\n", oldtio.c_ispeed, newtio.c_ispeed);
printf("c_ospeed - %08x %08x\n", oldtio.c_ospeed, newtio.c_ospeed);
printf("\n");
printf("VINTR - %02x %02x\n", oldtio.c_cc[VINTR], newtio.c_cc[VINTR]);
printf("VQUIT - %02x %02x\n", oldtio.c_cc[VQUIT], newtio.c_cc[VQUIT]);
printf("VERASE - %02x %02x\n", oldtio.c_cc[VERASE], newtio.c_cc[VERASE]);
printf("VKILL - %02x %02x\n", oldtio.c_cc[VKILL], newtio.c_cc[VKILL]);
printf("VEOF - %02x %02x\n", oldtio.c_cc[VEOF], newtio.c_cc[VEOF]);
printf("VTIME - %02x %02x\n", oldtio.c_cc[VTIME], newtio.c_cc[VTIME]);
printf("VMIN - %02x %02x\n", oldtio.c_cc[VMIN], newtio.c_cc[VMIN]);
printf("VSWTC - %02x %02x\n", oldtio.c_cc[VSWTC], newtio.c_cc[VSWTC]);
printf("VSTART - %02x %02x\n", oldtio.c_cc[VSTART], newtio.c_cc[VSTART]);
printf("VSTOP - %02x %02x\n", oldtio.c_cc[VSTOP], newtio.c_cc[VSTOP]);
printf("VSUSP - %02x %02x\n", oldtio.c_cc[VSUSP], newtio.c_cc[VSUSP]);
printf("VEOL - %02x %02x\n", oldtio.c_cc[VEOL], newtio.c_cc[VEOL]);
printf("VREPRINT - %02x %02x\n", oldtio.c_cc[VREPRINT], newtio.c_cc[VREPRINT]);
printf("VDISCARD - %02x %02x\n", oldtio.c_cc[VDISCARD], newtio.c_cc[VDISCARD]);
printf("VWERASE - %02x %02x\n", oldtio.c_cc[VWERASE], newtio.c_cc[VWERASE]);
printf("VLNEXT - %02x %02x\n", oldtio.c_cc[VLNEXT], newtio.c_cc[VLNEXT]);
printf("VEOL2 - %02x %02x\n", oldtio.c_cc[VEOL2], newtio.c_cc[VEOL2]);
printf("\n");
printf("\n");
}
SER* SERinit(const char* strPort, int myBAUD)
{
SER* mySER;
//------------------------------------------------------------
// create the global SER struct instance.
if ((mySER = malloc(sizeof(SER))) == NULL)
{
fprintf(stderr, "SERinit() - mySER malloc()\n%s\n", strerror(errno));
return NULL;
}
memset(mySER, 0, sizeof(SER));
//------------------------------------------------------------
// setup the BAUD.
mySER->BAUDindex = myBAUD;
mySER->BAUDvalue = BAUDarray[myBAUD];
mySER->BAUDmult = BAUDdelay[myBAUD];
//------------------------------------------------------------
// open the serial port.
mySER->sfd = open(strPort, O_RDWR | O_NOCTTY);
if (mySER->sfd < 0)
{
fprintf(stderr, "SERInit() - open()\n%s\n", strerror(errno));
free(mySER);
return NULL;
}
//------------------------------------------------------------
// save old port settings for when we exit.
tcgetattr(mySER->sfd, &oldtio);
//------------------------------------------------------------
// prepare the newtio struct with current settings.
newtio = oldtio;
//------------------------------------------------------------
// set BAUD
if (cfsetspeed(&newtio, B9600) != 0)//mySER->BAUDvalue
{
fprintf(stderr, "SERInit() - cfsetspeed()\n%s\n", strerror(errno));
free(mySER);
return NULL;
}
//------------------------------------------------------------
// set for non-canonical (raw).
cfmakeraw(&newtio);
newtio.c_cflag |= (CLOCAL | CREAD);
newtio.c_cflag &= ~(CRTSCTS | CSTOPB);
// read() blocks until one char or until 100 mS timeout.
newtio.c_cc[VTIME] = 1;
newtio.c_cc[VMIN] = 1;
// flush the toilet.
tcflush(mySER->sfd, TCIFLUSH);
// write new port settings.
tcsetattr(mySER->sfd, TCSANOW, &newtio);
serPrint();
return mySER;
}
void SERclose(SER* mySER)
{
// restore old port settings.
tcsetattr(mySER->sfd, TCSANOW, &oldtio);
close(mySER->sfd);
}
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;
}