提高 pyserial readline 速度
Posted
技术标签:
【中文标题】提高 pyserial readline 速度【英文标题】:Increasing pyserial readline speed 【发布时间】:2017-06-20 13:22:16 【问题描述】:我正在使用 pyserial 读取 arduino 发送的数据。 arduino 每 50 毫秒发送一次数据。我试过以两种不同的格式接收,这两种格式都是字符串。我想知道是否有更快的方法在我的 python gui 中接收这些数据,无论是使用不同的库、接收不同的数据类型还是优化代码。 第一种格式:
String potcolumn = String(pot0holder) + "." + String(pot1holder) + "." + String(i) + "|" + String(int(pot0holder)+30) + "." + String(int(pot1holder)+30) + "." + String(i) + "|" + String(int(pot0holder)+60) + "." + String(int(pot1holder)+60) + "." + String(i) + "|" + String(int(pot0holder)+90) + "." + String(int(pot1holder)+90) + "." + String(i);
平均读取时间:0.0523106797228 秒
第二种格式:
pressure1 = String(pot0array[0]) + "," + String(pot0array[1]);
displacement1 = String(pot1array[0]) + "," + String(pot1array[1]);
iteration1 = String(i-1) + "," + String(i);
full1 = pressure1 + ">" + displacement1 + ">" + iteration1;
pressure2 = String(pot0array[0]+30) + "," + String(pot0array[1]+30);
displacement2 = String(pot1array[0]+30) + "," + String(pot1array[1]+30);
iteration2 = String(i-1) + "," + String(i);
full2 = pressure2 + ">" + displacement2 + ">" + iteration2;
pressure3 = String(pot0array[0]+60) + "," + String(pot0array[1]+60);
displacement3 = String(pot1array[0]+60) + "," + String(pot1array[1]+60);
iteration3 = String(i-1) + "," + String(i);
full3 = pressure3 + ">" + displacement3 + ">" + iteration3;
pressure4 = String(pot0array[0]+90) + "," + String(pot0array[1]+90);
displacement4 = String(pot1array[0]+90) + "," + String(pot1array[1]+90);
iteration4 = String(i-1) + "," + String(i);
full4 = pressure4 + ">" + displacement4 + ">" + iteration4;
fulltotal = full1 + "|" + full2 + "|" + full3 + "|" + full4;
Serial.println(fulltotal);
这平均需要:0.0937848151484 秒才能读取,这是有道理的,因为它是数据的两倍
这是一个非常简单的 GUI,用于接收数据并使用 pyserial、tkinter 和 python 测试读取时间:
import Tkinter
import serial
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from matplotlib import pyplot as plt
import matplotlib.animation as animation
from collections import deque
import random
import time
import cProfile
class App:
def __init__(self, master):
self.arduinoData = serial.Serial('com5', 250000, timeout=None)
frame = Tkinter.Frame(master)
self.go = 0
self.run = Tkinter.LabelFrame(frame, text="Testing", borderwidth=10, relief=Tkinter.GROOVE, padx=10, pady=10)
self.run.grid(row=0, column=0, padx=20, pady=20)
self.run_respiration = Tkinter.Button(self.run, text="RUN",bd=10, height=5, width=10, command=self.getData)
self.run_respiration.grid(row=0, column=0, padx=5, pady=5)
self.test_options = Tkinter.LabelFrame(frame, text="Test Options", borderwidth=10, relief=Tkinter.GROOVE, padx=10, pady=10 )
self.test_options.grid(row=0, column=1, padx=20, pady=20)
self.stop = Tkinter.Button(self.test_options, text="STOP", bd=10, height=5, width=10, command=self.stopTest)
self.stop.grid(row=0, column=0, padx=5, pady=5)
frame.grid(row=0, column=0, padx=20, pady=20)
def getData(self):
return self.start()
def stopTest(self):
self.arduinoData.write("<H>")
self.go = 0
def start(self):
self.arduinoData.write("<L>")
self.go = 1
self.timer()
def readData(self):
if (self.arduinoData.inWaiting()>0):
t = time.time()
x = self.arduinoData.readline()
print str(time.time()-t)# + "\t" + str(x)
def timer(self):
if self.go == 1:
self.readData()
root.after(0, self.timer)
root = Tkinter.Tk()
app = App(root)
root.mainloop()
arduino 很容易以正确的速度发送数据,只是 python gui 的读取速度不够快,无法满足我的使用需求。
是否有可能使用 cython 或使用 C++ 的扩展可以更快地读取它,如果有任何资源可以用作我还没有找到任何东西的指南。
即使只是运行这段代码,平均时间也只有 0.11438 秒:
import time
import serial
def readData():
if arduinoData.inWaiting()>0:
t = time.time()
x = arduinoData.readline()
y = str(time.time()-t)
print y
def run():
x = 5000
z = 0
while z < x:
readData()
z += 1
if __name__ == "__main__":
arduinoData = serial.Serial('com5', 250000, timeout=None)
arduinoData.write("<L>")
run()
print('done')
感谢您的帮助和建议
【问题讨论】:
您确定发送数据实际上需要 50 毫秒吗? “只是 python gui 的读取速度不够快,无法满足我的使用需求。”当您实际上不使用 gui 来显示任何数据并且显然不会在此处造成任何挂起时,这是没有意义的。看起来您也在发送端使用 python。在你开始下结论之前,你可能想要提供更多的证据来证明你的断言。还有你的意思是“每 50 毫秒发送一次数据”,你的意思是每字节吗? 首先我会从分析你的 python 脚本开始,看看挂断在哪里,因为 50ms 似乎不正确。其次,像这样将字符串拼凑在一起对 arduino 中的 ram 不利,它会导致内存碎片,这会在几个循环后陷入困境。相反,您可以为每个位设置多个print
s(先保存转换为字符串然后连接),最后一个块使用 println
代替
另外请注意,当性能至关重要时,控制台打印语句很昂贵。由于 IO 通道的缓冲,将输出保存到文件实际上更快。这样,输出实际上被写入内存缓冲区,并在缓冲区填满时刷新到磁盘控制器。
@snb 我正在使用 matplotlibs 动画函数来绘制数据,我只是认为这并不像实际读取数据那样重要,所以我不想让帖子陷入困境有一堆额外的代码。我并不是说 pyserial 或其他任何东西有问题,我只是在询问我是否能够提高读取数据的速度的建议。在开始从 arduino 读取数据之前,我发送了一些值,我应该在发布之前删除这些值。 arduino 会在 50 毫秒后发送我在帖子中输入的字符串。
@James Kent 我将根据您的建议更改 arduino 代码并重新计时,以查看是否提高了速度。谢谢
【参考方案1】:
在分析上面的代码后,我收到了这个作为输出
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.001 0.001 profile_stack.py:61(readData)
1 0.001 0.001 0.001 0.001 serialwin32.py:234(inWaiting)
2 0.000 0.000 0.000 0.000 _ctypes.byref
1 0.000 0.000 0.000 0.000 method 'disable' of '_lsprof.Profiler' objects
270 function calls in 0.025 seconds
Ordered by: standard name
ncalls tottime percall cumtime percall filename:lineno(function)
24 0.000 0.000 0.000 0.000 __init__.py:49(create_string_buffer)
1 0.000 0.000 0.025 0.025 profile_stack.py:61(readData)
24 0.000 0.000 0.000 0.000 serialutil.py:404(getTimeout)
1 0.001 0.001 0.001 0.001 serialwin32.py:234(inWaiting)
24 0.023 0.001 0.024 0.001 serialwin32.py:242(read)
146 0.000 0.000 0.000 0.000 _ctypes.byref
48 0.000 0.000 0.000 0.000 isinstance
1 0.000 0.000 0.000 0.000 method 'disable' of '_lsprof.Profiler' objects
1 0.000 0.000 0.024 0.024 method 'readline' of '_io._IOBase' objects
5 function calls in 0.001 seconds
这是我用来获取此信息的代码:
import Tkinter
import serial
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from matplotlib import pyplot as plt
import matplotlib.animation as animation
from collections import deque
import random
import time
import cProfile
class App:
def __init__(self, master):
self.arduinoData = serial.Serial('com5', 250000, timeout=None)
frame = Tkinter.Frame(master)
self.go = 0
self.run = Tkinter.LabelFrame(frame, text="Testing", borderwidth=10, relief=Tkinter.GROOVE, padx=10, pady=10)
self.run.grid(row=0, column=0, padx=20, pady=20)
self.run_respiration = Tkinter.Button(self.run, text="RUN",bd=10, height=5, width=10, command=self.getData)
self.run_respiration.grid(row=0, column=0, padx=5, pady=5)
self.test_options = Tkinter.LabelFrame(frame, text="Test Options", borderwidth=10, relief=Tkinter.GROOVE, padx=10, pady=10 )
self.test_options.grid(row=0, column=1, padx=20, pady=20)
self.stop = Tkinter.Button(self.test_options, text="STOP", bd=10, height=5, width=10, command=self.stopTest)
self.stop.grid(row=0, column=0, padx=5, pady=5)
frame.grid(row=0, column=0, padx=20, pady=20)
def do_cprofile(func):
def profiled_func(*args, **kwargs):
profile = cProfile.Profile()
try:
profile.enable()
result = func(*args, **kwargs)
profile.disable()
return result
finally:
profile.print_stats()
return profiled_func
def getData(self):
return self.start()
def stopTest(self):
self.arduinoData.write("<H>")
self.go = 0
def start(self):
self.arduinoData.write("<L>")
self.go = 1
self.timer()
@do_cprofile
def readData(self):
if (self.arduinoData.inWaiting()>0):
t = time.time()
x = self.arduinoData.readline()
print str(time.time()-t)# + "\t" + str(x)
def timer(self):
if self.go == 1:
self.readData()
root.after(0, self.timer)
root = Tkinter.Tk()
app = App(root)
root.mainloop()
【讨论】:
以上是关于提高 pyserial readline 速度的主要内容,如果未能解决你的问题,请参考以下文章
python pyserial readline 不工作,但 screen 有点工作,在 ubuntu 16 中工作
Pyserial:readline() 是阻塞的,虽然定义了超时