Python在嵌入式数据可视化中的应用:实时绘制传感器采集的动态曲线
嵌入式开发中,调试传感器数据最原始的方式是看串口打印的数字,但一串冰冷的数值很难直观反映趋势。用Python在上位机实时绘制动态曲线,能让温度漂移、振动波形、信号毛刺一目了然。本文以PyQtGraph库为例,演示从串口读取传感器数据到实时滚动画布的完整实现。
为什么选PyQtGraph
Matplotlib的animation模块也能实现动态绘图,但帧率超过10fps时CPU占用飙升,且交互卡顿。PyQtGraph基于Qt和OpenGL,专门为高性能实时可视化设计,在百万级数据点下依然流畅。它原生支持滚动视图、自动缩放和多曲线叠加,非常适合嵌入式上位机。
整体架构
传感器 → 串口 → Python串口线程 → 环形缓冲区 → PyQtGraph绘图线程
数据采集与UI渲染分离,避免串口阻塞界面。使用QTimer定时刷新画布,典型刷新间隔30ms(约33fps)。
核心代码实现
1. 串口数据采集线程
import serial, threading, collections
class SerialReader(threading.Thread):
def __init__(self, port, baud=115200):
super().__init__()
self.ser = serial.Serial(port, baud, timeout=0.1)
self.buffer = collections.deque(maxlen=500) # 环形缓冲区
self.running = True
def run(self):
while self.running:
line = self.ser.readline()
if line:
try:
value = float(line.strip())
self.buffer.append(value)
except ValueError:
pass
def stop(self):
self.running = False
self.ser.close()
假设传感器每秒输出10个数值,每行一个浮点数,如23.45\n。
2. PyQtGraph实时绘图窗口
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtWidgets
import sys
class RealTimePlot(QtWidgets.QMainWindow):
def __init__(self, reader):
super().__init__()
self.reader = reader
self.setWindowTitle("传感器实时曲线")
self.resize(800, 400)
# 创建绘图部件
self.plot_widget = pg.PlotWidget()
self.setCentralWidget(self.plot_widget)
self.curve = self.plot_widget.plot(pen='y')
# 定时器:每30ms更新一次
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.update_plot)
self.timer.start(30)
def update_plot(self):
# 从缓冲区取出所有数据并清空
data = list(self.reader.buffer)
self.reader.buffer.clear()
if data:
# 将新数据追加到现有曲线后方
old_x, old_y = self.curve.getData()
if old_x is None:
x = list(range(len(data)))
y = data
else:
x = list(old_x) + [old_x[-1] + i + 1 for i in range(len(data))]
y = list(old_y) + data
# 只保留最近500点
if len(x) > 500:
x = x[-500:]
y = y[-500:]
self.curve.setData(x, y)
# 自动滚动X轴
self.plot_widget.setXRange(max(0, x[-1]-500), max(500, x[-1]))
3. 主程序入口
if __name__ == '__main__':
reader = SerialReader('COM3', 115200)
reader.start()
app = QtWidgets.QApplication(sys.argv)
win = RealTimePlot(reader)
win.show()
app.exec_()
reader.stop()
性能优化要点
缓冲区大小:maxlen=500足够显示最近5秒数据(每秒10个点)。若采样率1kHz,应增大到2000以上。
更新频率:QTimer(30ms)对应33fps,人眼已觉流畅。若CPU吃紧可改为50ms。
数据类型:使用numpy.array替代Python列表可进一步提升setData速度,但本例为保持简洁使用列表。
避免内存泄漏:每次update_plot清空缓冲区,旧数据被丢弃。
实际效果
用DHT22温度传感器配合ESP32,每200ms输出一次温度值。上位机运行上述代码,曲线平滑滚动,延迟低于一个采样周期。当温度从25°C升至30°C时,上升趋势清晰可见,无需盯着串口数字发呆。
扩展思路
多曲线叠加:创建多个plot()对象,分别绑定不同传感器通道。
坐标轴标签:添加setLabel('left', '温度 (°C)')和setLabel('bottom', '采样点')。
触发式截图:按空格键保存当前波形为PNG,用于测试报告。
网络数据源:将SerialReader替换为SocketReader,即可远程监控。
写在最后
Python+PyQtGraph的组合让嵌入式数据可视化变得像写脚本一样简单。从串口读取到动态曲线,核心代码不到80行,却解决了“数据太多看不懂”的根本痛点。对于需要长时间记录传感器趋势的调试场景,这套方案比商业数据采集软件更轻量、更可控。下次调试PID参数或分析振动信号时,不妨试试让数据“画”出来。





