可靠地以56K逐字节读取Linux串行数据 [英] Reading serial data in linux byte-for-byte at 56K reliably

查看:105
本文介绍了可靠地以56K逐字节读取Linux串行数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to create a function with the most minimal delay possible that checks to see if the serial port has data and if it does, it reads every single byte and prints each byte in hex format until no more bytes are available. If there is no data, the function must return right away.

这是我的代码:

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);

是否可以解决此问题?

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

This whole function (minus the serial port options section) will eventually run in an endless loop as I am constantly scanning my port for data then printing it. Then later I'll add some more functionality where I'll write data to the port at predefined times regardless of what data is received.

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

P.S. not sure if this is of help, but the target device I'm doing I/O with is custom hardware around an 8051 microcontroller and its serial fifo buffers are only 1 byte whereas the PC's buffers I think are 14 or 16 bytes.

推荐答案

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

If you know beforehand the times when you need to write to the device, you can use select() or poll() to wait for input until the next time you wish/intend to write.

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

A much more simple and robust approach — because your reads and writes are not in a defined sequence, and your hardware is full duplex — is to use separate threads for reading and writing. Basically, you use blocking reads and writes (c_cc[VMIN] = 1, c_cc[VTIME] = 0 for reading, O_NONBLOCK not among file open flags). You should allow larger buffers, though; reads will return all that have been received thus far, but with those settings wake up the reader whenever there is at least one char received. For writing, I do recommend you do a tcdrain(fd); after each write that completes a command/message to the device, to ensure it is sent on the wire by the kernel. (Do remember that writes to the serial port can be short; you need a write loop.)

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

In all cases, the kernel on the host side will cache data sent and received. Depending on the hardware and driver, even a blocking write() may return earlier than when all of the data is actually on the wire. The hardware and the kernel driver, not the host software, is responsible for correct timing of the serial data.

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

Using one-byte buffer on the host side will not affect the microcontroller at all, you'll just do more syscalls than is necessary, wasting CPU resources and possibly slow down your program a bit. At 57600 baud, with 8 data bits, no parity, 1 stop bit, and the implicit start bit, the actual data rate is 46080 bits/second (±5% typically allowed), or 5760 bytes per second. The microcontroller will always have about 1 s /5760 ≃ 0.0001736 seconds, or over 173 microseconds, to process each incoming byte. (I'd design my firmware to not allow higher priority interrupts etc. to delay processing for more than 100 microseconds or so even in the worst case, to ensure no chars are dropped. If you receive the chars in an interrupt handler, I'd use a small circular buffer, and an indicator character, \r or \n, so that if such character is received, a flag is raised for the main program to notice that a new complete command has been received. The circular buffer should be long enough to hold two full commands, or more if some commands may take longer to parse/process/handle.)

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

If the host operating system wakes up the process 1 ms after of receiving the first character, additional four or five characters have arrived in the mean time. Because this latency can be much higher on some systems, I'd use a much larger buffer, say up to 256 chars, to avoid doing superfluous syscalls when the kernel is for some reason delayed in waking up the reader thread. Yes, it will often read just 1 char, and that is fine; but when the system is overloaded, you don't want to add to that load by doing hundreds of superfluous syscalls when one would suffice.

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

Remember, the termios interface, with VMIN=1, VTIME=0, will cause the blocking read to be woken up as soon as possible, whenever even a single character is received. It is just that you cannot ensure your program is constantly running, unless you waste about 100% of CPU power by spinning in place (and if you do, nobody will want to run your program). Depending on the system, there may be a delay in waking up the blocking read, during which time more data may be received, so using a larger read() buffer is definitely sensible.

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

Similarly, you can safely use as large writes as you want (up to a limit of about 2 GiB), although most serial drivers can return short counts, so you'll need a loop there anyway. The tcdrain(fd) on the serial port descriptor will block until all written data has been actually transmitted, so you'll probably want to use it. (If you do not, you can just write more data; the kernel driver will take care of the details, and not reorder/mess data up.)

使用两个线程,一种用于阅读,一种用于写作,听起来可能令人生畏/奇怪,但这实际上是实现强大通信的最简单方法。您甚至可以使用 pthread_setcancelstate() pthread_setcanceltype () 以及可选的 pthread_testcancel() ,编写线程函数,以便您可以简单地取消线程(使用 pthread_cancel() )来阻止它们,即使它们具有关键部分(例如,将接收到的消息添加到受互斥锁保护的队列中)。

Using two threads, one for reading, one for writing, may sound daunting/odd, but it is actually the simplest way of achieving robust communications. You can even use pthread_setcancelstate(), pthread_setcanceltype() and optionally pthread_testcancel(), to write the thread functions so that you can simply cancel the threads (using pthread_cancel()) to stop them, even if they have critical sections (like adding a received message to some queue protected by a mutex).

这篇关于可靠地以56K逐字节读取Linux串行数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆