c - 可靠地以56K逐字节读取linux中的串行数据

标签 c linux serial-port timeout buffer

我正在尝试创建一个具有最小延迟的函数,以检查串行端口是否有数据,如果有,它将读取每个字节并以十六进制格式打印每个字节,直到没有更多字节可用为止。如果没有数据,该函数必须立即返回。

这是我的代码:

int fd=open("/dev/ttyS0", O_RDWR | O_NOCTTY | O_SYNC);  

// Trying to set correct options here
struct termios o;
tcgetattr(fd,&o);
cfsetispeed(&o,57600);
cfsetospeed(&o,57600);
/* 8 bits, no parity, 1 stop bit */
o.c_cflag &= ~PARENB;o.c_cflag &= ~CSTOPB;o.c_cflag &= ~CSIZE;o.c_cflag |= CS8;
/* no hardware flow control */
o.c_cflag &= ~CRTSCTS;
/* enable receiver, ignore status lines */
o.c_cflag |= CREAD | CLOCAL;
/* disable input/output flow control, disable restart chars */
o.c_iflag &= ~(IXON | IXOFF | IXANY);
/* disable canonical input, disable echo, disable visually erase chars, disable terminal-generated signals */
o.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
/* disable output processing */
o.c_oflag &= ~OPOST;
o.c_cc[VMIN] = 0; //to prevent delay in read();
o.c_cc[VTIME] = 0;
tcsetattr(fd, TCSANOW, &o);
tcflush(fd, TCIFLUSH);

char sp[1]; //hold 1 byte
int bytes=read(fd,sp,1); //Good news: this function doesn't lock
if (bytes > 0){
    //this is never reached even if a byte is 
    //present on the serial line. why?
    printf("Read: ");
    while(bytes > 0){
        printf("%X ",sp[0]);
        bytes=read(fd,sp,1);
    }
}
fclose(fd);


有没有办法来解决这个问题?

整个功能(减去串行端口选项部分)最终将无休止地运行,因为我不断扫描端口以获取数据,然后进行打印。然后,稍后我将添加更多功能,无论接收到什么数据,都将在预定义的时间将数据写入端口。

附言不确定这是否有帮助,但是我正在使用I / O的目标设备是8051微控制器周围的定制硬件,其串行fifo缓冲区仅为1字节,而我认为PC的缓冲区为14或16字节。

最佳答案

如果事先知道需要写入设备的时间,则可以使用select()poll()等待输入,直到下次您希望/打算进行写入。

一种更简单,更健壮的方法(因为您的读写未按定义的顺序进行,并且您的硬件是全双工的),因此,请使用单独的线程进行读写。基本上,您使用阻止读取和写入(c_cc[VMIN] = 1c_cc[VTIME] = 0进行读取,O_NONBLOCK不在文件打开标志中)。不过,您应该允许使用更大的缓冲区。读取将返回到目前为止已接收的所有内容,但是使用这些设置,只要至少接收到一个字符,就会唤醒读取器。为了进行写入,我建议您在每次写入后对设备完成命令/消息都执行一个tcdrain(fd);,以确保它由内核在线发送。 (请记住,对串行端口的写操作可能很短;您需要一个写循环。)

在所有情况下,主机端的内核都会缓存发送和接收的数据。根据硬件和驱动程序的不同,甚至阻塞的write()可能比实际连接所有数据时更早返回。硬件和内核驱动程序而非主机软件负责串行数据的正确时序。

在主机端使用一个字节的缓冲区根本不会影响微控制器,您只会执行过多的系统调用,从而浪费CPU资源,并可能会降低程序速度。在57600波特下,具有8个数据位,无奇偶校验,1个停止位和隐式起始位,实际数据速率为46080位/秒(通常允许±5%)或每秒5760字节。微控制器将始终有大约1 s / 5760≃0.0001736秒,或超过173微秒,以处理每个输入字节。 (我将固件设计为不允许更高优先级的中断等。即使在最坏的情况下,也要延迟超过100微秒的处理时间,以确保不会丢失任何字符。如果您在中断处理程序中接收到这些字符,我将d使用一个小的循环缓冲区和一个指示符\r\n,这样,如果接收到此字符,则为主程序引发一个标志,以通知已接收到一个新的完整命令。应该足够长以容纳两个完整的命令,或者如果某些命令可能需要更长的时间来解析/处理/处理,则更长。)

如果主机操作系统在接收到第一个字符后1 ms唤醒了进程,则在此同时还有四个或五个字符到达。因为此延迟在某些系统上可能更高,所以我将使用更大的缓冲区(例如最多256个字符),以避免在内核由于某种原因而延迟唤醒阅读器线程时进行多余的syscall。是的,它通常只读取1个字符,这很好。但是,当系统过载时,您不想在需要的时候执行数百个多余的syscall来增加负载。

请记住,带有VMIN=1, VTIME=0termios接口,即使接收到单个字符,也会尽快唤醒阻塞读取。只是不能确保程序一直运行,除非您通过旋转就浪费了大约100%的CPU能力(如果这样做,则没人会运行程序)。根据系统的不同,唤醒阻塞读取可能会有延迟,在此期间可能会接收更多数据,因此使用较大的read()缓冲区绝对是明智的。

同样,尽管大多数串行驱动程序可以返回短计数,但您可以安全地使用所需的大写操作(最多2 GiB的限制),因此无论如何您都需要一个循环。串行端口描述符上的tcdrain(fd)将阻塞,直到实际写入所有写入的数据为止,因此您可能要使用它。 (如果不这样做,则可以只写更多数据;内核驱动程序将处理细节,而不会重新排序/弄乱数据。)

使用两个线程,一个用于读取,一个用于写入,听起来可能令人生畏/奇特,但这实际上是实现可靠通信的最简单方法。您甚至可以使用pthread_setcancelstate()pthread_setcanceltype()和可选的pthread_testcancel()编写线程函数,以便即使它们具有关键部分(例如添加一个或多个),也可以简单地取消线程(使用pthread_cancel())以停止它们。收到的邮件发送到受互斥锁保护的队列中)。

关于c - 可靠地以56K逐字节读取linux中的串行数据,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51828498/

相关文章:

c - 错误: Expression must have a constant value in keil

c - 如何在 Linux 源代码中包含用户级 C 程序以与 Linux 内核一起编译?

c++ - pcre 不能支持多个子组

c++ - 在 ubuntu 上编译 lsnes 模拟器时出错

linux - 抓屏.py Python win32api

c - 如何在C中打印如下图案?

c - 套接字错误 : connection refused

macos - 在 osx 上嗅探 usb 串行通信的方法

c++ - 使用 Windows USB 虚拟 Com 端口识别断开连接事件

linux - 让两个 Linux(虚拟)盒子通过串口通话