Чтение значений в реальном времени с помощью pySerial при построении графика - PullRequest
3 голосов
/ 05 октября 2011

Итак, вот в чем дело, у меня есть модуль, который отправляет данные через последовательный порт со скоростью 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 home basic, если это имеет значение.

Любая подсказка, почему это происходит?

РЕДАКТИРОВАТЬ

Кроме того, всякий раз, когда я нажимаю наСюжет висит.

1 Ответ

2 голосов
/ 06 октября 2011

Я знаю, что приведенный ниже код кажется длинным и слишком сложным для вашей простой задачи, но ручная оптимизация обычно требует больше кода и больше потенциальных ошибок. Поэтому преждевременная оптимизация почти всегда является ошибкой.

В __init__ он устанавливает график, устанавливает ссылки для оси, холста, линии (она начинается с линии, нарисованной за пределами экрана) и фона перед анимацией. Дополнительно __init__ регистрирует обратные вызовы для обработки изменения размера и выключения. Обратный вызов on_resize необходим для обновления фона (используется для блита) при изменении размера окна. Обратный вызов on_close использует блокировку для обновления рабочего состояния. Я не устранил все условия гонки с этим, но это работает, чтобы предотвратить _tkinter.TclError, вызванный попыткой перейти к завершенному приложению Tk. Я тестировал только с Tk, и только на одной машине. YMMV, и я открыт для предложений.

В методе run я добавил вызов canvas.flush_events(). Это должно удерживать окно графика от зависания, если вы попытаетесь перетащить окно вокруг и изменить его размер. Цикл while в этом методе вызывает self.get_new_values() для установки данных на графике. Затем он обновляет график, используя настроенный метод. Если self.blit имеет значение true, он использует canvas.blit, иначе он использует pyplot.draw.

Переменная spf (выборок на кадр) контролирует, сколько выборок отображается в кадре. Вы можете использовать его в своей реализации get_new_values для определения количества считываемых байтов (например, 2 * self.spf для 2 байтов на выборку). Я установил значение по умолчанию 120, что составляет 5 кадров в секунду, если ваша скорость передачи данных составляет 600 выборок в секунду. Вы должны найти точку, которая максимизирует пропускную способность по сравнению с временным разрешением на графике, а также не отставать от входящих данных.

Чтение ваших данных в массив 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)
...