使用 Python 最大化来自 Teensy 3.2 的实时绘图数据的串行通信速度
Maximizing serial communication speed for live plotting data from Teensy 3.2 using Python
我正在尝试使用从通过串行通信发送模拟数据的 Teensy 3.2 接收到的 Python (PyQtGraph) 尽快绘制数据。该代码可以充分绘制更高频率的测试波形(约 5kHz 的正弦波),但绘图需要近 30 秒才能显示频率的变化。例如,如果关闭测试波形,它会继续绘制正弦波半分钟。
我已经尝试执行 "serial flush" 来清除 Python 端和 Teensy 端的缓冲区,但是,这会严重减慢情节并且我的情节的频率响应会消失低至单赫兹。
Python(绘图)边:
# 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 侧:
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
}
有人对如何加快速度有什么建议吗?
如M.R。建议 如果在发送之前打包更多数据而不是一次发送 two-byte 数据包,你可能会更好。
但是您看到的糟糕性能更多地与您在计算机上读取数据的方式有关。如果您只从串行端口读取两个字节并将它们附加到图中,那么您最终的开销将是巨大的。
如果您改为处理 RX 缓冲区中可用的字节数,您可以获得几乎 real-time 的性能。
只需更改您的更新功能:
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 做到这一点,巧合的是我发现了 this question,这与你的非常相似。所以 numpy 解决方案值得称赞。
不幸的是,我的便携式示波器没电了,所以我无法正确测试上面的代码。但我认为一个好的解决方案应该是可行的。
我没有详细检查 Teensy 代码,但快速浏览一下,我认为您用来为 ADC 提供节奏的中断计时器可能有点太紧了。您忘记考虑随每个数据字节传输的起始位和停止位,并且您没有考虑完成 AD 转换所需的时间(我想这应该非常小,可能是 10 微秒)。考虑到所有因素,我认为您可能需要增加心跳以确保您不会引入不规则的采样时间。使用 Teensy 应该可以获得更快的采样率,但要做到这一点,您需要使用完全不同的方法。我想另一个问题的主题很好......
我正在尝试使用从通过串行通信发送模拟数据的 Teensy 3.2 接收到的 Python (PyQtGraph) 尽快绘制数据。该代码可以充分绘制更高频率的测试波形(约 5kHz 的正弦波),但绘图需要近 30 秒才能显示频率的变化。例如,如果关闭测试波形,它会继续绘制正弦波半分钟。
我已经尝试执行 "serial flush" 来清除 Python 端和 Teensy 端的缓冲区,但是,这会严重减慢情节并且我的情节的频率响应会消失低至单赫兹。
Python(绘图)边:
# 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 侧:
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
}
有人对如何加快速度有什么建议吗?
如M.R。建议
但是您看到的糟糕性能更多地与您在计算机上读取数据的方式有关。如果您只从串行端口读取两个字节并将它们附加到图中,那么您最终的开销将是巨大的。
如果您改为处理 RX 缓冲区中可用的字节数,您可以获得几乎 real-time 的性能。
只需更改您的更新功能:
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 做到这一点,巧合的是我发现了 this question,这与你的非常相似。所以 numpy 解决方案值得称赞。
不幸的是,我的便携式示波器没电了,所以我无法正确测试上面的代码。但我认为一个好的解决方案应该是可行的。
我没有详细检查 Teensy 代码,但快速浏览一下,我认为您用来为 ADC 提供节奏的中断计时器可能有点太紧了。您忘记考虑随每个数据字节传输的起始位和停止位,并且您没有考虑完成 AD 转换所需的时间(我想这应该非常小,可能是 10 微秒)。考虑到所有因素,我认为您可能需要增加心跳以确保您不会引入不规则的采样时间。使用 Teensy 应该可以获得更快的采样率,但要做到这一点,您需要使用完全不同的方法。我想另一个问题的主题很好......