线路缓冲串行输入

时间:2012-04-19 06:34:10

标签: python io serial-port

我有一个串口设备,我正试图从中读取输入。我发送了一个字符串“ID \ r \ n”,它返回“ID XX \ r”(其中\ r是ASCII回车,十六进制0x0d)。

由于不再支持serial.readline上的eol选项,我使用TextIOWrapper从串口读取并一次返回一行。

我的问题是,它不会在看到回车后立即返回我的字符串,而是等到我打开串口时设置的超时时间的两倍。我希望它在读取整行后立即返回字符串,因为我可能有数百个命令发送到设备,我不想每次都等待超时。如果我将超时设置为0,那么我根本就没有输出(可能是因为我的脚本在设备有机会输出任何内容之前停止等待),如果我将超时设置为None,脚本将永远阻塞。

这是一个简单的测试脚本:

import serial
import io
import time

ser = serial.Serial("/dev/ttyUSB0", baudrate=9600,
                    bytesize=8, parity='N', stopbits=1,
                    xonxoff=0, rtscts=1, timeout=5)

sio = io.TextIOWrapper(io.BufferedRWPair(ser, ser),
                       newline=None)


sio.write(unicode("ID\r"))
sio.flush()

print "reading..."

x = sio.readline()

print len(x)
print x

脚本从“读取”开始总是需要10秒钟,直到它打印出从串口读取的“ID XX”字符串。

我确定设备正在输出回车,因为我使用strace来观察读数:

select(4, [3], [], [], {5, 0})          = 1 (in [3], left {4, 991704})
read(3, "I", 8192)                      = 1
select(4, [3], [], [], {5, 0})          = 1 (in [3], left {4, 999267})
read(3, "D", 8191)                      = 1
select(4, [3], [], [], {5, 0})          = 1 (in [3], left {4, 999420})
read(3, " ", 8190)                      = 1
select(4, [3], [], [], {5, 0})          = 1 (in [3], left {4, 999321})
read(3, "X", 8189)                      = 1
select(4, [3], [], [], {5, 0})          = 1 (in [3], left {4, 999355})
read(3, "X", 8188)                      = 1
select(4, [3], [], [], {5, 0})          = 1 (in [3], left {4, 999171})
read(3, "\r", 8187)                     = 1
select(4, [3], [], [], {5, 0})          = 0 (Timeout)
select(4, [3], [], [], {5, 0})          = 0 (Timeout)

您可以看到2个select()超时,它们提供10秒延迟,但您也可以清楚地看到正在读取回车符。我已经尝试将newline参数设置为'None'和''(它应该自动允许\ r,\ n和\ r \ n),并设置为'\ r',但每次都有相同的结果。

我也尝试将BufferedRWPair()调用中的buffer_size设置为'1'以防止缓冲输入,但这没有任何区别。

知道我做错了吗?

如果我无法正常工作,我的下一步将是使用serial.read()一次读取一个字符并进行自己的行缓冲,但我想尝试“正确”先用textiowrapper的方式。

4 个答案:

答案 0 :(得分:6)

今天在此浪费了几个小时。事实证明,io.BufferedReader读取,直到它填充了缓冲区,然后将缓冲区传递给io.TextIOWrapper。默认缓冲区大小为8192,因此根据您的设备,这可能需要一段时间。

正确的示例代码应为:

# buffer size is 1 byte, so directly passed to TextIOWrapper
sio = io.TextIOWrapper(io.BufferedRWPair(ser, ser, 1), encoding='ascii')
print sio.readline()[:-1]

答案 1 :(得分:3)

警告:我在Mac上使用Python 3.4,因此你的里程可能会有所不同,但我相信TextIOWrapper向后移植到Python 2.7,Python 2.7(和其他操作系统)的情况基本上与我在下面描述的相同。

主要问题是io.TextIOWrapper本身使用缓冲机制,由未记录的_CHUNK_SIZE属性控制。这非常不愉快。所以你有两个选择:

  1. 尝试使用超时。这是pyserial文档页面上readline文档中暗示的内容。但是,如果您使用较大的值(如您所做),当没有足够的数据来填充TextIOWrapper的缓冲区时,您的代码将阻塞,直到达到超时。这就是你正在经历的事情(我没有追究为什么你必须等待超时值的两倍,但我认为这可以通过查看TextIOWrapper的实现来解决,最终与你的问题无关)。
  2. 第二种选择是将_CHUNK_SIZE更改为1.在您的情况下,只需添加行

    sio._CHUNK_SIZE = 1
    
    初始化sio后立即

    到您的代码。这可能会产生令人不快的影响,即TextIOWrapper本身的缓冲将被关闭(这用于输入的增量解码)。如果性能不是问题,这是最简单的解决方案。如果性能有问题,您可以设置较低的超时值,而不是触及_CHUNK_SIZE。但是,在这种情况下,准备从readline()获取一个空字符串(如果设备发送一个空行,它将通过' \ n',因此它可以与空字符串区分开来当读数用完所分配的时间时你会得到。)

  3. 您的代码还有另一个问题:当删除sio时,ser的close方法将被调用两次,这将导致程序即将完成时出现异常(至少这是如果我发生的情况)在我的电脑上试试你的代码)。您应该创建(似乎)serialSerial的实例并将它们传递给BufferedRWPair。

    我还创建了一个基于TextIOWrapper的包装类,我也可以发帖,如果有兴趣的话,我不想用一些额外的代码来回应垃圾,严格来说,不需要。

    PS:与此同时,我已经在Ubuntu上试验了代码。在我的Mac上,我没有看到需要将io.BufferedRWPair的缓冲区大小设置为1,在Ubuntu上我也必须这样做,除了将_CHUNK_SIZE设置为1。

答案 2 :(得分:0)

如果没有实际看到它,这将很难调试。但看看你是否可以使用我的tty模块。

http://code.google.com/p/pycopia/source/browse/trunk/aid/pycopia/tty.py

尝试使用SerialPort对象。我已经成功地使用它与串行乐器进行交互,其中“那个其他串行模块”有很多类似于你描述的问题。这个也可以告诉你FIFO中是否有数据。

现在让我告诉它如何。

答案 3 :(得分:0)

感谢代码Keith,但我想保持这段代码的可移植性,所以我想坚持使用默认的“serial”软件包。

另外,由于我还在学习Python,我想尝试学习如何按照预期的方式使用TextIOWrapper。

我放弃了尝试使serial.readline()工作,所以现在我只使用一个简单的“readLine”函数一次读取一个字符并查找回车终止符。虽然如果我遇到更多连续的古怪,我可能会重新使用你的代码。

谢谢!

def readLine(ser):
    str = ""
    while 1:
        ch = ser.read()
        if(ch == '\r' or ch == ''):  
            break
        str += ch

    #"print "str = " + str

    return str