提高 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 不利,它会导致内存碎片,这会在几个循环后陷入困境。相反,您可以为每个位设置多个 prints(先保存转换为字符串然后连接),最后一个块使用 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相关资料整理

Pyserial:readline() 是阻塞的,虽然定义了超时

Pyserial readline() 并等到收到一个值才能继续

在另一个线程中中断 pySerial readline

PySerial 读取问题