USB devices affect stty (uart) - keyboard

I'm writing program that communicates with MCUs via UART (stty)
The platform I use is Acmesystems Acqua A5 (SAMA5D31) with Debian.
The protocol between MCU and PC is following: MCU address, data, CRC. If all is ok, MCU generates a coresponding answer.
The problem is - when I type something on a USB keyboard or plug flash drive, communication freezes. So, I think USB strangely affects stty. I found out that if I disable IGNBRK, there are no freezes, but lot of errors during communication (that's true, I don't need any special characters)
Here is COM port initialization:
struct termios options;
tcgetattr (fd, &options);
cfsetispeed (&options, B115200);
cfsetospeed (&options, B115200);
options.c_cflag |= (CLOCAL | CREAD);
options.c_cflag &= ~(PARENB | CSTOPB | CSIZE);
options.c_cflag |= CS8;
options.c_lflag &= ~(ICANON | ISIG | IEXTEN | ECHO | ECHOE | ECHOK | ECHOKE | ECHONL | ECHOCTL | ECHOPRT);
options.c_lflag |= NOFLSH;
options.c_iflag &= ~(INPCK | INLCR | IXON | IXOFF | IXANY | IGNCR | ICRNL | IUCLC);
options.c_iflag |= (IGNBRK | IGNPAR);
options.c_oflag &= ~(OPOST | OFILL);
options.c_cc[VINTR] = 0;
options.c_cc[VQUIT] = 0;
options.c_cc[VERASE] = 0;
options.c_cc[VKILL] = 0;
options.c_cc[VEOF] = 0;
options.c_cc[VTIME] = 0;
options.c_cc[VMIN] = 1;
options.c_cc[VSWTC] = 0;
options.c_cc[VSTART] = 0;
options.c_cc[VSTOP] = 0;
options.c_cc[VSUSP] = 0;
options.c_cc[VEOL] = 0;
options.c_cc[VREPRINT] = 0;
options.c_cc[VDISCARD] = 0;
options.c_cc[VWERASE] = 0;
options.c_cc[VLNEXT] = 0;
options.c_cc[VEOL2] = 0;
tcsetattr (fd, TCSANOW, &options);
fcntl (fd, F_SETFL, FNDELAY);

Related

Linux Serial Port Receive 7 Bit

My serial configuration like below, but my serial port read 7 bit why causes this problem. I want 8 bit configuration.
fd = open(device, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1)
qDebug() << "SERIAL: Unable to open port ";
else
fcntl(fd, F_SETFL, FNDELAY);
struct termios port_settings;
cfsetispeed(&port_settings, B115200);
cfsetospeed(&port_settings, B115200);
port_settings.c_cflag &= ~PARENB;
port_settings.c_cflag &= ~CSTOPB;
port_settings.c_cflag &= ~CSIZE;
port_settings.c_cflag |= CS8;
tcsetattr(fd, TCSANOW, &port_settings);

File descriptor is in blocking mode, but read() is not blocking

I am writing some software to handle serial port read/writing for a Beaglebone system. The OS is Debian 9. I am writing code in C with --std=gnu99.
Here is my code:
// reference
// https://www.cmrr.umn.edu/~strupp/serial.html
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <termios.h>
#include <sys/ioctl.h>
int open_port(void)
{
int fd;
fd = open("/dev/ttyS1", O_RDWR | O_NOCTTY | O_NDELAY);
// removing O_NDELAY no difference
if(fd == -1)
{
perror("open_port: Unable to open /dev/ttyS1 - ");
}
else
{
fcntl(fd, F_SETFL, 0);
}
return fd;
}
int main()
{
// open fd for serial port
int fd = open_port();
// set baud rate
struct termios options;
// get current options
tcgetattr(fd, &options);
// set input and output baud
cfsetispeed(&options, B115200);
cfsetospeed(&options, B115200);
// enable reciever and set local mode
// CLOCAL: ignore modem control lines
// CREAD: enable reciever
options.c_cflag |= (CLOCAL | CREAD);
// set partity bit
//options.c_cflag &= PARENB;
options.c_cflag &= ~PARENB;
// use even parity
//options.c_cflag &= ~PARODD;
// use only 1 stop bit
options.c_cflag &= ~CSTOPB;
// set character size to 8 bit
options.c_cflag &= ~CSIZE;
options.c_cflag &= CS8;
// disable flow control
//options.c_cflag &= ~CNEW_RTSCTS; // does not work?
// note: check local options, some may be required
// disable canonical input (use raw)
// disable echoing of characters
// disable echoing of erase characters
// disable signals SIGINTR SIGSUSP SIGDSUSP, SIGQUIT
// input characters are passed through exactly as recieved
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
// disable parity check
options.c_iflag &= IGNPAR;
// disable flow control (software)
options.c_iflag &= ~(IXON | IXOFF | IXANY);
// set raw output, no output processing
options.c_oflag &= ~OPOST;
// set options
tcsetattr(fd, TCSANOW, &options);
char data[] = {0x01, 0x03, 0x00, 0x00};
int n = write(fd, data, 4);
if(n =! 4)
{
printf("write fail %d\n", n);
}
char buffer[40];
n = read(fd, buffer, 2);
if(n != 2)
{
printf("read fail %d\n", n);
}
if(buffer[0] == 0x03 && buffer[1] == 0x00)
{
}
else
{
printf("uart error\n");
}
char data2[] = {0x00, 0x00, 0x00, 0x01};
n = write(fd, data2, 4);
if(n != 4)
{
printf("write fail %d\n", n);
}
n = read(fd, buffer, 2);
if(n != 2)
{
printf("read fail %d\n", n);
}
if(buffer[0] == 0x03 && buffer[1] == 0x00)
{
}
else
{
printf("uart error\n");
}
printf("process complete\n");
// to close
close(fd);
}
The issue I have is calls to read() do not block. This is not the desired behaviour. These calls should block until 2 bytes of data are received.
My guess would be I have misconfigured an option somewhere. However I don't know where the mistake is, and from what I have researched, this should be reading in blocking mode. (fcntl(fd, F_SETFL, 0);)
The issue I have is calls to read() do not block.
That's actually a conclusion, rather than an observation.
Presumably your program is reporting zero bytes read, which technically is not an error.
... from what I have researched, this should be reading in blocking mode. (fcntl(fd, F_SETFL, 0);)
Correct, you have the file descriptor configured for blocking mode.
My guess would be I have misconfigured an option somewhere.
Yes, your termios configuration is incomplete. (It uses proper bitwise operations, but unfortunately is incomplete and has errors, see ADDENDUM.)
When configured for non-canonical mode, you must also specify the VMIN and VTIME parameters.
When left unspecified (as in your code), the default values of VMIN = 0 and VTIME = 0 are likely to be in effect, which is equivalent to a non-blocking read.
Note that the byte length in the read() syscall has minimal effect on completion behavior (other than capping the number of bytes to be returned).
From the man page for read(2):
read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.
Therefore the count parameter is effectively a maximum.
The actual number of bytes read can be between zero and count depending on the values configured for VMIN and VTIME (and the actual data available).
See Linux Blocking vs. non Blocking Serial Read for more details.
To read at least two bytes per syscall (with no time limit), include the following in the termios configuration:
options.c_cc[VMIN] = 2;
options.c_cc[VTIME] = 0;
The above two statements inserted into your program before the tcsetattr() syscall would cause your program to block forever until exactly two bytes can be returned in each read(fd, buffer, 2) statement.
A read(fd, buffer, 4) statement would also block forever, and return either 2, 3, or 4 bytes of data (depending on program timing with respect to data reception & buffering).
ADDENDUM
Turns out there are a few bugs in your termios initialization.
As posted your program neither transmits or receives any data correctly, and you failed to detect these errors or neglect to mention them.
The following statement in your program obliterates all the previous c_cflag operations, and unintentionally reconfigures the baudrate to B0 and the character size to CS5:
options.c_cflag &= CS8;
The correct syntax is:
options.c_cflag |= CS8;
The statement
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
should be expanded with more attributes to match cfmakeraw():
options.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
The following statement in your program disables every attribute in c_iflag except IGNPAR, and leaves the IGNPAR attribute in an unknown/ambiguous state:
// disable parity check
options.c_iflag &= IGNPAR;
To match cfmakeraw(), it should be changed to:
options.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
| INLCR | IGNCR | ICRNL);
With all the corrections mentioned, I can get your program to execute as expected.

stty serial port settings for parity not persistent

I have a netcore app that opens the serial port, and writes "parity error" on the console once a parity error gets detected. It works fine in Windows 10 but can't get it to work under Linux.
My assumption is that the OS is not passing the parity error to netcore.
For checking the port settings I run:
stty -D /dev/ttyS0 -ignpar inpck
then I run:
stty -D /dev/ttyS0 -a
and the settings seem to be properly set (-ignpar inpck) as expected.
Then I run my netcore 3 app but parity error is not detected.
So I run
stty -D /dev/ttyS0 -a
for validating the settings, but these seem to be reset (-ignpar -inpck)
How can I force my app to run with the inpck attribute enabled?
Is there a way to make inpck to be enabled by default?
Thanks.
UPDATE: The netcore 3 app parity error detection works fine in windows 10, but it does not work under linux. My assumptions are that either:
A) netcore runtime is not passing the parity settings to the driver
(not likely)
B) the OS is ignoring the instructions.
The stty command is simply a method from the shell to utilize the termios API.
Application programs are expected to use the termios API to configure the serial terminal to the exact requirements of the situation (rather than rely on an expected configuration on startup).
If the app environment that you're using does not permit access to the termios API, then you may be using an inappropriate method.
Do you know any linux app that could explicitly react somehow to a parity error?
The following C program reads lines (i.e. canonical mode) from a serial terminal, and is configured to detect a Mark (or 1) as the parity bit with an 8-bit character frame.
#define SERIALTERMINAL "/dev/ttyS0"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
int set_interface_attribs(int fd, int speed)
{
struct termios tty;
if (tcgetattr(fd, &tty) < 0) {
printf("Error from tcgetattr: %s\n", strerror(errno));
return -1;
}
cfsetospeed(&tty, (speed_t)speed);
cfsetispeed(&tty, (speed_t)speed);
tty.c_cflag |= CLOCAL | CREAD;
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8; /* 8-bit characters */
tty.c_cflag |= PARENB; /* enable parity */
tty.c_cflag &= ~PARODD; /* Even parity */
tty.c_cflag |= CMSPAR; /* force Even parity to SPACE */
tty.c_cflag &= ~CSTOPB; /* only need 1 stop bit */
tty.c_cflag &= ~CRTSCTS; /* no hardware flowcontrol */
tty.c_lflag |= ICANON | ISIG; /* canonical input */
tty.c_lflag &= ~(ECHO | ECHOE | ECHONL | IEXTEN);
tty.c_iflag &= ~IGNCR; /* preserve carriage return */
tty.c_iflag &= ~(INLCR | ICRNL | IUCLC | IMAXBEL);
tty.c_iflag &= ~(IXON | IXOFF | IXANY); /* no SW flowcontrol */
tty.c_iflag |= IGNBRK; /* ignore breaks */
tty.c_iflag &= ~ISTRIP;
tty.c_iflag &= ~IGNPAR; /* report error */
tty.c_iflag |= INPCK; /* test parity */
tty.c_iflag |= PARMRK; /* verbose parity err */
tty.c_oflag &= ~OPOST;
tty.c_cc[VEOL] = 0;
tty.c_cc[VEOL2] = 0;
tty.c_cc[VEOF] = 0x04;
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
printf("Error from tcsetattr: %s\n", strerror(errno));
return -1;
}
return 0;
}
int main(void)
{
char *portname = SERIALTERMINAL;
int fd;
int wlen;
fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
if (fd < 0) {
printf("Error opening %s: %s\n", portname, strerror(errno));
return -1;
}
/*baudrate 115200, 8 bits, Space for parity, 1 stop bit */
set_interface_attribs(fd, B115200);
/* simple output */
wlen = write(fd, "Hello!\n", 7);
if (wlen != 7) {
printf("Error from write: %d, %d\n", wlen, errno);
}
tcdrain(fd); /* delay for output */
/* simple canonical input, read lines */
do {
unsigned char buf[81];
unsigned char *p;
int rdlen;
rdlen = read(fd, buf, sizeof(buf) - 1);
if (rdlen > 0) {
buf[rdlen] = 0;
printf("Read %d:", rdlen);
/* first display as hex numbers then ASCII */
for (p = buf; rdlen-- > 0; p++) {
printf(" 0x%x", *p);
if (*p < ' ')
*p = '.'; /* replace any control chars */
}
printf("\n \"%s\"\n\n", buf);
} else if (rdlen < 0) {
printf("Error from read: %d: %s\n", rdlen, strerror(errno));
} else { /* rdlen == 0 */
printf("Nothing read. EOF?\n");
}
/* repeat read */
} while (1);
}
The program was executed on an old Linux (Ubuntu 14.04.2 LTS) PC that has a built-in 16550A serial port.
[ 2.656593] 00:08: ttyS0 at I/O 0x3f8 (irq = 4, base_baud = 115200) is a 16550A
This serial port does not seem capable of transmitting 8-bits with parity (an 11-bit frame), but does seem capable of reading 8-bits with parity.
The serial data is generated from a SBC that has a 9-bit capable UART. An oscilloscope was used to capture frames to confirm that the 8S1 and 8E1 frames were 11 bits long.
(An FTDI USB-to-RS232 converter was not reliable in generating all parity configurations with 8-bit characters.)
When the sender is configured for 8-bits and Space for parity (which matches the program), the PC program reads "ABCDEFG\n" as:
Read 8: 0x41 0x42 0x43 0x44 0x45 0x46 0x47 0xa
"ABCDEFG."
The data is read correctly.
When the sender is configured for 8-bits and Even parity, the PC program reads "ABCDEFG\n" as:
Read 14: 0x41 0x42 0xff 0x0 0x43 0x44 0xff 0x0 0x45 0xff 0x0 0x46 0x47 0xa
"AB�.CD�.E�.FG."
The read (correctly) identifies three characters that have Mark instead of Space as the parity bit.
Each character with "parity error" is preceded by bytes of 0xFF 0x00 (i.e. a total of three bytes).
Note that when the actual received datum is 0xFF (with no parity error), termios will expand that datum to two bytes of 0xFF 0xFF. So beware that when the next datum is 0x00, then this is not the error indication. IOW reading 0xFF 0xFF 0x00 converts to actual data 0xFF 0x00.
But when the actual received datum is 0xFF with a parity error, then read returns 0xFF 0x00 0xFF (i.e. there is no expansion combined with the error indication).

How do I wait for a keystroke interrupt with a syscall on Linux?

I want to receive an interrupt when the user presses a special keystroke like F1-12 in my program, which is written in nasm. I simply need to wait for a function keystroke at the start of my main function. I know that this is possible with the BIOS's int 16h, which returns a scancode. How can I do this under Linux?
The necessary code for this is rather complicated; I eventually figured out how to check for F1 in C with raw ioctl, read, and write. The translation to nasm should be straightforward if you're familiar with assembly and Linux syscalls.
It's not exactly what you want, in that it only checks for F1, not the rest of them. F1's sequence is 0x1b, 0x4f, 0x50. You can find other sequences with od -t x1 and pressing the key. For example, F2 is 0x1b, 0x4f, 0x51.
The basic idea is that we get the current terminal attributes, update them to be raw (cfmakeraw), and then set them back. The ioctl syscall is used for this.
On a terminal in raw mode, read() will get any character(s) the user has typed, unlike the "cooked" mode where the kernel does line-editing with backspace and control-u until the user submits the line by pressing enter or control-d (EOF).
#include <unistd.h>
#include <sys/ioctl.h>
#include <termios.h>
struct ktermios {
tcflag_t c_iflag;
tcflag_t c_oflag;
tcflag_t c_cflag;
tcflag_t c_lflag;
cc_t c_line;
cc_t c_cc[19];
};
int getch() {
unsigned char c;
read(0, &c, sizeof(c));
return c;
}
int main(int argc, char *argv[]) {
struct ktermios orig, new;
ioctl(0, TCGETS, &orig);
ioctl(0, TCGETS, &new); // or more simply new = orig;
// from cfmakeraw documentation
new.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
new.c_oflag &= ~OPOST;
new.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
new.c_cflag &= ~(CSIZE | PARENB);
new.c_cflag |= CS8;
ioctl(0, TCSETS, &new);
while (1) {
if (getch() == 0x1b && getch() == 0x4f && getch() == 0x50) {
break;
}
}
write(1, "Got F1!\n", 8);
ioctl(0, TCSETS, &orig); // restore original settings before exiting!
return 0;
}
I based this on this answer, which was very helpful.

serial port not responding

I wrote a program that communicate with serial port, using termios, this program will read serial port in non-blocking mode and write response to serial port once it read data. If there is no data read from serial port, the program will do other thing, on next loop, the program read serial port again.
now the question is, after sometimes gone, maybe several minutes, or maybe several hours, the serial port don't respond to my program any more. Even I execute echo 'HB\n' > /dev/ttyUSB0(then the serial port should respond 'HACK'), it doesn't respond any more..
I even don't known when the serial port is 'dead', I don't have any clue.. it 'dead' untimed.
here is my configuration:
/// set local mode options
//tminfo.c_lflag |= ICANON;
tminfo.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
/// set control mode options
tminfo.c_cflag |= (CLOCAL | CREAD);
tminfo.c_cflag |= HUPCL;
// set hardware flow control
tminfo.c_cflag &= ~CRTSCTS;
// set how many bits in a character
tminfo.c_cflag &= ~CSIZE;
tminfo.c_cflag |= CS8;
// set parity mode (default to odd validation), this option (PARENB) will both enable input and output parity checking
tminfo.c_cflag &= ~PARENB; // we don't need prity checking now
/// set input mode options
// set input parity checking
tminfo.c_iflag &= ~INPCK;
tminfo.c_cflag &= ~CSTOPB;
/// set output mode options
tminfo.c_oflag &= ~OPOST;
tminfo.c_cc[VMIN] = 1;
tminfo.c_cc[VTIME] = 1;
/// set line speed, defaults to 38400bps, both for input and output
// this call will set both input and output speed
cfsetspeed(&tminfo, B38400);
It's hard to debug the serial in this situation. I really can't figure out what cause the serial port 'dead' on earth. I'm nearly crazy...
what the possible reason? Any help will be appreciated!
update:
when the serial port "dead", its configuration is:
speed 38400 baud; rows 0; columns 0; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>;
start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 1;
-parenb -parodd cs8 hupcl -cstopb cread clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff -iuclc -ixany -imaxbel
-iutf8
-opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
-isig -icanon -iexten -echo -echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
update 2
/proc/tty/driver/ar933x-uart
I have noticed that this tx and rx field value does not change during my program running, even if I write to my serial manually.
serinfo:1.0 driver revision:
0: uart:AR933X UART mmio:0x18020000 irq:11 tx:169 rx:0 RTS|DTR|CD
/proc/tty/driver/serial
serinfo:1.0 driver revision:
0: uart:unknown port:00000000 irq:0
1: uart:unknown port:00000000 irq:0
2: uart:unknown port:00000000 irq:0
3: uart:unknown port:00000000 irq:0
4: uart:unknown port:00000000 irq:0
5: uart:unknown port:00000000 irq:0
6: uart:unknown port:00000000 irq:0
7: uart:unknown port:00000000 irq:0
8: uart:unknown port:00000000 irq:0
9: uart:unknown port:00000000 irq:0
10: uart:unknown port:00000000 irq:0
11: uart:unknown port:00000000 irq:0
12: uart:unknown port:00000000 irq:0
13: uart:unknown port:00000000 irq:0
14: uart:unknown port:00000000 irq:0
15: uart:unknown port:00000000 irq:0
/proc/tty/driver/usbserial
usbserinfo:1.0 driver:2.0
0: module:pl2303 name:"pl2303" vendor:067b product:2303 num_ports:1 port:1 path:usb-ehci-platform-1
and, below is the more detailed code...
int Serial::openup(const char *devfile) {
if(-1 == (devfds = open(devfile, O_RDWR | O_NOCTTY ))) {
perror(strerror(errno));
return -1;
}
// set device file io mode to nonblock
//int oldflags = fcntl(devfds, F_GETFL);
//fcntl(devfds, F_SETFL, oldflags | O_NONBLOCK);
// get terminal's attributes
tcgetattr(devfds, &tminfo);
memset(&tminfo, 0, sizeof(struct termios));
/// set local mode options ///
//tminfo.c_lflag |= ICANON;
tminfo.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG | IEXTEN);
/// set control mode options ///
tminfo.c_cflag |= (CLOCAL | CREAD);
// disable hardware flow control
tminfo.c_cflag &= ~CRTSCTS;
// set how many bits in a character
tminfo.c_cflag &= ~CSIZE;
tminfo.c_cflag |= CS8;
// we don't need prity checking
tminfo.c_cflag &= ~PARENB;
tminfo.c_cflag &= ~CSTOPB;
/// set input mode options ///
// disable input parity checking, this
tminfo.c_iflag &= ~(INPCK | PARMRK | IGNBRK | BRKINT | ISTRIP
| INLCR | IGNCR | ICRNL | IXON);
/// set output mode options ///
//tminfo.c_oflag |= (OPOST | ONLCR);
tminfo.c_oflag &= ~OPOST; // ***
tminfo.c_cc[VMIN] = 0; // ***
tminfo.c_cc[VTIME] = 1; // ***
/// set line speed, defaults to 38400bps, both for input and output ///
// this call will set both input and output speed
cfsetspeed(&tminfo, B38400);
if(-1 == tcsetattr(devfds, TCSANOW, &tminfo)) {
perror(strerror(errno));
return -1;
}
return 0;
}
int Serial::serve() {
char buffer[256] = {0};
/*
struct timeval timeo;
timeo.tv_sec = 0;
timeo.tv_usec = 2 * 1000;
select(0, NULL, NULL, NULL, &timeo);
*/
//print_trace("ready to read data from serial port.\n");
int read_count = 0;
if((read_count = read_line(devfds, buffer, 256))) {
print_trace("read line: %d bytes, %s\n", read_count, buffer);
if(0 == strncmp(buffer, "S", 1)) {
// do some operation
} else if(0 == strncmp(buffer, "N", 1)) {
// do some operation
}
} else {
//print_trace("read no data.\n");
}
// TODO: test only, for find out the reason of serial port 'dead' problem
tcflush(devfds, TCIFLUSH);
}
there is another function for other module to write to serial port
int Serial::write_to_zigbee_co(const char *msg) {
int write_count = 0;
int len = strlen(msg);
struct timeval timeo;
timeo.tv_sec = 0;
timeo.tv_usec = 20 * 1000;
select(0, NULL, NULL, NULL, &timeo);
tcflush(devfds, TCOFLUSH);
if(len == (write_count = write(devfds, msg, len))) {
} else {
tcflush(devfds, TCOFLUSH);
}
return write_count;
}
Serial ports do not just suddenly "die".
The typical reason for a suddenly "dead" or nonresponsive serial link is unwanted flow control. You seem to have HW flow-control disabled, but software flow-control has not been disabled and raw mode has not been properly configured.
Your initialization needs
tminfo.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXOFF);
Also clear the IEXTEN in c_lflag,
and clear the PARENB in c_cflag.
Consider using the cfmakeraw() function to simplify the initialization for raw mode.
Raw mode
cfmakeraw() sets the terminal to something like the "raw" mode of the old Version 7
terminal driver: input is available character by character, echoing is disabled, and
all special processing of terminal input and output characters is disabled. The
terminal attributes are set as follows:
termios_p->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
| INLCR | IGNCR | ICRNL | IXON);
termios_p->c_oflag &= ~OPOST;
termios_p->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
termios_p->c_cflag &= ~(CSIZE | PARENB);
termios_p->c_cflag |= CS8;
ADDENDUM
Your revised termios settings look okay.
The next step I would try would be to determine which side of the serial link is at fault. Is one end not receiving or is one end not transmitting?
You could try using the metrics maintained by the serial port drivers. If both sides are running Linux, then you should inspect the files in /proc/tty/drivers. The serial port divers will report the receive and transmit byte counts on each port. Compare the Rx and Tx counts before the test and then after the failure.
If you cannot get any stats from the CC2530 side, then a serial link monitor may be necessary. Beside a dedicated test instrument, you could make/set-up one using a PC with two serial ports. Connect Port A to the host and Port B to the CC2530, so that this PC is a "man in the middle". You would then have to write a program to retransmit the received data of Port A over to Port B, and Port B's RxD to Port A's TxD.
This data that is retransmitted (both channels) also has to be displayed or logged. The purpose is to determine which side of the serial link is failing. Once that has been established, then you have to figure out if it is a receive or transmit issue.
OR
you could post more of your code (the complete open(), initialization routines and read & write logic) for everyone to desk check it.
Addendum 2
The code you have posted has some issues.
Initialization code
// get terminal's attributes
tcgetattr(devfds, &tminfo);
memset(&tminfo, 0, sizeof(struct termios));
This code obtains the termios data, and then zeroes it out!
You need to remove the memset() statement.
Read code
if((read_count = read_line(devfds, buffer, 256))) {
The serial port has been initialized for non-canonical (aka raw) mode, but here is a read_line() which is a canonical input operation.
I don't know exactly what happens when you setup raw mode and try to read lines, but if the read operation ever hangs, I would not be surprised.
You need to evaluate the type of data that will be exchanged between these two devices over this serial link.
Is every message composed of ASCII text with each line terminated by a newline character?
If "yes", then you can use canonical mode and the read_line().
Otherwise you should use non-canonical mode and the read() syscall, and write code to parse the received data.
if((read_count = read_line(devfds, buffer, 256))) {
...
} else {
//print_trace("read no data.\n");
}
When read_line() returns an error (-1), this code will treat it as a good return, and try to process stale or garbage data in the receive buffer. If there have been any read errors they have been undetected and never reported.
tcflush(devfds, TCIFLUSH);
IMO you are misusing tcflush(). There may be some rare cases, but normally you should not throw away any data until you have actually parsed it and know that it is garbage data. You should delete this tcflush() statement.
Write code
select(0, NULL, NULL, NULL, &timeo);
Performing a time delay prior to the write is a questionable operation in userspace. In a multitasking environment with scheduling and preemption to disrupt actual execution time, userspace programs rarely need to add such a fixed delay to every write() syscall.
tcflush(devfds, TCOFLUSH);
Another questionable (mis)use of tcflush().
This should be removed.
if(len == (write_count = write(devfds, msg, len))) {
} else {
tcflush(devfds, TCOFLUSH);
}
Another questionable/improper use of tcflush().
This should be replaced with better recovery code. It's unlikely that a short write will occur; most likely to be returned is either the full write count or an error return (-1). You need to check for an error return (-1), and the errno variable. (You need to do this for other syscalls too, such as tcgetattr(). You need to read the man page of each syscall you use to learn what can be returned.)

Resources