开发者

Reading real time values with pySerial while plotting

So here is the deal, I have a module which sends out data over the serial port at 9600 baud and I am using the matplotlib to plot that data in real time. I wrote this code

#! 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")

I get 20 bytes and then make 10 big bytes (2 byte data is split as two 1 byte values while transmitting). But I just noticed that I am 开发者_C百科not getting real time values from this. I am on windows 7 home basic if that matters.

Any clue why this is happening?

EDIT

Also, whenever I click on the plot it hangs.


I know the code below seems long and overly complex for your simple problem, but manual optimization is usually more code and more potential bugs. That's why premature optimization is almost always a mistake.

In __init__ it sets up the plot, setting references for the axis, canvas, line (it starts with a line drawn off screen), and the pre-animation background. Additionally __init__ registers callbacks to handle resizing and shutdown. The on_resize callback is needed to update the background (used for the blit) when the window is resized. The on_close callback uses a lock to update the running status. I haven't eliminated all race conditions with this, but this works to prevent a _tkinter.TclError caused by trying to blit to a terminated Tk app. I only tested with Tk, and on only one machine. YMMV, and I'm open to suggestions.

In the run method I've added a call to canvas.flush_events(). This should keep the plot window from hanging if you try to drag the window around and resize it. The while loop in this method calls self.get_new_values() to set the data in the plot. It then updates the plot using the configured method. If self.blit is true, it uses canvas.blit, else it uses pyplot.draw.

The variable spf (samples per frame) controls how many samples are plotted in a frame. You can use it in your implementation of get_new_values to determine the number of bytes to read (e.g. 2 * self.spf for 2 bytes per sample). I've set the default to 120, which is 5 frames per second if your data rate is 600 samples per second. You have to find the sweet spot that maximizes throughput vs time resolution in the graph while also keeping up with the incoming data.

Reading your data into a NumPy array instead of using a Python list will likely speed up processing. Also, it would give you easy access to tools to downsample and analyze the signal. You can read into a NumPy array directly from a byte string, but make sure you get the endianess right:

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

Code:

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜