Максимизация скорости последовательной связи для прямых графиков данных из Teensy 3.2 с использованием Python - PullRequest
1 голос
/ 10 июля 2019

Я пытаюсь построить данные как можно быстрее с помощью Python (PyQtGraph), полученного от Teensy 3.2, который отправляет аналоговые данные по последовательному каналу связи.Код может в достаточной степени построить тестовую форму волны более высоких частот (синусоида около 5 кГц), но на графике требуется около 30 секунд, чтобы показать изменение частоты.Например, если тестовая форма волны отключена, она продолжает строить синусоидальную волну еще полминуты.

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

Python (Plotting) Side:

# Import libraries
from numpy import *
from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph as pg
import serial
import re

# Create object serial port
portName = "COM8"                      
baudrate = 115200 
ser = serial.Serial(portName,baudrate)

### START QtApp #####
app = QtGui.QApplication([])           
####################

win = pg.GraphicsWindow(title="Signal from serial port") # creates a window
p = win.addPlot(title="Realtime plot")  # creates empty space for the plot in the window
curve = p.plot()                        # create an empty "plot" (a curve to plot)
windowWidth = 100                       # width of the window displaying the curve - this is the time scale of the plot
Xm = linspace(0,0,windowWidth)          # create array of zeros that is the size of the window width   
ptr = -windowWidth                      # set first x position

# Realtime data plot. Each time this function is called, the data display is updated
def update():

    global curve, ptr, Xm    
    Xm[:-1] = Xm[1:]                    # shift data in the temporal mean 1 sample left 
    if ser.isOpen():                    # make sure there is data coming in
        b1 = ser.read(1)                # read the first byte of data
        b2 = ser.read(1)                # read the second byte of data
        data = b1 + b2                  # concatenate the two bytes
        data_int = int.from_bytes(data, byteorder='big')

        Xm[-1] = data_int                     # stack the data in the array
        ptr += 1                              # update x position for displaying the curve
        curve.setData(Xm)                     # set the curve with this data
        curve.setPos(ptr,0)                   # set x-y position in the graph to 0 and most recent data point - this creates the scrolling of the plot 
        QtGui.QApplication.processEvents()    # process the plot

### MAIN PROGRAM #####    
# this is a brutal infinite loop calling realtime data plot
while True: update()

### END QtApp ####
pg.QtGui.QApplication.exec_() 
##################

Teensy 3.2 Side:

const int sensorPin = A9;
uint16_t sensorValue = 0;
byte b1;
byte b2;
int flag = 0;
IntervalTimer heartBeatTimer;

void setup() 
{
  analogReadRes(12);
  Serial.begin(115200);
  heartBeatTimer.begin(heartBeat, 140); // (1 / 115200 Baud) * 16 bits / integer = 139us per 16 bits sent. Interrupt at 140 us to synchronize with baud rate.
  pinMode(13, OUTPUT);
}

void heartBeat() 
{
  flag = 1;                           // Interrupt routine every 140us
}


void loop() 
{
  if (flag == 1) {
    sensorValue = analogRead(sensorPin);  // read the analog pin as a 16 bit integer
    b1 = (sensorValue >> 8) & 0xFF;       // break up the reading to two bytes
    b2 = sensorValue & 0xFF;              // get the second byte
    Serial.write(b1);                     // write the first byte (trying to speed things up by sending only strictly necessary data)
    Serial.write(b2);                     // write the second byte
    digitalWrite(13, HIGH);               // just to make sure we're interrupting correctly
    flag = 0;                             // wait for next interrupt
  }
  digitalWrite(13, LOW);                  // just to make sure we're interrupting correctly 
}

У кого-нибудь есть какие-либо предложения о том, как ускорить процесс?

1 Ответ

1 голос
/ 10 июля 2019

Как предположил М.Р. выше , вам, вероятно, будет лучше, если вы упакуете больше данных, прежде чем отправлять их, вместо того, чтобы отправлять двухбайтовый пакет за раз.

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

Если вместо этого вы обрабатываете столько байтов, сколько у вас имеется в буфере RX, вы можете получить производительность почти в реальном времени.

Просто измените функцию обновления:

def update():

    global curve, ptr, Xm    

    if ser.inWaiting() > 0                         # Check for data not for an open port
        b1 = ser.read(ser.inWaiting())             # Read all data available at once
        if len(b1) % 2 != 0:                       # Odd length, drop 1 byte
            b1 = b1[:-1]
        data_type = dtype(uint16)
        data_int = fromstring(b1, dtype=data_type) # Convert bytes to numpy array
        data_int = data_int.byteswap()             # Swap bytes for big endian
        Xm = append(Xm, data_int)              
        ptr += len(data_int)                              
        Xm[:-len(data_int)] = Xm[len(data_int):]   # Scroll plot
        curve.setData(Xm[(len(Xm)-windowWidth):])                     
        curve.setPos(ptr,0)                   
        QtGui.QApplication.processEvents()   

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

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

Я не проверял подробно код Teensy, но, на первый взгляд, я думаю, что таймер прерывания, которое вы используете для задания темпа для АЦП, может быть слишком сжатым. Вы забыли рассмотреть начальные и конечные биты, которые перемещаются с каждым байтом данных, и вы не учитываете время, необходимое для выполнения преобразования AD (я думаю, оно должно быть очень маленьким, возможно, 10 микросекунд). Учитывая все вышесказанное, я думаю, вам может понадобиться увеличить сердцебиение, чтобы быть уверенным, что вы не вводите нерегулярные времена выборки. Должно быть возможно получить намного более высокую частоту дискретизации с помощью Teensy, но для этого вам нужно использовать совершенно другой подход. Хорошая тема для другого вопроса, я думаю ...

...