使用Arduino和pyserial绘制串口数据
我正在尝试使用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 个回答
我觉得你遇到这些性能问题的主要原因是,matplotlib这个工具并不是专门为实时数据设计的。它在从脚本中生成优秀的图表方面表现得非常出色,但如果只是添加一个数据点然后重新绘制,matplotlib可能就不太适合了。
你可以考虑其他一些实时数据查看的选项,比如(不怕自我推荐)lognplot:https://github.com/windelbouwman/lognplot
还有一个关于实时数据查看程序的有用列表,可以在这里找到:https://arduino.stackexchange.com/questions/1180/serial-data-plotting-programs
我之前在从Arduino的串口绘制数据时也遇到了速度慢的问题。解决办法可以在这个链接找到:http://asaf.pm/wordpress/?p=113#comment-273。在这种情况下,我成功地以大约700帧每秒的速度绘制了一个包含50个数据点的数组。
根据我的经验,重新绘制一条包含10,000个点的线大约需要5毫秒,但这取决于你的后端和电脑性能。如果你想要更快地做到这一点,那就麻烦了。另一方面,更新图表超过每秒50次是不必要的,因为人眼对变化的感知有延迟。
所以,正如MaxNoe
所说,如果数据变化很快,你应该对数据进行缓冲。你可以使用固定大小的缓冲区,或者使用带超时的缓冲区。(看起来你仍然在接收稳定的数据流,否则你在没有超时的情况下使用ser.readline
会遇到问题。)
还有一些其他方法可以加速你的代码。不过,由于我不知道数据的具体量,我无法判断这些方法是否真的能提高性能。性能分析是关键(正如James Mills
所说)。
首先,你应该把x
和y
设置为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个点的图表,而不会让你的电脑卡顿。如果你在性能较低的机器上(比如树莓派等),那么减少更新频率就是解决办法。
有几个方法可以加快这个过程。我们在用Arduino读取地震仪时也遇到过同样的问题。
主要的窍门是先缓存十个或更多的数值,然后再进行绘图,而不是每有一个新事件就绘图一次。
使用动画包来绘图,而不是手动绘制,也可能会加快这个过程。
可以看看我们的GitHub代码:https://gist.github.com/ibab/10011590
还有,你为什么要把z转换两次为浮点数:
z = float(y)
x.append(float(z))