【问题标题】:How can I improve PySerial read speed如何提高 PySerial 读取速度
【发布时间】:2015-06-15 22:21:48
【问题描述】:

我目前正在构建一台使用 Arduino Mega2560 作为其主控制器的机器。 Arduino通过串口连接,获取命令,执行它并每1ms吐出一堆测量数据。我有一个运行 Python 的 Raspberry Pi,为用户提供了一个很好的 GUI 来发送命令,并以可读的形式呈现数据。

我面临的问题:Arduino 能够每毫秒吐出 15 个字节的数据(所以只有 15kbyte/s),但我正在运行的代码每 10 毫秒只能处理大约 15 个字节,所以 1.5千字节/秒。

当我运行 cat /dev/ttyACM0 > somefile 时,我很好地看到了所有数据点。

我有以下精简的 Python 代码

# Reset Arduino by starting serial
microprocBusy = True
serialPort = serial.Serial("/dev/ttyACM0", baudrate=460800, timeout=0)
time.sleep(0.22);
serialPort.setDTR(False);
time.sleep(0.22);
serialPort.setDTR(True);
time.sleep(0.10);

logfile = open(logfilenamePrefix + "_" + datetime.datetime.now().isoformat() + '.txt', 'a')

# Bootloader has some timeout, we need to wait for that
serialPort.flushInput()
while(serialPort.inWaiting() == 0):
    time.sleep(0.05)

# Wait for welcome message
time.sleep(0.1)
logfile.write(serialPort.readline().decode('ascii'))
logfile.flush()

# Send command
serialPort.write((command + '\n').encode('ascii'))

# Now, receive data
while(True):
    incomingData = serialPort.readline().decode('ascii')
    logfile.write(incomingData)
    logfile.flush() 

    if(incomingData[:5] == "FATAL" or incomingData[:6] == "HALTED" or incomingData[:5] == "RESET"):
        break;
    elif(incomingData[:6] == "RESULT"):
            resultData = incomingData;

logfile.flush() 

当我运行它时,前约 350 个数据点进入,然后我看到一些损坏的数据并错过了大约 2000 个数据点,之后我看到另外 350 个左右的数据点。进程中CPU使用率为100%

出了什么问题? PySerial 是否优化不佳,或者我错过了我的代码中的一些错误?我可以从 Python 运行 cat /dev/ttyACM0 > somefile 然后读取该文件,但这并不是一个很好的解决方案,是吗?

非常感谢:)

【问题讨论】:

  • 不要经常刷新你的日志文件,你会立即看到一些性能改进。您通过使其在每个循环中写入磁盘来绕过文件缓冲。
  • 我试过了,没用。顺便说一句,它在 linux 上,所以没有实际的写入涉及 AFAIK,它只涉及 Python 缓冲区,而不是 OS 缓冲区
  • 尝试将 timeout 设置为 1,并使用 .read 函数而不是 readlines 函数。您可以将字节存储在内部缓冲区中,并在循环结束时一次(或在单独的线程中)将它们全部解码,然后写入日志文件。

标签: python arduino raspberry-pi pyserial


【解决方案1】:

我已经从 PySerial 切换到 PyTTY,这解决了我的问题。只需将其插入此代码(进行一些小的更改,例如将 serialPort.inWaiting() == 0 替换为 serialPort.peek() == b'')就可以使我的代码能够处理数据流并且不会超过 50% 的 CPU 使用率,这意味着它至少快 10 倍.不过,我仍在使用 PySerial 来设置 DTR 行。

所以,我想这个问题的答案是 PySerial 确实优化得很差。

【讨论】:

  • 您声称 PySerial “优化不佳”未经证实(我对此没有任何问题)。 PyTTY otoh 自 2012 年添加以来似乎已被废弃,而 PySerial 仍在维护中。
  • 例如,PySerial 在其 miniterm.py 终端工具 (python -m serial.tools.miniterm) 中的使用方式非常有效。我自己在使用它时遇到了问题,但 miniterm 实现的速度表明它可能只是我们自己的代码需要一些更改。
  • @toohonestforthissite,仅在更高的波特率下可见,我的数据中有“间隙”,(我在传入的数据流中添加了一个计数器)......当波特率为 921600 时,我必须说uart 很忙! (大量数据),我使用了乔的技巧!
【解决方案2】:

我意识到这是一个旧线程,但在撰写本文时已被查看 3000 次,我不希望有人在这次遭遇中关闭 pySerial。

我认为作者的问题最可能的罪魁祸首是在读取之间进行的隐式解析:

incomingData = serialPort.readline().decode('ascii')

readline() 方法告诉 pyserial 解析到下一行。您还在接收周期的中间执行decode()。所有这些都发生在您的信息流中间。

更好的方法可能如下所示:

waiting = port.in_waiting  # find num of bytes currently waiting in hardware
buffer += [chr(c) for c in port.read(waiting)] # read them, convert to ascii

# ...keep accumulating the buffer for as long as is reasonable...

processSerialData(buffer)  # whatever processing needs to happen, split your
                           # lines, log, or whatever else *after* you get
                           # your data

【讨论】:

  • 我只想说声谢谢,因为您(在十几个类似缓慢的链接中)让我查看了其余代码,结果证明这是罪魁祸首。我正在使用来自 Adafruit 的 stats.py,它向主机查询 ip addr/cpu load/mem 等。它导致串行读取的滞后和缓冲效应。删除了这些,它现在按预期工作。
  • @SamGreadly 没问题!乐意效劳!我每天都必须经常处理这个问题,当人们说“pyserial 很慢”而实际上是周围的代码时,这让我很困扰。祝你好运!
【解决方案3】:

可以在here找到一个非常好的解决方案。

作者说:

下面的代码给了我 790 kB/秒,同时将代码替换为 pyserial 的 readline 方法只给了我 170kB/秒。

没有关于此比较设置的波特率的说明。下例中的 9600 波特值仅用于测试。

此解决方案还避免了 100 % 的 CPU 使用率。

class ReadLine:
    def __init__(self, s):
        self.buf = bytearray()
        self.s = s
    
    def readline(self):
        i = self.buf.find(b"\n")
        if i >= 0:
            r = self.buf[:i+1]
            self.buf = self.buf[i+1:]
            return r
        while True:
            i = max(1, min(2048, self.s.in_waiting))
            data = self.s.read(i)
            i = data.find(b"\n")
            if i >= 0:
                r = self.buf + data[:i+1]
                self.buf[0:] = data[i+1:]
                return r
            else:
                self.buf.extend(data)

ser = serial.Serial('COM7', 9600)
rl = ReadLine(ser)

while True:

    print(rl.readline())

【讨论】:

  • 嗨。我看不出 9600 的波特率如何能达到 170 kB/s,更不用说 790 kB/s。考虑到整个 UART 协议正在用于数据传输(这是不正确的),您需要波特率 > 6,320,000。我能想象这些数据速率的唯一方法是使用 USB 高速。
  • 作者声明他正在virtual COM port /dev/ttyACM1 attached via USB 上进行测试,不确定波特率。整件事与波特率本身无关,更多的是不同的读取方法如何减慢进程。
  • @FranciscoGomes,我猜他的意思是 170kBit/s 和 790kBit/s。从我的角度来看,我可以确认这个技巧使我的数据不再有任何空白。 (波特率 = 921600)
  • 我发现的唯一问题是没有超时了......
  • 你在实例化serial.Serial()时指定了一个吗? (默认为timeout=None
猜你喜欢
  • 1970-01-01
  • 2019-06-22
  • 2016-11-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-08-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多