BeagleBone Black: UART crashes the BBB after two successful read write calls - c

I'm having some trouble getting consistent UART with the BBB. I've set things up as shown on this page for non-canonical inputs. I seem to be able to transmit just fine, even on the third cycle. It's the read command that seems to be getting me. I'm not even really sure where to start with this so any pointers would be greatly appreciated! Here's the code I'm using:
EDIT: For specificity
When I say two read and write calls I mean my loop executes as expected twice. I send out the data and read exactly what I expected to. On the third attempt at reading, after the "wrote data..." printf executes, the program crashes. I can still CTRL-C out and run the BBB as usual, and restarting the program will let me read and write exactly twice again before the same issue arises.
With the while loop condition, I accidentally forgot to add the initialization of the read_test variable in the code you see below. I'm adding this serial communication to a much larger program controlling a robot I do research on. I believe I've captured all of the relevant code, this test is the first thing that happens after I initialize all the variables I need, however if the entire file is helpful I can add it.
void SERIAL_Init(void)
{
/*******************************************************************/
printf("\tSerial Port Initialization ...");
SERIAL_fd = open(MODEMDEVICE,O_RDWR | O_NOCTTY /*| O_NDELAY*/);
if (SERIAL_fd < 0)
{ perror(MODEMDEVICE);
exit(-1);
}
fcntl(SERIAL_fd,F_SETFL,0);
tcgetattr(SERIAL_fd,&newtio);
newtio.c_cflag |= CS8 | CLOCAL | CREAD ;
cfsetispeed(&newtio,BAUDRATE);
cfsetospeed(&newtio,BAUDRATE);
newtio.c_iflag = IGNBRK | IGNPAR;
newtio.c_oflag = 0;
newtio.c_lflag = 0;
newtio.c_cc[VTIME]=0; /* inter-character timer unused */
newtio.c_cc[VMIN]=31; /* blocking read until 18 chars received */
tcflush(SERIAL_fd,TCIFLUSH);
tcsetattr(SERIAL_fd,TCSANOW,&newtio);
/****************************************************************/
printf("Done\n");
return;
}
//Test data for reading serial
unsigned char test_data[2] = {0x00,0x00};
//Read from maestro command
unsigned char read_command[2][2] = {
{0x1a,0x2b},
{0x90,0x05},
};
while(readTest == 0)
{
printf("Enter 0 to read from maestro pin, 1 to continue with testing\n");
scanf("%d",&readTest);
write(SERIAL_fd,read_command[0],2);
printf("Wrote values...\n");
num_bytes = read(SERIAL_fd,&test_data,2);
printf("Value read: %x %x\n",test_data[0],test_data[1]);
}

The issue was solved by changing to VMIN=2 to match the amount of data I'm expecting with each read command. Reading this page was incredibly helpful. I thought having the VMIN set high wasn't the problem since I was still getting for the first two commands even though the number of bytes was below the old VMIN value, however that makes sense now given "We believe that it's undefined behavior if nbytes is less then VMIN." Thanks so much for the help!

Related

Reading variable length data from serial with timeout

I am reading data frames from a device using a software UART driver. It's written as a tty module so reading from is is pretty standard:
struct termios options;
int tty = open(path, O_RDWR | O_NOCTTY | O_SYNC);
tcgetattr(tty, &options);
options.c_cflag = B4800 | CS8 | CLOCAL | CREAD | PARENB;
options.c_iflag = IGNPAR;
options.c_oflag = 0;
options.c_lflag = 0;
tcsetattr(tty, TCSANOW, &options);
while (running)
{
int rx_count = read(tty_device, (void*)header, header_size);
// parse the header to figure out the total frame size
rx_count = read(tty_device, (void*)frame_data, frame_size);
}
It works most of the time, but sometimes I miss some bytes due to limitations in the reliability of the UART.
My problem is that when I miss bytes the code reads the initial bytes of the next frame as the final bytes of the previous frame. This causes random, unpredictable behavior.
I am wondering if there is a good way to read based on time, instead of data size. Something like this:
while (running)
{
// read bytes continuously from the tty device
// if 1 second passes with no further bytes coming, stop reading
// process whatever data was read
}
I could probably hack together something that does this eventually but I imagine there is a good chance this has already been figured out, and I am just failing to find the info on the internet.
I was able to accomplish what I need using VMIN and VTIME as found in the link from the comment of sawdust:
Linux Blocking vs. non Blocking Serial Read
This was also relevant to my problem. Somehow I couldn't figure out the right search terms to find it before:
VMIN and VTIME Terminal Settings for variable sized messages

Reading serial data in linux byte-for-byte at 56K reliably

I'm trying to create a function with the most minimal delay possible that checks to see if the serial port has data and if it does, it reads every single byte and prints each byte in hex format until no more bytes are available. If there is no data, the function must return right away.
This is my code:
int fd=open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_SYNC);
// Trying to set correct options here
struct termios o;
tcgetattr(fd,&o);
cfsetispeed(&o,57600);
cfsetospeed(&o,57600);
/* 8 bits, no parity, 1 stop bit */
o.c_cflag &= ~PARENB;o.c_cflag &= ~CSTOPB;o.c_cflag &= ~CSIZE;o.c_cflag |= CS8;
/* no hardware flow control */
o.c_cflag &= ~CRTSCTS;
/* enable receiver, ignore status lines */
o.c_cflag |= CREAD | CLOCAL;
/* disable input/output flow control, disable restart chars */
o.c_iflag &= ~(IXON | IXOFF | IXANY);
/* disable canonical input, disable echo, disable visually erase chars, disable terminal-generated signals */
o.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
/* disable output processing */
o.c_oflag &= ~OPOST;
o.c_cc[VMIN] = 0; //to prevent delay in read();
o.c_cc[VTIME] = 0;
tcsetattr(fd, TCSANOW, &o);
tcflush(fd, TCIFLUSH);
char sp[1]; //hold 1 byte
int bytes=read(fd,sp,1); //Good news: this function doesn't lock
if (bytes > 0){
//this is never reached even if a byte is
//present on the serial line. why?
printf("Read: ");
while(bytes > 0){
printf("%X ",sp[0]);
bytes=read(fd,sp,1);
}
}
fclose(fd);
Is there a way to fix this?
This whole function (minus the serial port options section) will eventually run in an endless loop as I am constantly scanning my port for data then printing it. Then later I'll add some more functionality where I'll write data to the port at predefined times regardless of what data is received.
P.S. not sure if this is of help, but the target device I'm doing I/O with is custom hardware around an 8051 microcontroller and its serial fifo buffers are only 1 byte whereas the PC's buffers I think are 14 or 16 bytes.
If you know beforehand the times when you need to write to the device, you can use select() or poll() to wait for input until the next time you wish/intend to write.
A much more simple and robust approach — because your reads and writes are not in a defined sequence, and your hardware is full duplex — is to use separate threads for reading and writing. Basically, you use blocking reads and writes (c_cc[VMIN] = 1, c_cc[VTIME] = 0 for reading, O_NONBLOCK not among file open flags). You should allow larger buffers, though; reads will return all that have been received thus far, but with those settings wake up the reader whenever there is at least one char received. For writing, I do recommend you do a tcdrain(fd); after each write that completes a command/message to the device, to ensure it is sent on the wire by the kernel. (Do remember that writes to the serial port can be short; you need a write loop.)
In all cases, the kernel on the host side will cache data sent and received. Depending on the hardware and driver, even a blocking write() may return earlier than when all of the data is actually on the wire. The hardware and the kernel driver, not the host software, is responsible for correct timing of the serial data.
Using one-byte buffer on the host side will not affect the microcontroller at all, you'll just do more syscalls than is necessary, wasting CPU resources and possibly slow down your program a bit. At 57600 baud, with 8 data bits, no parity, 1 stop bit, and the implicit start bit, the actual data rate is 46080 bits/second (±5% typically allowed), or 5760 bytes per second. The microcontroller will always have about 1 s /5760 ≃ 0.0001736 seconds, or over 173 microseconds, to process each incoming byte. (I'd design my firmware to not allow higher priority interrupts etc. to delay processing for more than 100 microseconds or so even in the worst case, to ensure no chars are dropped. If you receive the chars in an interrupt handler, I'd use a small circular buffer, and an indicator character, \r or \n, so that if such character is received, a flag is raised for the main program to notice that a new complete command has been received. The circular buffer should be long enough to hold two full commands, or more if some commands may take longer to parse/process/handle.)
If the host operating system wakes up the process 1 ms after of receiving the first character, additional four or five characters have arrived in the mean time. Because this latency can be much higher on some systems, I'd use a much larger buffer, say up to 256 chars, to avoid doing superfluous syscalls when the kernel is for some reason delayed in waking up the reader thread. Yes, it will often read just 1 char, and that is fine; but when the system is overloaded, you don't want to add to that load by doing hundreds of superfluous syscalls when one would suffice.
Remember, the termios interface, with VMIN=1, VTIME=0, will cause the blocking read to be woken up as soon as possible, whenever even a single character is received. It is just that you cannot ensure your program is constantly running, unless you waste about 100% of CPU power by spinning in place (and if you do, nobody will want to run your program). Depending on the system, there may be a delay in waking up the blocking read, during which time more data may be received, so using a larger read() buffer is definitely sensible.
Similarly, you can safely use as large writes as you want (up to a limit of about 2 GiB), although most serial drivers can return short counts, so you'll need a loop there anyway. The tcdrain(fd) on the serial port descriptor will block until all written data has been actually transmitted, so you'll probably want to use it. (If you do not, you can just write more data; the kernel driver will take care of the details, and not reorder/mess data up.)
Using two threads, one for reading, one for writing, may sound daunting/odd, but it is actually the simplest way of achieving robust communications. You can even use pthread_setcancelstate(), pthread_setcanceltype() and optionally pthread_testcancel(), to write the thread functions so that you can simply cancel the threads (using pthread_cancel()) to stop them, even if they have critical sections (like adding a received message to some queue protected by a mutex).
Your program has a variety of problems, many of which have been discussed in comments:
you do not check the return values of your functions
you poll the serial port and furthermore perform a system call for every byte read
you appear to be setting the serial port transmission speed incorrectly, via raw integers instead of the macros reserved for the purpose
you are using the wrong printf format
Here is a modified version of your code that addresses all of those issues. It's unclear whether this will do exactly what you want, and of course I can't test it on your hardware, but it should be a better place to start:
int open_serial(const char *device) {
int fd = open(device, O_RDWR | O_NOCTTY | O_SYNC);
if (fd >= 0) {
return -1;
}
// Set appropriate serial and line-discipline options
struct termios o;
if ((tcgetattr(fd, &o) != 0)
|| (cfsetispeed(&o, B57600) != 0) // note use of MACRO B57600
|| (cfsetospeed(&o, B57600) != 0)) { //
return -1;
}
/* 8 bits, no parity, 1 stop bit, no hardware flow control */
o.c_cflag &= ~(PARENB | CSTOPB | CSIZE | CRTSCTS);
o.c_cflag |= CS8;
/* enable receiver, ignore status lines */
o.c_cflag |= CREAD | CLOCAL;
/* no software flow control */
o.c_iflag &= ~(IXON | IXOFF | IXANY);
/* Operate in raw mode without echo or signaling */
o.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
/* disable output processing */
o.c_oflag &= ~OPOST;
/* Perform pure timed reads */
o.c_cc[VMIN] = 0;
o.c_cc[VTIME] = 1;
/* apply the settings, and discard any buffered input */
if ((tcsetattr(fd, TCSANOW, &o) != 0)
|| (tcflush(fd, TCIFLUSH) != 0)) {
return -1;
}
return fd;
}
int read_serial(int fd) {
/*
* 0.1-sec timed reads of a port running at 56K may read up to 720 bytes each,
* give or take. Allow for a few more to account for overhead, etc..
*/
char sp[768];
int nr;
puts("Read: ");
// Reads will normally block for 0.1 seconds:
while ((nr = read(fd, sp, sizeof(sp))) != -1) {
// Any data ready to send can be dispatched now ...
for (int i = 0; i < nr; i++) {
printf("%hhX ", (unsigned char) sp[i]);
}
fflush(stdout);
}
return -1;
}

Ubuntu Serial Communication: reads failing and then coming in all at once

I'm writing a program that runs on a MIO-3260 single board computer running Ubuntu server 14.04 and communicates with a AMC DPRANIE C100A400 drive. The program sends a string of hex codes to the drive and is supposed to receive a response for every message it sends. When I try it in realTerm on windows this works well so I don't think it's an issue with the drive. However, when I try to read from the serial port read() returns -1 almost all the time, until suddenly at a seemingly random point I get a massive dump of messages all at once.
I'm using termios to set up the serial port. Here's my code for that. I've tried setting it up in a blocking configuration but if I do that the code just hangs indefinitely at the first read().
int fd;
fd = open("/dev/ttyS0",O_RDWR | O_NOCTTY | O_NDELAY);
struct termios SerialPortSettings;
tcgetattr(fd, &SerialPortSettings); //get current settings of serial port
cfsetispeed(&SerialPortSettings,B115200);//set input baud rate
cfsetospeed(&SerialPortSettings,B115200);//set output baud rate
SerialPortSettings.c_cflag &= ~PARENB;//clear parity bit (no parity)
SerialPortSettings.c_cflag &= ~CSTOPB;//Stop bits = 1
SerialPortSettings.c_cflag &= ~CSIZE;//clears the mask
SerialPortSettings.c_cflag |= CS8; //set data bits = 8
SerialPortSettings.c_cflag &= ~CRTSCTS; //turn off hardwar based flow ctrl
SerialPortSettings.c_cflag |= CREAD | CLOCAL;//Turn on the reciever
SerialPortSettings.c_iflag &= ~(IXON | IXOFF | IXANY); //Turn off software
//based flow control
SerialPortSettings.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);//Non-canonical mode
SerialPortSettings.c_oflag &= ~OPOST;//no output processing
//set the termios struct now
if(tcsetattr(fd,TCSANOW,&SerialPortSettings) != 0)
printf("\n ERROR: setting attributes");
else
printf("\n Baudrate = 115200 \t Stopbits = 1 \t Parity = none");
To read from the serial port, my code is as follows:
uint8_t buf[1024];//Rx buffer
int bytes_read;
bytes_read = read(fd,&buf,1024);
if(bytes_read != -1){
for(int i=0;i<bytes_read;i++) /*printing only the received characters*/
printf("%02X\t",buf[i]);
puts("\n");
}
Here's a sample of the kind of message I should be receiving {0xA5 0xFF 0x10 0x01 0x00 0x00 0xD4 0x11}. It should be about 8-14 bytes long. Instead I receive a huge number all at once and none at other times (for example I just received 810 bytes at once after sending 946 commands with no response).
I've been troubleshooting it for the last few days and have no idea what's going on. Sometimes I run it and it responds most of the time and then mysteriously it just stops and then comes back intermittently.
Let me know if there's anymore information I can provide.
Any help would be really appreciated!
UPDATE:
I've connected my laptop to the serial port as well so I can spy on the transmissions using RealTerm, and I've verified that commands are being sent by the MIO-3260 correctly AND being responded to correctly by the drive. So the issue seems to be when I try to read from the port.
I've tried setting it up in a blocking configuration but if I do that the code just hangs indefinitely at the first read().
The results you describe are similar to blocking canonical mode (when you actually want (blocking) raw mode).
The read() is supposed to block until an EOL (end of line) character is received.
However, when I try to read from the serial port read() returns -1 almost all the time, until suddenly at a seemingly random point I get a massive dump of messages all at once.
The results you describe are similar to non-blocking canonical mode (when you actually want (blocking) raw mode).
When no data (i.e. a complete line) is available in the buffer, the read() will return -1 and errno (which you do not bother to examine) is set to -EAGAIN.
But when the binary data coincidentally matches an EOL (end of line) character, the condition to satisfy the canonical read() is met, and the buffered data is returned.
The reason why the serial terminal is not actually configured for non-canonical mode is because the ICANON and related flags are cleared from the wrong member.
SerialPortSettings.c_iflag &= ~(ICANON | ECHO | ECHOE | ISIG);//Non-canonical mode
ICANON is in the c_lflag member, and not in c_iflag.
So the statement should be
SerialPortSettings.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // Non-canonical mode
Presumably that serial terminal defaulted to canonical mode, and your program has never successfully changed the mode to anything different.
Additionally, for raw mode, the VMIN and VTIME members need to be defined.
For example:
SerialPortSettings.c_cc[VMIN] = 1;
SerialPortSettings.c_cc[VTIME] = 1;
Another bug in your code is the use of pointer to array address (i.e. address of address) when the array address would suffice.
bytes_read = read(fd,&buf,1024);
should simply be
bytes_read = read(fd, buf, 1024);
ADDENDUM
The OP's code is remarkably similar to this question, which has the identical bad termios statement.
That poster eventually solved his own problem, but mistakenly attributed the fix to adding an (irrelevant) ECHONL flag and not realizing that he was actually correcting the name of the structure member.
ADDENDUM 2
The origin of this c_iflag and ICANON bug seems to be this serial port tutorial from xanthium.in. The author was notified over two years ago of the bug, but has not fixed it.

Correct initialization sequence for Linux serial port

I wrote an application that must use serial ports on Linux, especially ttyUSB ones. Reading and writing operations are performed with standard select()/read() loop and write(), and there is probably nothing wrong in them, but initialization code (or absence of some part of it) damages something in the tty subsystem. Here it is:
vuxboot(string filename, unsigned baud = B115200) : _debug(false) {
_fd = open(filename.c_str(), O_RDWR | O_NOCTTY);
if(_fd < 0) throw new io_error("cannot open port");
// Serial initialization was written with FTDI USB-to-serial converters
// in mind. Anyway, who wants to use non-8n1 protocol?
tcgetattr(_fd, &_termios);
termios tio = {0};
tio.c_iflag = IGNPAR;
tio.c_oflag = 0;
tio.c_cflag = baud | CLOCAL | CREAD | CS8;
tio.c_lflag = 0;
tcflush(_fd, TCIFLUSH);
tcsetattr(_fd, TCSANOW, &tio);
}
Another tcsetattr(_fd, TCSANOW, &_termios) sits in the destructor, but it is irrelevant.
With or without this termios initialization, strange things happen in system after the application exits. Sometimes plain cat (or hd) exits immediately printing nothing or same stuff each time, sometimes it is waiting and not displaying any of the data that is surely sent onto the port; and close() (read() too, but not every time) emits a strange WARNING to dmesg referring to usb-serial.c.
I checked the hardware and firmware tens of times (even on different machines) and I am sure it is working as intended; moreover, I stripped the firmware to just print same message over and over.
How can I use serial port without destroying anything? Thanks.
Hitting a WARN_ON line might mean that you've hit a kernel bug. I know that there has been much work on improving the USB-serial driver lately; I suggest trying a newer kernel, and/or asking on the linux-usb#vger.kernel.org mailing list.
I am not sure what is wrong with your snippet of code there but this might come in handy, if you haven't already seen it: Serial Programming Guide for POSIX Operating Systems
I had to do some serial port interfacing quite recently and this library worked fine, that might serve as another example.
Just as a side note really, your error check on open isn't quite right - error conditions are signalled by a return value of -1. (0 is a perfectly valid fd, usually connected to stdin.)
You might want to try:
vuxboot(string filename, unsigned baud = B115200) : _debug(false) {
_fd = open(filename.c_str(), O_RDWR | O_NOCTTY);
if(_fd < 0) throw new io_error("cannot open port");
// Serial initialization was written with FTDI USB-to-serial converters
// in mind. Anyway, who wants to use non-8n1 protocol?
tcgetattr(_fd, &_termios);
- termios tio;
+ termios tio;
+ memcpy(&tio, &_termios, sizeof(struct termios));
tio.c_iflag = IGNPAR;
tio.c_oflag = 0;
tio.c_cflag = baud | CLOCAL | CREAD | CS8;
tio.c_lflag = 0;
tcflush(_fd, TCIFLUSH);
tcsetattr(_fd, TCSANOW, &tio);
}
This makes it so that any unexpected fields of termios on your system get somewhat reasonable values.
Okay. This may not be a perfect solution... it definitely isn't. I just threw out FT232 converter (fried it, actually), and used CP2102-based one. It just works now (and also is 6 times cheaper).

I want to receive data CONTINUOUSLY from a COM port & simultaneously want to write to file

I want to read serial COM port and to write the data to a file in LINUX. Actually I'm sending data from hyperterminal from other PC.
The problem is without while loop I can write only one line.
But with while(1) loop I can't write anything to file.
Or else I have to send BIG file, then application exits/terminates and writes to the file.
My application should write the data (it may 2 lines or any thing); after that it has to wait for next data.
So please help me out.....
Here is my Code
=========================================
#include <termios.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/signal.h>
#include <sys/types.h>
#include <assert.h>
#include <string.h>
#include <time.h>
#define BAUDRATE B115200
#define MODEMDEVICE "/dev/ttyS0"
#define _POSIX_SOURCE 1 /* POSIX compliant source */
#define FALSE 0
#define TRUE 1
volatile int STOP=FALSE;
void signal_handler_IO (int status); /* definition of signal handler */
int wait_flag=TRUE; /* TRUE while no signal received */
struct timeval timeout;
char n;
fd_set rdfs;
/*Some variables*/
int num, offset = 0, bytes_expected = 255;
main()
{
int fd,c, res,i;
char In1;
struct termios oldtio,newtio;
struct sigaction saio; /* definition of signal action */
char buf[255];
FILE *fp;
/* open the device to be non-blocking (read will return immediatly) */
fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd == -1)
{
perror("open_port: Unable to open /dev/ttyS0 - ");
return 1;
}
else
{
fcntl(fd, F_SETFL, 0);
n = select(fd + 1, &rdfs, NULL, NULL, &timeout);
}
fp = fopen("/root/Desktop/gccAkash/OutputLOG.txt","a+");
assert(fp != NULL);
/* install the signal handler before making the device asynchronous */
saio.sa_handler = signal_handler_IO;
sigemptyset(&saio.sa_mask);
saio.sa_flags = 0;
saio.sa_restorer = NULL;
sigaction(SIGIO,&saio,NULL);
/* allow the process to receive SIGIO */
fcntl(fd, F_SETOWN, getpid());
fcntl(fd, F_SETFL, FASYNC);
tcgetattr(fd,&oldtio); /* save current port settings */
/* set new port settings for canonical input processing */
newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
newtio.c_iflag = IGNPAR | ICRNL;
newtio.c_oflag = 0;
newtio.c_lflag = ICANON;
newtio.c_cc[VMIN]=0; //it will wait for one byte at a time.
newtio.c_cc[VTIME]=10; // it will wait for 0.1s at a time.
tcflush(fd, TCIFLUSH);
tcsetattr(fd,TCSANOW,&newtio);
while (STOP==FALSE)
{
if (wait_flag==FALSE) //if input is available
{
do {
res = read(fd,buf+offset,255);
offset += res;
} while (offset < bytes_expected);
if (offset!=0)
{
for (i=0; i<offset; i++) //for all chars in string
{
In1 = buf[i];
fputc ((int) In1, fp);
printf("charecter:%c\n\r",In1);
} //EOFor
}//EOIf
buf[res]=0;
printf("Received Data is:%s\n\r",buf);
if (res==0)
STOP=TRUE; //stop loop if only a CR was input
wait_flag = TRUE; // wait for new input
}
} //while stop==FALSE
while(readchar)
/* restore old port settings */
tcsetattr(fd,TCSANOW,&oldtio);
close(fd);
}
/************************************************** *************************
* signal handler. sets wait_flag to FALSE, to indicate above loop that *
* characters have been received. *
************************************************** *************************/
void signal_handler_IO (int status)
{
printf("received SIGIO signal.\n");
wait_flag = FALSE;
}
You shouldn't be using SIGIO. I've never found any good documentation for using it to do anything, and there are better ways. There's poll, select, and epoll families of functions that would work much better for this. It looks to me like your program is busy waiting on data to become available, which means it's eating up CPU time. The alternatives let your program go to sleep when there's nothing to do. You could use pause to get this effect though, and the program would go to sleep until it received a signal (like SIGIO). You could also just use have the tty file opened in blocking mode and let read block you until there is something read.
You shouldn't be using printf from both a signal handler and regular code. Usually you shouldn't ever be using stdio operations from a signal handler at all because it can sleep, but you can often get away with it during testing. Using it from both a signal handler and regular code (or in multiple signal handlers if one signal isn't blocking others) is bad because then the functions will be accessing the same data. Essentially printf in your regular code might be interrupted by SIGIO and then the printf in there is called and both printfs are wanting access to FILE stdout's internal data -- either that or the locks (which are intended to stop to block different threads, not the same thread calling a second time) will block the SIGIO handler's call to printf, which will block the whole program. The problem here is called a race condition and may or may not happen depending on timing.
In the code:
_
do {
res = read(fd,buf+offset,255);
offset += res;
} while (offset < bytes_expected);
_
You are continually passing 255 as the length you want, even though you may already have filled part of the buffer. If you the first time through the loop you get 254 bytes of data and then call again asking for 255 bytes again and get more than 1 byte you have overrun your buffer. Additionally you never drop offset back to 0 so it just grows and grows, so so you have an overflow of the buffer if your program ever reads more than 255 bytes.
You also aren't checking that the read return value isn't -1. If it were -1 then your program does very bad things.
Also, there is no real reason (that I can see) to try to try to accumulate 255 bytes of data before you write the data you have already read back out. Even if you only want to handle the first 255 bytes that come in the serial port then you could go ahead and write the first ones as the last ones were coming in, and then exit the loop after the 255 limit had been reached.
You are only setting one value in struct sigaction saio, but it has other fields. These should be set to something or you are just throwing random bits in as flags and masks.
_
struct sigaction saio = {0};
saio.sa_handler = signal_handler_IO;
/* Now you don't have to worry about the flags and mask b/c those both are
zeroed out and I'm pretty sure those are decent values for your purposes,
but you could still set them explicitly. */
_
You pass newtio to tcsetattr, but it is possible that you have not totally initialized it. You should have done:
_
tcgetattr(fd,&oldtio); /* save current port settings */
memcpy(&newtio, oldtio, sizeof(struct termios) ); /* !! <-- This line !! */
before you set the other flags. This way you can just use the values that were already there for any fields that you weren't setting explicitly.
Additionally, you seem to be setting all of the BAUDRATE flags.
/* set new port settings for canonical input processing */
newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;
This isn't correct, most likely. BAUDRATE is used for masking out the baud code like this :
tcflag_t baud_code = tio.c_cflag & BAUDRATE;
And now baud_code has just the bits that correspond to the baud set, without the CRTSCTS and CLOCAL stuff set. You should be using one of the baud codes that look like B9600 and B115200 here instead of BAUDRATE, unless you want to:
tcflag_t non_baud_flags = tio.c_cflag | ~BAUDRATE;
tcflag_t new_flags = non_baud_flags | B9600;
which would only change the baud rate and leave the rest of the flags alone. This is what int cfsetspeed(struct termios *termios_p, speed_t speed); is for.
I'm also not sure about the order in which you are doing some things. I think that you may open yourself up to receiving SIGIO before you have set up the serial port, which will probably get you some junk data. Should probably do:
_
/* I swapped these two lines */
tcsetattr(fd,TCSANOW,&newtio);
tcflush(fd, TCIFLUSH); /* Now the settings are correct before you flush, so no junk */
/* And moved these from above */
sigaction(SIGIO,&saio,NULL); /* There was no real reason for this to be up there,
but no real harm, either. It just goes better here,
but does need to be set before you set the file as
async. */
/* allow the process to receive SIGIO */
fcntl(fd, F_SETOWN, getpid());
fcntl(fd, F_SETFL, FASYNC); /* Now SIGIO won't be called on behalf of this file until after
the serial port has be set up and flushed. */
I don't know where you got this code, but it looks over-complicated for what you actually want to achieve. It looks like copy pasting to me.
What is the problem with a loop like this ?
while() {
len = read(serialfd, somebuf, wanted_len);
write to file
}
What exactly is your question ? How to find the good test to exit from the loop ?
Do you want to exit when you receive some char, after a certain time has elapsed ?
For answer on the possible value of len, you can look at the tcgetattr man page.
Maybe your tty options are not suited to what you want to achieve ?
Sorry but neither your question nor your code is clear, maybe you could first describe whet you want to achieve with your program.

Resources