使用Arduino和pyserial绘制串口数据

2 投票
4 回答
2343 浏览
提问于 2025-04-18 13:56

我正在尝试使用Arduino和pyserial、numpy以及matplotlib来绘制加速度计的串行数据。问题是每当我打开图形用户界面(GUI)时,接收到的数据速度就变得非常慢,绘图也很慢。如果我不打开GUI,而是直接在命令窗口打印数据,接收到的数据就很快。请帮帮我!!

这是我用Python写的代码:

import serial
import matplotlib.pyplot as plt
import numpy as np
import time


ser = serial.Serial('COM3', 9600, timeout=0) #sets up serial connection (make sure baud          rate is correct - matches Arduino)

ser.flushInput()
ser.flushOutput()

plt.ion()                    #sets plot to animation mode

length = 500       #determines length of data taking session (in data points)
x = [0]*length               #create empty variable of length of test
y = 0
z = 0
fig, ax = plt.subplots()
line, = ax.plot(np.random.randn(100))
plt.show(block=False)
xline, = plt.plot(x)         #sets up future lines to be modified
plt.ylim(30,120)        #sets the y axis limits

#for i in range(length):     #while you are taking data
tstart = time.time()
n = 0
while time.time()-tstart < 5:
   y = (ser.readline().decode('utf-8')[:-2])
   if not (len(y)==0):
       z = float(y)
       x.append(float(z))   #add new value as int to current list

       del x[0]

       xline.set_xdata(np.arange(len(x))) #sets xdata to new list length

       xline.set_ydata(x)                 #sets ydata to new list

       #  ax.draw_artist(ax.patch)
       #  ax.draw_artist(line)
       #  fig.canvas.update()
       fig.canvas.draw()
       fig.canvas.flush_events()   
       #plt.pause(0.0001)                   #in seconds                                      #draws new plot
       #plt.show()
       n +=1
       print (z)
       print(n)




 ser.close() #closes serial connection (very important to do this! if you have an error   partway through the code, type this into the cmd line to close the connection)

4 个回答

0

我觉得你遇到这些性能问题的主要原因是,matplotlib这个工具并不是专门为实时数据设计的。它在从脚本中生成优秀的图表方面表现得非常出色,但如果只是添加一个数据点然后重新绘制,matplotlib可能就不太适合了。

你可以考虑其他一些实时数据查看的选项,比如(不怕自我推荐)lognplot:https://github.com/windelbouwman/lognplot

还有一个关于实时数据查看程序的有用列表,可以在这里找到:https://arduino.stackexchange.com/questions/1180/serial-data-plotting-programs

0

我之前在从Arduino的串口绘制数据时也遇到了速度慢的问题。解决办法可以在这个链接找到:http://asaf.pm/wordpress/?p=113#comment-273。在这种情况下,我成功地以大约700帧每秒的速度绘制了一个包含50个数据点的数组。

0

根据我的经验,重新绘制一条包含10,000个点的线大约需要5毫秒,但这取决于你的后端和电脑性能。如果你想要更快地做到这一点,那就麻烦了。另一方面,更新图表超过每秒50次是不必要的,因为人眼对变化的感知有延迟。

所以,正如MaxNoe所说,如果数据变化很快,你应该对数据进行缓冲。你可以使用固定大小的缓冲区,或者使用带超时的缓冲区。(看起来你仍然在接收稳定的数据流,否则你在没有超时的情况下使用ser.readline会遇到问题。)

还有一些其他方法可以加速你的代码。不过,由于我不知道数据的具体量,我无法判断这些方法是否真的能提高性能。性能分析是关键(正如James Mills所说)。

首先,你应该把xy设置为ndarray(NumPy数组),这样可以避免将列表转换为数组时的额外开销。基本上就是:

# initializing the array
x = np.zeros(length)

# adding one item to the array:
# shift the existing items and add an item to the end
x[:-1] = x[1:]
x[-1] = float(z)

另外,注意每次循环都不需要调用set_xdata,因为xdata是固定不变的。只需在循环之前设置一次即可。

如果你使用缓冲,基于时间的缓冲大致是这样的,每次刷新周期:

databuf = []
# collect data until you have something and at least 20 ms has gone
t0 = time.time()
while time.time() - t0 < 0.02 or len(databuf) == 0:
    ln = ser.readline()
    try:
        databuf.append(float(ln[:-2]))
    except:
        # something went wrong, but now we don't care
        pass

n_newpoints = len(databuf)
x[:-n_newpoints] = x[n_newpoints:]
x[-n_newpoints:] = databuf

# draw etc ...

这里的databuf是一个列表,但由于它很短,转换的开销几乎可以忽略不计。

通过这些改进,你应该能够流畅地更新一个包含10,000个点的图表,而不会让你的电脑卡顿。如果你在性能较低的机器上(比如树莓派等),那么减少更新频率就是解决办法。

0

有几个方法可以加快这个过程。我们在用Arduino读取地震仪时也遇到过同样的问题。

主要的窍门是先缓存十个或更多的数值,然后再进行绘图,而不是每有一个新事件就绘图一次。

使用动画包来绘图,而不是手动绘制,也可能会加快这个过程。

可以看看我们的GitHub代码:https://gist.github.com/ibab/10011590

还有,你为什么要把z转换两次为浮点数:

z = float(y)
x.append(float(z))

撰写回答