我需要读取多个(至少2个)串行端口(当前通过USB连接的FT2232H模块上的两个端口)。
我正在使用它来监视串行连接,因此这两个端口的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 \000 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
),但得到类似的结果。
我应该如何确保执行正确的时间安排?
注意:我对精确的时间不是很感兴趣,但是应该保留顺序(即:我想避免在请求之前看到答案)。
答案 0 :(得分:1)
两个串行端口将具有缓冲-不能在应用程序级别确定单个字符的到达顺序。那将需要编写自己的驱动程序,或者可能将任何缓冲减少到1个字符-否则可能会溢出。
即使那样,它只有在拥有真正的UART并直接控制它并且没有硬件FIFO的情况下才能起作用。通过将虚拟UART实现为USB CDC / ACM类驱动程序,在任何情况下都不可能实现,因为实时UART事务会在主从USB传输中丢失,这与真正的UART的工作方式完全不同。除此之外,FT2232H具有内部缓冲,您无法控制它。
简而言之,由于多种因素,您无法在实现中的两个不同端口上实时获取各个字符的顺序,而大多数因素无法缓解。
您必须了解FT2232具有两个实际的UARTS和USB设备接口,它们表现为两个CDC / ACM设备。它具有可在UART和USB之间缓冲和交换数据的固件,并且USB交换由主机轮询-以其自身的有效时间,速率和顺序进行。数据以分组而不是单个字符的形式异步传输,并且不可能恢复任何单个字符的原始到达时间。您所知道的只是字符在单个端口上的到达顺序 -您无法确定端口在之间的到达顺序。甚至在主机操作系统设备驱动程序缓冲数据之前,所有这些。
可能需要一个硬件解决方案,使用一个在UART级别工作的微控制器,该时间戳将为时间戳并记录每个端口在两个端口上的每个字符的到达时间,然后将带有时间戳的日志数据传输到您的主机(也许通过USB),然后,您可以根据时间戳重建到达顺序。
答案 1 :(得分:1)
我认为您要尝试做的是,如果我正确理解了一种端口嗅探器来识别串行链路上交换的事务,那么对于USB到串行转换器和传统的OS来说是不可行的,除非您正在以低波特率运行。
USB端口始终会带来一定的延迟(可能是数十毫秒),并且您必须将操作系统的不可预测性放在首位。
由于有两个端口,您可以尝试运行两个单独的线程并为接收到的每个数据块加上时间戳。这可能有助于改善情况,但我不确定是否可以让您清楚地遵循顺序。
如果您拥有真实的(旧版)串行端口,并且操作系统负荷不高,则也许可以采取某种方式。
但是,如果您想要便宜的串行端口嗅探器,则可以尝试类似this solution的操作。如果您在端口上进行转发,那么您将始终知道来自何处的信息。当然,您需要有权访问通信的任何一端。
如果您没有那么奢侈的话,我想几乎可以通过任何一种微控制器来获得想要的东西。
编辑:另一个想法可能是使用双串口转USB转换器。由于两个端口都由同一芯片提供服务,因此我认为您可以按照其中的一个顺序进行操作。如果您想知道完整的代码片段,我可以访问this one,如果您想知道的话,下周可以测试。
答案 2 :(得分:1)
我正在这样设置端口:
...
这显然是次优的,因为它不会暂停等待字符。
尽管有这种认识,您仍然使用并发布了此代码?
我怀疑这个“次优” 代码会在浪费CPU周期的同时轮询系统中的数据,并消耗进程的时间片是问题的一部分。您尚未发布完整且最小的问题示例,而我只能部分复制该问题。
在具有两个USART的SBC上,我有一个程序在串行端口上生成“请求”和“响应”数据。生成程序为:
#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);
}
问题是我遇到某种缓冲,因为当两个进程在一个紧密的循环上交换数据时,我经常会看到一些请求,然后是相应的答案
您没有弄清您所说的“紧密循环” ,但上述程序将在“请求”后30毫秒(由两通道示波器测量)生成“响应” 。
顺便说一句,串行终端接口是高度分层的。即使没有USB使用的外部总线的开销,也至少有termios缓冲区和tty翻转缓冲区以及DMA缓冲区。参见Linux serial drivers
SBC的每个USART连接到FTDI USB到RS232转换器(这是旧的四端口转换器的一部分)。请注意,USB端口速度仅为USB 1.1。用于串行捕获的主机PC是运行着旧版Ubuntu发行版的10年硬件。
尝试复制您的结果产生了:
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秒)才可以进行多次写入捕获。 (在发生这种情况之前,甚至在输出中会有明显的暂停。)否则,每个读取/捕获都属于部分或单个/完整的请求/响应。
使用使用阻塞I / O的捕获程序,对于4字节的请求消息和2字节的响应消息,结果始终是“完美的”。
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
ABCD
xy
通过更改请求的VMIN = 4和响应的VMIN = 2来调整程序,以对所有内容进行更改,从而略微更改捕获的质量:
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
尽管会发生部分捕获,但每次读取都不会有任何多个“消息”。输出平稳且一致,与非阻塞程序一样,没有任何暂停。
使用阻止读取的捕获程序是:
#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);
}
从本质上讲,这是每个串行终端上用于请求-响应对话框的双半双工捕获。实际的全双工对话框无法准确捕获/显示。
使用阻塞读取的这些结果似乎与USB串行转换器将缓冲并将串行数据打包为无法识别的字节段的其他答案相矛盾。
仅当我使用非阻塞读取时,我才会遇到您报告的“缓冲” 。
答案 3 :(得分:0)
您滥用VMIN
和VTIME
c_cc
单元格。如果您在termios(3)
的基础上仔细阅读VMIN > 0 && VTIME > 0
手册页,驱动程序将不会将数据发送到应用程序直到检测到VTIME
持续时间超时< / strong>。在这种情况下,VTIME
参数是一个 intercharacter超时(但它会阻塞直到收到第一个字符)。我认为您误解了这种情况。它是在驱动程序中引入的,用于处理可变长度的数据包输入设备(例如鼠标或网络),这些设备可以按顺序传送多个数据包,以确保缓冲区与数据包的开头保持同步(同时处理数据包丢失)。但是该模式下的操作是无限期地等待第一个字符,然后等待多达VTIME
十分之一秒以查看是否收到另一个字符,一旦达到VMIN
计数,在这种情况下,驱动程序会缓冲字符并等待另一个超时。这是针对具有可变长度的数据包和标头的,通常将VMIN
设置为标头的大小,然后使用字符间超时来处理超时后丢失的字符。这不是您在问题中所说的。
要创建一个方案,在该方案中,您读取多个端口并在获得单个字符时立即接收它们,则必须使用VMIN == 1, VTIME == 0
,以便在接收到每个字符后立即获得它们。要接收第一个收到的消息,而与接收它的端口无关,您需要使用select(2)
系统调用,这将阻塞您,直到在几个端口之一上有可用的输入,然后查看哪个端口是的,然后对该端口执行read(2)
。如果您希望获得良好的时间戳记,请在从clock_gettime(2)
系统调用返回时立即执行select(2)
(您尚未read(2)
字符,但是您知道它已经存在,以后,一旦阅读,便可以将时间戳记与正确的字符和端口相关联。
正如我在您的问题中所看到的那样,您已经与termios(3)
进行了斗争,并且对自己想要的东西有所了解,请阅读select(2)
手册页并准备处理该问题的代码。如果您遇到麻烦,请在下面给我留言,以便为您编写一些代码。请记住:VMIN
是您要接收的最小字符数,而不是最大值(您在read(2)
的参数中输入的最大字符数),而VTIME
只是绝对超时, VMIN == 0
(但您可以在select(2)
中处理超时,比在驱动程序中处理更好)
这种错误很常见,我也通过了:)
我使用此处指示的方法开发了a simple example to monitor several tty lines(不一定是两个)。只是说,通过逐个字符读取字符并使用最佳时间粒度方法,它可以将树莓派2B +用作串行协议分析器。