如何提高 PySerial 读取速度
How can I improve PySerial read speed
我目前正在构建一台使用 Arduino Mega2560 作为主控制器的机器。 Arduino 通过串口连接,获取命令,执行它并每 1ms 吐出一堆测量数据。我有一个 Raspberry Pi 运行ning Python 给用户一个漂亮的 GUI 来发送命令,并以可读的形式呈现数据。
我面临的问题:Arduino 能够每毫秒吐出 15 字节的数据(所以只有 15kbyte/s),但是我 运行ning 的代码只能处理大约 15每 10 毫秒一个字节,所以 1.5kB/s。
当我 运行 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
然后读取那个文件,但这不是一个很好的解决方案,是吗?
非常感谢 :)
我已经从 PySerial 切换到 PyTTY,这解决了我的问题。只需将其插入此代码(进行一些小的更改,例如将 serialPort.inWaiting() == 0
替换为 serialPort.peek() == b''
)使我的代码能够处理数据流并且不会超过 50% CPU 使用率,这意味着它至少快 10 倍。不过,我仍在使用 PySerial 来设置 DTR 行。
所以,我想问题的答案是 PySerial 确实优化不佳。
我知道这是一个旧线程,但在撰写本文时它已被查看 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
可以找到一个很好的解决方案 here。
作者声明:
The code below gives me 790 kB/sec while replacing the code with
pyserial's readline method gives me just 170kB/sec.
本次对比设置的波特率没有说明。下例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())
我目前正在构建一台使用 Arduino Mega2560 作为主控制器的机器。 Arduino 通过串口连接,获取命令,执行它并每 1ms 吐出一堆测量数据。我有一个 Raspberry Pi 运行ning Python 给用户一个漂亮的 GUI 来发送命令,并以可读的形式呈现数据。
我面临的问题:Arduino 能够每毫秒吐出 15 字节的数据(所以只有 15kbyte/s),但是我 运行ning 的代码只能处理大约 15每 10 毫秒一个字节,所以 1.5kB/s。
当我 运行 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
然后读取那个文件,但这不是一个很好的解决方案,是吗?
非常感谢 :)
我已经从 PySerial 切换到 PyTTY,这解决了我的问题。只需将其插入此代码(进行一些小的更改,例如将 serialPort.inWaiting() == 0
替换为 serialPort.peek() == b''
)使我的代码能够处理数据流并且不会超过 50% CPU 使用率,这意味着它至少快 10 倍。不过,我仍在使用 PySerial 来设置 DTR 行。
所以,我想问题的答案是 PySerial 确实优化不佳。
我知道这是一个旧线程,但在撰写本文时它已被查看 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
可以找到一个很好的解决方案 here。
作者声明:
The code below gives me 790 kB/sec while replacing the code with pyserial's readline method gives me just 170kB/sec.
本次对比设置的波特率没有说明。下例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())