Program hangs when closing serial port connection - c

I am reading data through a USB connection as a serial port with the PL2303 driver. It returns successfully when doing an open and when I set they TTY options and non blocking. When I try to close the connection, it hangs. In this state it reads "�" instead of characters.
I can connect to the device perfectly fine with cutecom. Here is the strange part:
If I first connect to the device via cutecom (a serial monitor), my program will connect and close perfectly fine every time afterwards. It reads the characters as I expect them to be read. (No �).
If I disconnect and reconnect the hardware, my program will hang again until I run cutecom.
Since it works after I use cutecom, it makes me think that I am missing something in my initial connection, or connection settings. Here's what I use to connect:
baud_rate = 38400;
fd = open (device_path, O_RDONLY | O_NOCTTY );
In my set_tty_options function:
struct termios tty_options;
memset (&tty_options, 0, sizeof(tty_options));
tcgetattr (fd, &tty_options);
cfsetispeed(&tty_options, baud_rate); // set baud rate
tty_options.c_cflag = (tty_options.c_cflag & ~CSIZE) | CS8; // 8 bit msgs
tty_options.c_cflag |= (CLOCAL | CREAD); // enable reading
tty_options.c_cflag &= ~(PARENB | PARODD); // shut off parity
tty_options.c_cflag |= parity;
tty_options.c_cflag &= ~CSTOPB;
tty_options.c_cflag &= ~CRTSCTS;
if (tcsetattr (fd, TCSANOW, &tty_options) != 0)
{
printf("error %d from tcsetattr\n", errno);
return TTY_ERROR;
}
In set_blocking function:
if (tcgetattr (fd, &tty) != 0)
{
printf("error %d from tggetattr", errno);
return FAILURE;
}
// 0 or 1 byte is enough to return from read
tty.c_cc[VMIN] = should_block ? 1 : 0;
tty.c_cc[VTIME] = 5; // 0.5 seconds read timeout
if (tcsetattr (fd, TCSANOW, &tty) != 0)
{
printf("error %d setting term attributes", errno);
return FAILURE;
}

I think you want to add | O_SYNC to the open flags to insist on synchronous i/o. I doubt that is causing a problem though.
However, I think you want to ignore the break signal, which is reported as a NUL character like you are getting:
tty_settings.c_iflag &= ~IGNBRK; // ignore break signal
Also, you want to be sure the input processing is completely turned off, so that receipt of a backspace, ^C, ^\, etc. aren't triggering any reaction:
tty_settings.c_lflag = 0; // no signaling chars, no echo,
// no canonical processing
It looks like you are already using my set_blocking() function, so that should be okay.

Here's what I ended up doing. I figured this out by basically copying and pasting parts from cutecom's source code.
When opening...
int fd, n;
fd = open (device_path, O_RDONLY | O_NOCTTY | O_NDELAY);
... error check fd ...
n = fcntl(ail_info->ail_serial_fd, F_GETFL, 0);
fcntl(fd, F_SETFL, n & ~O_NDELAY);
You cannot set the baud rate as I was doing. You have to use the defined B38400;
baud = B38400;
Then, I added wallyk's answer.
tty_settings.c_lflag = 0;
Edit: As per sawdust comment, I found a better way to set this to raw input.
tty_options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
And it works.

Related

POSIX read() call remains blocked forever regardless of what's set in VTIME

I need to configure the UART settings such that the read() call remains blocked until a certain time before it's unblocked again if it didn't receive any data within the timeout. So if the timeout is 5 seconds, it remains blocked till 5 seconds max if it doesn't receive any byte and then unblocks...
I tried using VMIN which should block the read() call until no character is read within the time allowed, after which the call to read() returns 0, but that doesn't seem to be the case for me: the read() call remains blocked forever and as soon as I enter stuff in a minicom serial session, read() unblocks and then goes back to getting blocked again.
I'm not sure if it's taking into account VTIME setting, or maybe I'm misconfiguring it.
Rather, would select() with a timeout be a better approach?
#define SERIAL_PORT "/dev/ttyUSB4"
pthread_t td;
int fd;
int SerialOpen()
{
struct termios term;
fd = open(SERIAL_PORT, O_RDWR);
if (fd < 0)
{
perror ("failed to open");
return -1;
}
bzero(&term, sizeof(term));
cfmakeraw(&term);
term.c_cflag |= CREAD;
tcgetattr(fd, &term);
term.c_iflag &= ~ICRNL;
term.c_iflag &= ~INLCR;
term.c_iflag &= ~IGNBRK;
term.c_oflag &= ~OCRNL;
term.c_oflag &= ~ONLCR;
term.c_oflag &= ~OPOST;
term.c_lflag &= ~ICANON;
term.c_lflag &= ~ISIG;
term.c_lflag &= ~IEXTEN;
term.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|ECHOCTL|ECHOPRT|ECHOKE);
cfsetspeed(term, B115200); // set baud rate
term.c_cflag &= ~CSTOPB;
term.c_cflag |= CS8;
// disable flow control
term.c_cflag &= ~CRTSCTS;
term.c_iflag &= ~(IXON | IXOFF);
term.c_cflag &= ~PARENB; // no parity
term.c_cc[VTIME] = 50; // Wait for up to 5s (50 deciseconds), returning as soon as any data is received.
term.c_cc[VMIN] = 0;
if ( (tcsetattr(fd, TCSANOW, &term)) < 0)
{
perror ("Failed to set attr");
return -1;
}
return 1;
}
void *Rx(void *arg)
{
char buff[100] = {0};
while(1)
{
int sz = read(fd, buff, sizeof(buff)); // block until VTIME times out
if (sz < 0)
{
perror ("Read failed");
}
printf ("Received bytes %d: %s\n", sz, buff);
}
}
int main()
{
int ret = SerialOpen();
if (ret < 0)
{
return -1;
}
if (pthread_create(&td, NULL, Rx, NULL) != 0)
{
printf("Fail to create thread!\n");
}
pthread_join(td, 0);
return 0;
}
Caveat: Prefaced by the top comments.
Be sure you're applying the fixes I suggested in the comments above.
select may have the same issues as VMIN/VTIME. select operates at a level after the TTY layer [which is where VMIN/VTIME operate]. At a lower level is the USB driver. If the TTY layer isn't set up correctly, select may have the same issues as read (i.e. probably not the issue). Although, you might do: open(/dev/whatever,O_RDWR | O_NONBLOCK)
But, I suspect that the issue is at a more fundamental/lower level. I think the USB layer/level is part of the problem.
USB-to-RS232 cables are tricky. The cable may need active H/W flow control enabled to operate. So, just disabling H/W flow control via (e.g.) clearing CRTSCTS may not work too well.
Double check: Are you sure that you are getting all the way through the configuration/initialization of the device. Could the open be hanging? You could add debug printf statements throughout the code [easier than using gdb for this].
What is the specific manufacturer and device for the USB cable? What is the specific manufacturer/model of the end/UART device? Although the standard/generic USB serial driver (e.g. usbserial) should work, some vendors need a specific driver (e.g. FTDI has its own USB level driver). So, you may need to consult the manufacturer's website/datasheet for the specific devices in question.
Also, sometimes a USB cable shows up on two different /dev/tty* devices. You may have the wrong one. You may need ttyS* instead of ttyUSB* or the device can show up as (e.g.) ttyACM* !!! This can occur even if ttyS* or ttyUSB* are [also] present.
Look at:
ls -l /dev
/proc/devices
/sys/devices
lsmod
lsusb
lspci
dmesg [and, maybe, syslog output]
Did you [or the system] modprobe usbserial?
Specifically, the dmesg output should say which tty* the device is connected to.
Some resources (from a "all the words" websearch on linux /dev USB uart cable):
https://www.cyberciti.biz/faq/find-out-linux-serial-ports-with-setserial/
https://unix.stackexchange.com/questions/81754/how-to-match-a-ttyusbx-device-to-a-usb-serial-device

Program hangs when setting serial port parameters

In my program a connection is opened with serial port that recieves 10-byte packages with set frequency (usually 100 Hz). However i have run into the following problem: sometimes (but not always) when i am closing connection, the program hangs.
In the following code i do not specify constants; at runtime connection parameters (baud rate, parity...) are determined from command line. In practice, connection is usually opened with baud rate 230400, no parity, 8-bit character size and 1 stop bit.
This is how i am opening serial port:
struct termios config;
LOGPRINT("Will operate on device \'%s\'\n", pconfig->device);
LOGPRINT("Opening serial device\n");
int flag = O_RDWR | O_NOCTTY | ASYNC_LOW_LATENCY;
// pconfig contains some necessary information to set connection parameters
// async == false for connectin that causes problems
int fd = open(pconfig->device, async? flag | O_NONBLOCK: flag);
if(fd == -1)
{ /* skipped error handling */ }
LOGPRINT("Retrieving terminal attributes\n");
if(!!tcgetattr(fd, &config))
{ /* skipped error handling */ }
else
{
// backup old configuration for restoring it on exit
if(poldconfig != NULL)
memcpy(poldconfig, &config, sizeof(struct termios));
LOGPRINT("Setting terminal attributes\n");
set_attribs(fd, &config, pconfig);
}
...
int set_attribs(const int fd, struct termios* pterm, const struct serial_config* pconfig)
{
// set baud rate
// baud_rates contains integers of actual rates
LOGPRINT("Setting baud rate to %d.\n", baud_rates[pconfig->baud_rate_index]);
// change to raw mode
LOGPRINT("Setting terminal to raw mode\n");
pterm->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR
| IGNCR | ICRNL | IXON);
pterm->c_oflag &= OPOST;
pterm->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
// baud_bits contains actual pre-defined baud rate flags
cfsetispeed(pterm, baud_bits[pconfig->baud_rate_index]);
cfsetospeed(pterm, baud_bits[pconfig->baud_rate_index]);
LOGPRINT("Set character size, parity and stop bit flags.\n");
// set character size
pterm->c_cflag &= ~CSIZE;
pterm->c_cflag |= pconfig->csize_flag;
// set parity
pterm->c_cflag &= ~(PARENB | PARODD);
pterm->c_cflag |= pconfig->parity_flag;
// set stopbits
pterm->c_cflag &= ~CSTOPB;
pterm->c_cflag |= pconfig->stopbits_flag;
// enable reading; ignore control lines
pterm->c_cflag |= CREAD | CLOCAL;
LOGPRINT("Flush terminal.\n");
// flush terminal
tcflush(fd, TCIOFLUSH);
LOGPRINT("Apply parameters.\n");
return tcsetattr(fd, TCSANOW, pterm);
}
For valid reasons, i needed to force serial device driver to use 1-byte buffer for incoming/outgoing data, which i have set using this function:
int set_port_type(int fd, int ptype)
{
struct serial_struct temp;
int res = ioctl(fd, TIOCGSERIAL, &temp);
if(!!res)
{ /* stipped error handling */ }
temp.type = ptype;
res = ioctl(fd, TIOCSSERIAL, &temp);
if(!!res)
{ /* stipped error handling */ }
return res;
}
with ptype = PORT_UNKNOWN or ptype = PORT_16450.
When finished with serial port, i perform the following operations:
Flush it
Set its type back to 16550A
Restore its settigns to saved ones
Close it
From time to time it causes the program to hang and stop responding to any external stimulus. It forces me to suspend the program and then reboot system, as it never releases file descriptors it holds.
What can i do to ensure that does not happen? Even if settings are not restored, the program needs to close on its own - maybe with error code.
Operating system i am using is TinyCore Linux. No other user program is being run an the same time as my program.
IMportant clarification. Serial device i am working with is actually NPort 5232 - asyncronous RS-422/485 - Ethernet communicator (description from vendor. Consequently, serial devices i am working with are virtual, which enables me to try and change their type / size of internal buffer.

How to recieve data on serial port synchronously?

I have the following set up: a computer that runs Linux (TCL) and device that sends packets of fixed length (10 bytes) with fixed frequency (100 Hz).
On computer, i have that serial port open, and i am trying to read incoming data. However, instead of recieving it in 10-byte chunks all the time, lesser amounts of data are being read, and i am forced to reassemble them back.
My goal is to send a responce for each packet once it arrives, preserving intervals between packets as much as possible.
This is how i open the serial port:
int fd = open(device_name, O_RDWR | O_NOCTTY | O_NONBLOCK);
if(fd == -1)
{
LOGPRINT("Failed to open terminal file.\nError #%d (%s)\n",
errno, strerror(errno));
return fd;
}
LOGPRINT("Setting terminal attributes\n");
struct termios config;
struct termios *pterm = &config;
// set baud rate
LOGPRINT("Setting baud rate to %d.\n", baud_rates[baud_rate_index]);
// change to raw mode
LOGPRINT("Setting terminal to raw mode\n");
pterm->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR
| IGNCR | ICRNL | IXON);
pterm->c_oflag &= OPOST;
pterm->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
cfsetispeed(pterm, baud_bits[pconfig->baud_rate_index]);
cfsetospeed(pterm, baud_bits[pconfig->baud_rate_index]);
LOGPRINT("Set character size, parity and stop bit flags.\n");
// set character size
pterm->c_cflag &= ~CSIZE;
pterm->c_cflag |= csize_flag;
// set parity
pterm->c_cflag &= ~(PARENB | PARODD);
pterm->c_cflag |= parity_flag;
// set stopbits
pterm->c_cflag &= ~CSTOPB;
pterm->c_cflag |= stopbits_flag;
// enable reading; ignore control lines
pterm->c_cflag |= CREAD | CLOCAL;
// disable flow control
pterm->c_cc[VMIN] = 1;
pterm->c_cc[VTIME] = 0;
LOGPRINT("Flush terminal.\n");
// flush terminal
tcflush(fd, TCIOFLUSH);
LOGPRINT("Apply parameters.\n");
return WRAPSYSCALL(tcsetattr(fd, TCSANOW, pterm));
Additionally i set the following on file descriptor with fnctl:
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC | O_DIRECT);
I am reading from file descriptor in a cycle with select() call on file descriptor set containing fd, then reading all bytes avaiable (requesting number of bytes much bigger then 10).
What should i change to ensure incoming data are processed as they come properly, in timely manner?
If your program does nothing else than waiting for bytes and answering, then you can try using blocking file operations. Remove O_NONBLOCK flag from your open function call, and after setting the port just do a read(fd, buffer, 10). The function will return after it reads exactly 10 bytes (or an error happens or signal arrives).
However, bear in mind that you read operation can start in the middle of packet transmission, so for example you can get last x bytes of n-th packet and first 10-x of packet n+1.

C: read() hangs without getting input from serial port on unix

I'm having some issues getting data from a serial port using C on my Ubuntu 12 system.
I'm using open() and read(), and here is my code:
Fd = open("/dev/ttyUSB0", O_RDONLY | O_NOCTTY);
if (Fd == -1) {
printf("Could not open serial port: %s\n", strerror(errno));
return 1;
}
fcntl(Fd, F_SETFL, 0);
char buf;
while (1) {
read(Fd, &buf, 1);
printf("%c", buf);
}
However - my serial device is set to send "Boot.\r\n" followed by "To send: ", but when I attach the device and start the program, I only get the first line ("Boot.") and then no more. If I start gtkterm/picocom, I get both lines straight away.
I've also tried adding a signal handler for SIGTERM to get the port closed properly, using:
void signal_callback_handler(int signum) {
printf("Caught SIGTERM\n");
close(Fd);
exit(signum);
}
and
signal(SIGINT, signal_callback_handler);
Using this, I get the following when I press CTRL-C:
Boot.
^CTo send: Caught SIGTERM
I've also tried setting up the port first, using:
struct termios port_settings; // structure to store the port settings in
cfsetispeed(&port_settings, B115200); // set baud rates
cfsetospeed(&port_settings, B115200);
port_settings.c_cflag &= ~PARENB; // set no parity, stop bits, data bits
port_settings.c_cflag &= ~CSTOPB;
port_settings.c_cflag &= ~CSIZE;
port_settings.c_cflag |= CS8;
tcsetattr(Fd, TCSANOW, &port_settings);// apply the settings to the port
This only makes the situation worse - I get spammed with � :(
I'd be very appreciative of any help, thanks in advance!
Looks like your printf is just not being flushed until it hits a newline. That's why you get the first part of the output, but not the second. You could add fflush(stdout) after your printf to see the output immediately.

Linux - serial port read returning EAGAIN

I am having some trouble reading some data from a serial port I opened the following way. I've used this instance of code plenty of times and all worked fine, but now, for some reason that I cant figure out, I am completely unable to read anything from the serial port.
I am able to write and all is correctly received on the other end, but the replies (which are correctly sent) are never received (No, the cables are all ok ;) )
The code I used to open the serial port is the following:
fd = open("/dev/ttyUSB0", O_RDWR | O_NONBLOCK | O_NOCTTY);
if (fd == -1)
{
Aviso("Unable to open port");
return (fd);
}
else
{
//Get the current options for the port...
bzero(&options, sizeof(options)); /* clear struct for new port settings */
tcgetattr(fd, &options);
/*-- Set baud rate -------------------------------------------------------*/
if (cfsetispeed(&options, SerialBaudInterp(BaudRate))==-1)
perror("On cfsetispeed:");
if (cfsetospeed(&options, SerialBaudInterp(BaudRate))==-1)
perror("On cfsetospeed:");
//Enable the receiver and set local mode...
options.c_cflag |= (CLOCAL | CREAD);
options.c_cflag &= ~PARENB; /* Parity disabled */
options.c_cflag &= ~CSTOPB;
options.c_cflag &= ~CSIZE; /* Mask the character size bits */
options.c_cflag |= SerialDataBitsInterp(8); /* CS8 - Selects 8 data bits */
options.c_cflag &= ~CRTSCTS; // disable hardware flow control
options.c_iflag &= ~(IXON | IXOFF | IXANY); // disable XON XOFF (for transmit and receive)
options.c_cflag |= CRTSCTS; /* enable hardware flow control */
options.c_cc[VMIN] = 0; //min carachters to be read
options.c_cc[VTIME] = 0; //Time to wait for data (tenths of seconds)
//Set the new options for the port...
tcflush(fd, TCIFLUSH);
if (tcsetattr(fd, TCSANOW, &options)==-1)
{
perror("On tcsetattr:");
}
PortOpen[ComPort] = fd;
}
return PortOpen[ComPort];
After the port is initializeed I write some stuff to it through simple write command...
int nc = write(hCom, txchar, n);
where hCom is the file descriptor (and it's ok), and (as I said) this works. But... when I do a read afterwards, I get a "Resource Temporarily Unavailable" error from errno.
I tested select to see when the file descriptor had something t read... but it always times out!
I read data like this:
ret = read(hCom, rxchar, n);
and I always get an EAGAIN and I have no idea why.
Update:
The HW is working fine! I can see that there is inbound data on the serial port because I've made a debug cable to read whats going on on another terminal. So...
I know what nonblocking should do. My question is... why isn't anything getting read!. The same setup works fine on windows, so all hardware is working fine...
This is driving me nuts! I'm sure it's something simple as hell! I even tried getting rid of O_NONBLOCK to see when I would receive something... but nothing...
Read this.
EAGAIN Non-blocking I/O has been
selected using O_NONBLOCK and no data
was immediately available for reading.
You need to check the serial terminal settings first.
use command - stty -F /dev/ttyUSB0 -a
Check that ctsrts is selected as -ctsrts
and do the other required settings with stty utility and you are done.
EAGAIN with O_NONBLOCK means there's been no data received on the port. Check that the port and cable are working properly (using minicom or some other known-good program), and that the remote really is sending some data.
see my code samples, if EAGAIN, you'd try to read again:
...
options.c_cflag &= ~PARENB;
options.c_iflag &= ~INPCK;
...
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // input
options.c_oflag &= ~OPOST; // output
...
fd = open("/dev/ttyUSB0", O_RDWR | O_NDELY | O_NOCTTY);
fcntl(fd, F_SETFL, 0);
...
int nc = write(hCom, txchar, n);
msleep(500); // wait 500ms
fcntl(hCom, F_SETFL, FNDELAY); // don't block serial read
ret = read(hCom, rxchar, n);
if (ret > 0) {
here had read n bytes or just partial data, read again if partial.
}
if (ret < 0) {
if (EAGAIN == errno) {
not a real error, just read again.
} else {
oops, errors.
}
}
...

Resources