使用pySerial实时读取值并绘图

3 投票
1 回答
5958 浏览
提问于 2025-04-17 03:43

事情是这样的,我有一个模块,它通过串口以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 个回答

2

我知道下面的代码看起来很长,而且对于你简单的问题来说有点复杂,但手动优化通常会导致代码变得更长,潜在的错误也会增多。这就是为什么过早优化几乎总是个错误。

__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)

撰写回答