I am setting up ports like this:
...
This is obviously highly suboptimal because it doesn't suspend waiting for chars.
In spite of this awareness you use and post this code?
I suspect that this "suboptimal" code that polls the system for data while wasting CPU cycles and consumes the process's time slice is part of the problem. You have not posted a complete and minimal example of the problem, and I have only been able to partially replicate the issue.
On a SBC that has two USARTs, I have a program that is generating "request" and "response" data on the serial ports. The generation program is:
#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); /* ignore modem controls */
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8; /* 8-bit characters */
tty.c_cflag &= ~PARENB; /* no parity bit */
tty.c_cflag &= ~CSTOPB; /* only need 1 stop bit */
tty.c_cflag &= ~CRTSCTS; /* no hardware flowcontrol */
/* setup for non-canonical mode */
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
tty.c_oflag &= ~OPOST;
/* fetch bytes as they become available */
tty.c_cc[VMIN] = 1;
tty.c_cc[VTIME] = 1;
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
printf("Error from tcsetattr: %s\n", strerror(errno));
return -1;
}
return 0;
}
int main(void)
{
char *masterport = "/dev/ttyS0";
char *slaveport = "/dev/ttyS2";
int mfd;
int sfd;
int wlen;
/* open request generator */
mfd = open(masterport, O_RDWR | O_NOCTTY | O_SYNC);
if (mfd < 0) {
printf("Error opening %s: %s\n", masterport, strerror(errno));
return -1;
}
/*baudrate 115200, 8 bits, no parity, 1 stop bit */
set_interface_attribs(mfd, B115200);
/* open response generator */
sfd = open(slaveport, O_RDWR | O_NOCTTY | O_SYNC);
if (sfd < 0) {
printf("Error opening %s: %s\n", slaveport, strerror(errno));
return -1;
}
/*baudrate 115200, 8 bits, no parity, 1 stop bit */
set_interface_attribs(sfd, B115200);
/* simple output loop */
do {
wlen = write(mfd, "ABCD", 4);
if (wlen != 4) {
printf("Error from write cmd: %d, %d\n", wlen, errno);
}
tcdrain(mfd); /* delay for output */
wlen = write(sfd, "xy", 2);
if (wlen != 2) {
printf("Error from write resp: %d, %d\n", wlen, errno);
}
tcdrain(sfd); /* delay for output */
} while (1);
}
Problem is I experience some kind of buffering because when two processes exchange data on a tight loop I often see a few requests together and then the corresponding answers
You do not clarify what you call a "tight loop", but the above program will generate the "response" 30 milliseconds after a "request" (as measured by a two-channel oscilloscope).
BTW the serial terminal interface is highly layered. Even without the overhead of the external bus used by USB, there is at least the termios buffer and the tty flip buffer, as well as a DMA buffer. See Linux serial drivers
Each USART of the SBC is connected to a FTDI USB-to-RS232 converter (which are part of an old quad-port converter). Note that the USB port speed is only USB 1.1. The host PC for the serial capture is 10-year old hardware running an old Ubuntu distro.
An attempt to replicate your results produced:
ABCD
x
y
A
BCD
xy
ABCD
xy
ABCD
xy
A
BCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABC
D
xy
ABCD
xy
ABCD
xy
ABC
D
xy
ABCD
xy
ABCD
xy
ABC
D
xy
ABCD
xy
ABCD
xy
ABC
D
xy
ABCD
xy
ABCD
xy
ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD
xyxyxyxyxyxyxyxyxyxyxyxyxy
ABCD
xy
ABCD
xy
AB
CD
xy
ABCD
xy
ABCD
xy
AB
CD
xy
ABCD
xy
ABCD
x
y
A
BCD
xy
ABCD
xy
ABCD
x
y
AB
CD
xy
ABCD
xy
ABCD
x
y
Only once (about 1.5 seconds after the capture program is started) is there a multi-write capture. (There's even a noticeable pause in output before this happens.) Otherwise every read/capture is of a partial or single/complete request/response.
Using a capture program that uses blocking I/O, the results are consistenly "perfect" for a 4-byte request message and 2-byte response message.
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
Tweaking the program by changing the VMIN=4 for requests and VMIN=2 for responses to VMIN=1 for everything, changes the quality of the captures slightly:
ABCD
xy
ABCD
x
ABCD
y
ABCD
xy
ABC
xy
D
x
ABCD
y
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABC
xy
D
x
ABCD
y
Although partial captures occur, there are never any multiple "messages" per read. The output is smooth and consistent, without any pause as with the nonblocking program.
The capture program that uses blocking reads is:
#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, int rlen)
{
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); /* ignore modem controls */
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8; /* 8-bit characters */
tty.c_cflag &= ~PARENB; /* no parity bit */
tty.c_cflag &= ~CSTOPB; /* only need 1 stop bit */
tty.c_cflag &= ~CRTSCTS; /* no hardware flowcontrol */
/* setup for non-canonical mode */
tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
tty.c_oflag &= ~OPOST;
/* fetch bytes as they become available */
tty.c_cc[VMIN] = rlen;
tty.c_cc[VTIME] = 1;
if (tcsetattr(fd, TCSANOW, &tty) != 0) {
printf("Error from tcsetattr: %s\n", strerror(errno));
return -1;
}
return 0;
}
int main(void)
{
char *masterport = "/dev/ttyUSB2";
char *slaveport = "/dev/ttyUSB3";
int mfd;
int sfd;
/* open request reader */
mfd = open(masterport, O_RDWR | O_NOCTTY | O_SYNC);
if (mfd < 0) {
printf("Error opening %s: %s\n", masterport, strerror(errno));
return -1;
}
/*baudrate 115200, 8 bits, no parity, 1 stop bit */
set_interface_attribs(mfd, B115200, 4);
/* open response reader */
sfd = open(slaveport, O_RDWR | O_NOCTTY | O_SYNC);
if (sfd < 0) {
printf("Error opening %s: %s\n", slaveport, strerror(errno));
return -1;
}
/*baudrate 115200, 8 bits, no parity, 1 stop bit */
set_interface_attribs(sfd, B115200, 2);
tcflush(mfd, TCIOFLUSH);
tcflush(sfd, TCIOFLUSH);
/* simple noncanonical input loop */
do {
unsigned char buffer[80];
int rdlen;
rdlen = read(mfd, buffer, sizeof(buffer) - 1);
if (rdlen > 0) {
buffer[rdlen] = 0;
printf("%s\n", buffer);
} else if (rdlen < 0) {
printf("Error from read: %d: %s\n", rdlen, strerror(errno));
} else { /* rdlen == 0 */
printf("Timeout from read\n");
}
rdlen = read(sfd, buffer, sizeof(buffer) - 1);
if (rdlen > 0) {
buffer[rdlen] = 0;
printf("%s\n", buffer);
} else if (rdlen < 0) {
printf("Error from read: %d: %s\n", rdlen, strerror(errno));
} else { /* rdlen == 0 */
printf("Timeout from read\n");
}
} while (1);
}
This is essentially a dual half-duplex capture on each serial terminal for a request-response dialog. An actual full-duplex dialog cannot be accurately captured/displayed.
These results using blocking reads would seem to contradict the other answers that USB-serial converters would buffer and packetize the serial data into unrecognizable byte segments.
Only when I use nonblocking reads do I encounter the "buffering" that you report.