如何在C或Python中实时读取多个串口
how to read multiple serial ports in realtime in C or Python
我需要读取多个(至少 2 个)串行端口(目前 FT2232H 模块上的两个端口通过 USB 连接)。
我用它来监控串行连接,所以这两个端口的 RX 并联连接到我需要监控的串行的 RX 和 TX。
设置与 this 非常相似。
我正在这样设置端口:
#define waitTime 0
int start_dev(const int speed, const char *dev) {
int fd = open(dev, O_RDWR | O_NOCTTY |O_NONBLOCK| O_NDELAY);
int isBlockingMode, parity = 0;
struct termios tty;
isBlockingMode = 0;
if (waitTime < 0 || waitTime > 255)
isBlockingMode = 1;
memset (&tty, 0, sizeof tty);
if (tcgetattr (fd, &tty) != 0) {
/* save current serial port settings */
printf("__LINE__ = %d, error %s\n", __LINE__, strerror(errno));
exit(1);
}
cfsetospeed (&tty, speed);
cfsetispeed (&tty, speed);
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; // 8-bit chars
// disable IGNBRK for mismatched speed tests; otherwise receive break
// as [=11=]0 chars
tty.c_iflag &= ~IGNBRK; // disable break processing
tty.c_lflag = 0; // no signaling chars, no echo,
// no canonical processing
tty.c_oflag = 0; // no remapping, no delays
tty.c_cc[VMIN] = (1 == isBlockingMode) ? 1 : 0; // read doesn't block
tty.c_cc[VTIME] = (1 == isBlockingMode) ? 0 : waitTime; // in unit of 100 milli-sec for set timeout value
tty.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl
tty.c_cflag |= (CLOCAL | CREAD); // ignore modem controls,
// enable reading
tty.c_cflag &= ~(PARENB | PARODD); // shut off parity
tty.c_cflag |= parity;
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CRTSCTS;
if (tcsetattr (fd, TCSANOW, &tty) != 0) {
printf("__LINE__ = %d, error %s\n", __LINE__, strerror(errno));
exit(1);
}
return fd;
}
...和目前我有这段代码可供阅读(我也尝试过select()
):
...
for (running=1; running;) {
for (int*p=devs; p<end; p++) {
char b[256];
int n = read(*p, b, sizeof(b));
if (n > 0) {
for (int i=0; i<n; i++) {
...
}
}
}
}
...
这显然不是最理想的,因为它不会挂起等待字符。
问题是我遇到了某种缓冲,因为当两个进程在紧密循环中交换数据时,我经常看到几个请求在一起,然后是相应的答案(1b6f
是请求,19
是空答案):
1b6f
19
1b6f
19
1b6f
19
1b6f
191919
1b6f1b6f1b6f
19191919
1b6f1b6f1b6f1b6f
1b6f1b6f1b6f
191919
我也尝试使用 python (pyserial
),但我得到了相似的结果。
我应该如何确保执行正确的时间安排?
注意:我对精确时间不是很感兴趣,但是顺序应该保留(即:我想避免在请求之前看到答案)。
这两个串口会有缓冲-各个字符到达的顺序无法在应用程序级别确定。这可能需要编写您自己的驱动程序或将任何缓冲减少到 1 个字符 - 有超限的风险。
即便如此,它也只能在你有一个真正的 UART 并直接控制它并且它没有硬件 FIFO 的情况下才能工作。使用作为 USB CDC/ACM class 驱动程序实现的虚拟 UART 在任何情况下都是不可能的,因为实时 UART 事务在主从 USB 传输中丢失,这与真正的 UART 工作。除此之外,FT2232H 具有您无法控制的内部缓冲。
简而言之,由于多种因素,您无法在您的实施中的两个单独端口上对单个字符进行实时排序,其中大部分无法缓解。
您必须了解 FT2232 具有两个真正的 UARTS 和 USB 设备接口,呈现为两个 CDC/ACM 设备。它具有在 UART 和 USB 之间缓冲和交换数据的固件,USB 交换由主机轮询 - 以其自己的最佳时间、速率和顺序。数据以数据包而非单个字符的形式异步传输,并且无法恢复任何单个字符的原始到达时间。您所知道的只是单个端口上字符到达的顺序 - 您无法确定 端口之间的到达顺序。所有这一切都发生在数据被主机 OS 设备驱动程序缓冲之前。
可能需要一个硬件解决方案,使用一个微控制器,在 UART 级别工作,将时间戳记并记录每个字符到达两个端口的每一个,然后将时间戳记的日志数据传输到您的主机(可能通过 USB ) 然后你可以从时间戳重建到达顺序。
在我看来,如果我正确理解了一种端口嗅探器来识别在串口上交换的事务,我认为你正在尝试做的事情 link 对于 USB 转串口转换器是不可行的和传统的 OS,除非您 运行 的波特率较慢。
USB 端口总是会引入一定的延迟(可能是几十毫秒),您必须将 OS 的不可预测性放在首位。
由于您有两个端口,您可以尝试 运行 两个单独的线程并为接收到的每个数据块添加时间戳。这可能有助于改进,但我不确定它是否能让您清楚地遵循顺序。
如果你有真正的(传统的)串行端口,并且负载不是很大 OS 也许你可以以某种方式做到这一点。
但是如果你想要一个便宜的串口嗅探器,你可以试试 this solution。如果你在你的端口上进行转发,你会随时知道什么来自哪里。当然,你需要能够访问任何一方的通信。
如果你没有那么奢侈,我想用几乎任何一种微控制器都可以很容易地得到你想要的东西。
编辑: 另一个想法可能是使用双串行端口到 USB 转换器。由于两个端口都由同一个芯片提供服务,因此我认为您很可能可以按照其中一个的顺序进行操作。我可以访问 this one 如果你 post 你的代码的完整工作片段我可以在下周测试它,如果你想知道的话。
您对 VMIN
和 VTIME
c_cc
单元格的使用不当。如果您仔细阅读 termios(3)
手册页,在 VMIN > 0 && VTIME > 0
的基础上,driver 不会将数据发送到应用程序 ,直到 [=11] 超时=] 检测到持续时间。在这种情况下,VTIME
参数是一个 字符间超时 (但它会阻塞,直到它收到第一个字符)。我认为你误解了那个案子。这是在 driver 中引入的,用于处理可变长度的数据包输入设备,例如鼠标或网络,它们可以按顺序传送多个数据包,以确保缓冲区与数据包的开始同步(在处理数据包时损失)。但是该模式下的操作是无限期地等待第一个字符,然后最多等待 VTIME
十分之一秒以查看是否收到另一个字符, 一旦 VMIN
计数达到,在这种情况下,driver 缓冲字符并等待另一个超时。这是为可变长度的数据包和 header 制作的,您通常将 VMIN
设置为 header 的大小,然后使用字符间超时来处理超时后丢失的字符。这不是你在问题中所说的。
要创建一个读取多个端口并在获取字符后立即接收单个字符的场景,您必须使用 VMIN == 1, VTIME == 0
这样您将在收到每个字符后立即获取每个字符。并且要接收您获得的第一个,与您从哪个端口接收它无关,您需要使用 select(2)
系统调用,这将阻止您,直到几个端口之一上有一些输入可用,然后查看哪个端口它是,然后对该端口执行 read(2)
。如果你想要好的时间戳,一旦你从 select(2)
系统调用 return 就做一个 clock_gettime(2)
(你还没有 read(2)
字符,但你知道它有没有,等你看完了,你就可以把时间戳关联到正确的字符和端口上了。
正如我在你的问题中看到的那样,你已经与 termios(3)
打过架并且你知道你想要什么,阅读 select(2)
手册页并准备代码来处理它。如果您 运行 遇到麻烦,请在下方给我留言,我会为您编写一些代码。请记住:VMIN
是您要接收的最小字符数,而不是最大值(您将其放入 read(2)
的参数中的最大值),并且 VTIME
只是一个绝对超时,当 VMIN == 0
(但你可以在 select(2)
中处理超时,比在 driver 中更好)
这种错误很常见,我也遇到过:)
编辑
我已经使用这里指出的方法开发了 a simple example to monitor several tty lines(不一定是两个)。只是说它允许 raspberry pi 2B+ 用作串行协议分析器,通过逐字符读取并使用最佳时间粒度方法。
I am setting up ports like this:
...
This is obviously highly suboptimal because it doesn't suspend waiting for chars.
尽管有这种意识,您还是使用 post 此代码?
我怀疑这个 "suboptimal" 代码在浪费 CPU 周期和消耗进程时间片的同时轮询系统数据是问题的一部分。您还没有 post 完整和最小的问题示例,我只能部分复制该问题。
在有两个 USART 的 SBC 上,我有一个程序在串行端口上生成 "request" 和 "response" 数据。生成程序为:
#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
你没有说明你所谓的"tight loop",但是上面的程序会在"request"之后30毫秒生成"response" (由双通道示波器测量)。
顺便说一句,串行终端接口是高度分层的。即使没有USB使用的外部总线的开销,也至少有termios buffer和tty flip buffer,还有一个DMA buffer。参见 Linux serial drivers
SBC 的每个 USART 都连接到一个 FTDI USB 到 RS232 转换器(它是旧四端口转换器的一部分)。请注意,USB 端口速度仅为 USB 1.1。用于串行捕获的主机 PC 是 10 年的旧硬件 运行 一个旧的 Ubuntu 发行版。
尝试复制您的结果:
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
只有一次(抓包程序启动后1.5秒左右)是多写抓包。 (在发生这种情况之前,输出中甚至会有一个明显的暂停。)否则每个 read/capture 都是部分的或 single/complete request/response。
使用使用阻塞I/O的捕获程序,对于4字节的请求消息和2字节的响应消息,结果一致"perfect"。
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
通过更改请求的 VMIN=4 和响应的 VMIN=2 对所有内容更改 VMIN=1 来调整程序,稍微改变捕获的质量:
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
虽然会发生部分捕获,但每次读取都不会出现多次 "messages"。输出流畅且一致,与非阻塞程序一样没有任何停顿。
使用阻塞读取的捕获程序是:
#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);
}
这本质上是每个串行终端上的双半双工捕获,用于请求-响应对话。实际的全双工对话不能准确 captured/displayed。
这些使用阻塞读取的结果似乎与 USB 串行转换器将串行数据缓冲并将其打包成无法识别的字节段的其他答案相矛盾。
只有当我使用非阻塞读取时,我才会遇到您报告的"buffering"。
我需要读取多个(至少 2 个)串行端口(目前 FT2232H 模块上的两个端口通过 USB 连接)。
我用它来监控串行连接,所以这两个端口的 RX 并联连接到我需要监控的串行的 RX 和 TX。
设置与 this 非常相似。
我正在这样设置端口:
#define waitTime 0
int start_dev(const int speed, const char *dev) {
int fd = open(dev, O_RDWR | O_NOCTTY |O_NONBLOCK| O_NDELAY);
int isBlockingMode, parity = 0;
struct termios tty;
isBlockingMode = 0;
if (waitTime < 0 || waitTime > 255)
isBlockingMode = 1;
memset (&tty, 0, sizeof tty);
if (tcgetattr (fd, &tty) != 0) {
/* save current serial port settings */
printf("__LINE__ = %d, error %s\n", __LINE__, strerror(errno));
exit(1);
}
cfsetospeed (&tty, speed);
cfsetispeed (&tty, speed);
tty.c_cflag = (tty.c_cflag & ~CSIZE) | CS8; // 8-bit chars
// disable IGNBRK for mismatched speed tests; otherwise receive break
// as [=11=]0 chars
tty.c_iflag &= ~IGNBRK; // disable break processing
tty.c_lflag = 0; // no signaling chars, no echo,
// no canonical processing
tty.c_oflag = 0; // no remapping, no delays
tty.c_cc[VMIN] = (1 == isBlockingMode) ? 1 : 0; // read doesn't block
tty.c_cc[VTIME] = (1 == isBlockingMode) ? 0 : waitTime; // in unit of 100 milli-sec for set timeout value
tty.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl
tty.c_cflag |= (CLOCAL | CREAD); // ignore modem controls,
// enable reading
tty.c_cflag &= ~(PARENB | PARODD); // shut off parity
tty.c_cflag |= parity;
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CRTSCTS;
if (tcsetattr (fd, TCSANOW, &tty) != 0) {
printf("__LINE__ = %d, error %s\n", __LINE__, strerror(errno));
exit(1);
}
return fd;
}
...和目前我有这段代码可供阅读(我也尝试过select()
):
...
for (running=1; running;) {
for (int*p=devs; p<end; p++) {
char b[256];
int n = read(*p, b, sizeof(b));
if (n > 0) {
for (int i=0; i<n; i++) {
...
}
}
}
}
...
这显然不是最理想的,因为它不会挂起等待字符。
问题是我遇到了某种缓冲,因为当两个进程在紧密循环中交换数据时,我经常看到几个请求在一起,然后是相应的答案(1b6f
是请求,19
是空答案):
1b6f
19
1b6f
19
1b6f
19
1b6f
191919
1b6f1b6f1b6f
19191919
1b6f1b6f1b6f1b6f
1b6f1b6f1b6f
191919
我也尝试使用 python (pyserial
),但我得到了相似的结果。
我应该如何确保执行正确的时间安排?
注意:我对精确时间不是很感兴趣,但是顺序应该保留(即:我想避免在请求之前看到答案)。
这两个串口会有缓冲-各个字符到达的顺序无法在应用程序级别确定。这可能需要编写您自己的驱动程序或将任何缓冲减少到 1 个字符 - 有超限的风险。
即便如此,它也只能在你有一个真正的 UART 并直接控制它并且它没有硬件 FIFO 的情况下才能工作。使用作为 USB CDC/ACM class 驱动程序实现的虚拟 UART 在任何情况下都是不可能的,因为实时 UART 事务在主从 USB 传输中丢失,这与真正的 UART 工作。除此之外,FT2232H 具有您无法控制的内部缓冲。
简而言之,由于多种因素,您无法在您的实施中的两个单独端口上对单个字符进行实时排序,其中大部分无法缓解。
您必须了解 FT2232 具有两个真正的 UARTS 和 USB 设备接口,呈现为两个 CDC/ACM 设备。它具有在 UART 和 USB 之间缓冲和交换数据的固件,USB 交换由主机轮询 - 以其自己的最佳时间、速率和顺序。数据以数据包而非单个字符的形式异步传输,并且无法恢复任何单个字符的原始到达时间。您所知道的只是单个端口上字符到达的顺序 - 您无法确定 端口之间的到达顺序。所有这一切都发生在数据被主机 OS 设备驱动程序缓冲之前。
可能需要一个硬件解决方案,使用一个微控制器,在 UART 级别工作,将时间戳记并记录每个字符到达两个端口的每一个,然后将时间戳记的日志数据传输到您的主机(可能通过 USB ) 然后你可以从时间戳重建到达顺序。
在我看来,如果我正确理解了一种端口嗅探器来识别在串口上交换的事务,我认为你正在尝试做的事情 link 对于 USB 转串口转换器是不可行的和传统的 OS,除非您 运行 的波特率较慢。
USB 端口总是会引入一定的延迟(可能是几十毫秒),您必须将 OS 的不可预测性放在首位。
由于您有两个端口,您可以尝试 运行 两个单独的线程并为接收到的每个数据块添加时间戳。这可能有助于改进,但我不确定它是否能让您清楚地遵循顺序。
如果你有真正的(传统的)串行端口,并且负载不是很大 OS 也许你可以以某种方式做到这一点。
但是如果你想要一个便宜的串口嗅探器,你可以试试 this solution。如果你在你的端口上进行转发,你会随时知道什么来自哪里。当然,你需要能够访问任何一方的通信。
如果你没有那么奢侈,我想用几乎任何一种微控制器都可以很容易地得到你想要的东西。
编辑: 另一个想法可能是使用双串行端口到 USB 转换器。由于两个端口都由同一个芯片提供服务,因此我认为您很可能可以按照其中一个的顺序进行操作。我可以访问 this one 如果你 post 你的代码的完整工作片段我可以在下周测试它,如果你想知道的话。
您对 VMIN
和 VTIME
c_cc
单元格的使用不当。如果您仔细阅读 termios(3)
手册页,在 VMIN > 0 && VTIME > 0
的基础上,driver 不会将数据发送到应用程序 ,直到 [=11] 超时=] 检测到持续时间。在这种情况下,VTIME
参数是一个 字符间超时 (但它会阻塞,直到它收到第一个字符)。我认为你误解了那个案子。这是在 driver 中引入的,用于处理可变长度的数据包输入设备,例如鼠标或网络,它们可以按顺序传送多个数据包,以确保缓冲区与数据包的开始同步(在处理数据包时损失)。但是该模式下的操作是无限期地等待第一个字符,然后最多等待 VTIME
十分之一秒以查看是否收到另一个字符, 一旦 VMIN
计数达到,在这种情况下,driver 缓冲字符并等待另一个超时。这是为可变长度的数据包和 header 制作的,您通常将 VMIN
设置为 header 的大小,然后使用字符间超时来处理超时后丢失的字符。这不是你在问题中所说的。
要创建一个读取多个端口并在获取字符后立即接收单个字符的场景,您必须使用 VMIN == 1, VTIME == 0
这样您将在收到每个字符后立即获取每个字符。并且要接收您获得的第一个,与您从哪个端口接收它无关,您需要使用 select(2)
系统调用,这将阻止您,直到几个端口之一上有一些输入可用,然后查看哪个端口它是,然后对该端口执行 read(2)
。如果你想要好的时间戳,一旦你从 select(2)
系统调用 return 就做一个 clock_gettime(2)
(你还没有 read(2)
字符,但你知道它有没有,等你看完了,你就可以把时间戳关联到正确的字符和端口上了。
正如我在你的问题中看到的那样,你已经与 termios(3)
打过架并且你知道你想要什么,阅读 select(2)
手册页并准备代码来处理它。如果您 运行 遇到麻烦,请在下方给我留言,我会为您编写一些代码。请记住:VMIN
是您要接收的最小字符数,而不是最大值(您将其放入 read(2)
的参数中的最大值),并且 VTIME
只是一个绝对超时,当 VMIN == 0
(但你可以在 select(2)
中处理超时,比在 driver 中更好)
这种错误很常见,我也遇到过:)
编辑
我已经使用这里指出的方法开发了 a simple example to monitor several tty lines(不一定是两个)。只是说它允许 raspberry pi 2B+ 用作串行协议分析器,通过逐字符读取并使用最佳时间粒度方法。
I am setting up ports like this:
...
This is obviously highly suboptimal because it doesn't suspend waiting for chars.
尽管有这种意识,您还是使用 post 此代码?
我怀疑这个 "suboptimal" 代码在浪费 CPU 周期和消耗进程时间片的同时轮询系统数据是问题的一部分。您还没有 post 完整和最小的问题示例,我只能部分复制该问题。
在有两个 USART 的 SBC 上,我有一个程序在串行端口上生成 "request" 和 "response" 数据。生成程序为:
#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
你没有说明你所谓的"tight loop",但是上面的程序会在"request"之后30毫秒生成"response" (由双通道示波器测量)。
顺便说一句,串行终端接口是高度分层的。即使没有USB使用的外部总线的开销,也至少有termios buffer和tty flip buffer,还有一个DMA buffer。参见 Linux serial drivers
SBC 的每个 USART 都连接到一个 FTDI USB 到 RS232 转换器(它是旧四端口转换器的一部分)。请注意,USB 端口速度仅为 USB 1.1。用于串行捕获的主机 PC 是 10 年的旧硬件 运行 一个旧的 Ubuntu 发行版。
尝试复制您的结果:
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
只有一次(抓包程序启动后1.5秒左右)是多写抓包。 (在发生这种情况之前,输出中甚至会有一个明显的暂停。)否则每个 read/capture 都是部分的或 single/complete request/response。
使用使用阻塞I/O的捕获程序,对于4字节的请求消息和2字节的响应消息,结果一致"perfect"。
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
通过更改请求的 VMIN=4 和响应的 VMIN=2 对所有内容更改 VMIN=1 来调整程序,稍微改变捕获的质量:
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
虽然会发生部分捕获,但每次读取都不会出现多次 "messages"。输出流畅且一致,与非阻塞程序一样没有任何停顿。
使用阻塞读取的捕获程序是:
#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);
}
这本质上是每个串行终端上的双半双工捕获,用于请求-响应对话。实际的全双工对话不能准确 captured/displayed。
这些使用阻塞读取的结果似乎与 USB 串行转换器将串行数据缓冲并将其打包成无法识别的字节段的其他答案相矛盾。
只有当我使用非阻塞读取时,我才会遇到您报告的"buffering"。