使用pySerial实时读取值并绘图
事情是这样的,我有一个模块,它通过串口以9600波特率发送数据,我用matplotlib来实时绘制这些数据。我写了这个代码:
#! python
############ import section ###################
import serial
import struct
import numpy as np
import matplotlib.pyplot as plt
###############################################
############ Serial Configuration #############
com = serial.Serial(14,9600)
print ("Successfully opened " + com.portstr)
###############################################
def get_new_values():
s = com.read(20)
raw_data = struct.unpack('!BBBBBBBBBBBBBBBBBBBB', s)
j = 0;
norm_data = []
for i in range(0,20,2):
big_byte = raw_data[i+1]+(raw_data[i]*256)
norm_data.append(big_byte)
j = j+1
return norm_data
x = range(0,10,1)
y = get_new_values()
plt.ion()
line, = plt.plot(x,y)
while(1):
a = get_new_values()
line.set_ydata(a)
plt.draw()
com.close()
print ("Port Closed Successfully")
我每次接收到20个字节的数据,然后把它们合成10个大字节(在传输时,2个字节的数据会被拆分成两个1字节的值)。但是我刚发现,我并没有得到实时的数据。我现在使用的是Windows 7家庭基础版,不知道这是否有影响。
有没有人知道为什么会这样?
补充说明
还有,每当我点击图表的时候,它就会卡住。
1 个回答
我知道下面的代码看起来很长,而且对于你简单的问题来说有点复杂,但手动优化通常会导致代码变得更长,潜在的错误也会增多。这就是为什么过早优化几乎总是个错误。
在 __init__
方法中,它设置了图表,包括坐标轴、画布、线条(一开始线条是在屏幕外面画的)和动画前的背景。此外,__init__
还注册了一些回调函数,用于处理窗口大小变化和关闭操作。on_resize
回调函数用于在窗口调整大小时更新背景(用于快速绘制)。on_close
回调函数使用锁来更新运行状态。我没有消除所有的竞争条件,但这样可以防止因为尝试在一个已经关闭的 Tk 应用上绘制而导致的 _tkinter.TclError
错误。我只在 Tk 上测试过,并且只在一台机器上测试过。你的情况可能会有所不同,我也欢迎建议。
在 run
方法中,我添加了一个调用 canvas.flush_events()
的语句。这应该可以防止当你尝试拖动窗口或调整大小时,图表窗口出现卡顿。这个方法中的 while 循环会调用 self.get_new_values()
来设置图表中的数据。然后,它会使用配置好的方法来更新图表。如果 self.blit
为真,它就会使用 canvas.blit
,否则就用 pyplot.draw
。
变量 spf
(每帧样本数)控制每帧绘制多少个样本。你可以在实现 get_new_values
时使用它来确定要读取的字节数(例如,2 * self.spf
表示每个样本读取 2 个字节)。我把默认值设置为 120,这意味着如果你的数据速率是每秒 600 个样本,那么就是每秒 5 帧。你需要找到一个最佳点,既能最大化数据处理速度,又能保持图表的时间分辨率,同时跟上输入数据的速度。
将数据读入 NumPy 数组而不是使用 Python 列表,可能会加快处理速度。此外,这样你可以轻松使用工具来降采样和分析信号。你可以直接从字节字符串读取到 NumPy 数组,但要确保字节序正确:
>>> data = b'\x01\xff' #big endian 256 + 255 = 511
>>> np.little_endian #my machine is little endian
True
>>> y = np.fromstring(data, dtype=np.uint16); y #so this is wrong
array([65281], dtype=uint16)
>>> if np.little_endian: y = y.byteswap()
>>> y #fixed
array([511], dtype=uint16)
代码:
from __future__ import division
from matplotlib import pyplot
import threading
class Main(object):
def __init__(self, samples_per_frame=120, blit=True):
self.blit = blit
self.spf = samples_per_frame
pyplot.ion()
self.ax = pyplot.subplot(111)
self.line, = self.ax.plot(range(self.spf), [-1] * self.spf)
self.ax.axis([0, self.spf, 0, 65536])
pyplot.draw()
self.canvas = self.ax.figure.canvas
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
self.canvas.mpl_connect('resize_event', self.on_resize)
self.canvas.mpl_connect('close_event', self.on_close)
self.lock = threading.Lock()
self.run()
def get_new_values(self):
import time
import random
#simulate receiving data at 9600 bps (no cntrl/crc)
time.sleep(2 * self.spf * 8 / 9600)
y = [random.randrange(65536) for i in range(self.spf)]
self.line.set_ydata(y)
def on_resize(self, event):
self.line.set_ydata([-1] * self.spf)
pyplot.draw()
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
def on_close(self, event):
with self.lock:
self.running = False
def run(self):
with self.lock:
self.running = True
while self.running:
self.canvas.flush_events()
with self.lock:
self.get_new_values()
if self.running:
if self.blit:
#blit for a higher frame rate
self.canvas.restore_region(self.background)
self.ax.draw_artist(self.line)
self.canvas.blit(self.ax.bbox)
else:
#versus a regular draw
pyplot.draw()
if __name__ == '__main__':
Main(samples_per_frame=120, blit=True)