常见的串口调试助手一般只有简单的文本界面,偶然看到 Arduino IDE 自带的串口绘图工具,觉得用户设计挺友好。想着利用一下周末空闲时间,用 PyQt5 实现一个串口数据实时绘图小工具,在这里记录一下。
目录
1 用户界面
2 串口通信
3 实时绘图
使用 Qt Designer 可以快速地设计用户界面,界面主体是绘制曲线的区域,下面放置两个 ComboBox,分别用于设置串口号和波特率,然后在右下角放一个按钮。
波特率下拉框勾选 editable,以支持自定义波特率,然后添加一些 ComboBox 项目,使用预览模式修改字体样式。
保存 ui 文件,使用 pyuic 工具进行转换。接着创建新的 py 文件,调用 setupUi() 函数实现 ui 界面。
from PyQt5 import Qt, QtGui, QtCore, QtWidgets
from ui_SerialPlot import Ui_Form
class SerialPlot(Qt.QWidget):
def __init__(self):
super().__init__()
self.ui = Ui_Form()
self.ui.setupUi(self)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
window = SerialPlot()
window.show()
sys.exit(app.exec_())
串口通信部分用到了 QSerialPort 和 QSerialPortInfo,使用以下语句导入:
from PyQt5.QtSerialPort import QSerialPort, QSerialPortInfo
在启动程序的时候,遍历一次串口信息,然后使用 addItem() 函数添加识别到的串口号到 ComboBox 中。
def __init__(self):
# ...
# 定义串口对象
self.COM = QSerialPort()
self.port_list = QSerialPortInfo.availablePorts()
# 读取串口信息,并选择第一个串口
for com_port in self.port_list:
self.ui.comport_comboBox.addItem(com_port.portName())
self.COM.setPortName(self.port_list[0].portName())
# 设置回调函数
self.ui.comport_comboBox.activated[str].connect(self.on_comport_changed)
self.ui.baudrate_comboBox.activated[str].connect(self.on_baudrare_changed)
# ...
def on_comport_changed(self, com_port):
self.COM.setPortName(com_port)
def on_baudrare_changed(self, baud_item):
baud_rate = int(baud_item.split(' ')[0])
self.COM.setBaudRate(baud_rate)
定义 on_pushbutton 函数,用于打开和关闭串口, 使用定时器每隔 100ms 读取一次串口数据。
def __init__(self):
# ...
# 串口相关
self.open_status = 'closed'
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.on_timeout)
self.ui.pushButton.clicked.connect(self.on_pushbuton)
# ...
def on_pushbutton(self):
if self.open_status == 'closed':
if not self.COM.open(QSerialPort.ReadWrite):
return
self.timer.start(100)
self.ui.pushButton.setText('STOP')
self.open_status = 'opened'
else:
self.COM.close()
self.timer.stop()
self.ui.pushButton.setText('START')
self.open_status = 'closed'
def on_timeout(self):
rcv_data = self.COM.readAll()
if len(rcv_data) >= 2:
print(int.from_bytes(bytes(rcv_data[0:2]), 'little'))
绘图部分用到了 pyqtgraph 库,使用以下语句导入:
import pyqtgraph as pg
在 __init__() 函数中定义 xdata 和 ydata,分别记录横轴和纵轴数据,并创建 PlotWidget 绘图控件到主窗口中。
def __init__(self):
# ...
# 创建 PlotWidget 对象
self.plotwidget = pg.PlotWidget(self, background='w')
self.plotwidget.setGeometry(QtCore.QRect(10, 10, 480, 270))
# 曲线相关
self.xdata = []
self.ydata = []
self.time = 0
self.pen = pg.mkPen(color='#bc5001', width=2)
self.curve = self.plotwidget.Plot(self.xdata, self.ydata, pen=self.pen)
# ...
最后修改之前的 on_timeout() 函数,更新曲线数据。
def on_timeout(self):
rcv_data = self.COM.readAll()
if len(rcv_data) >= 2:
value = int.from_bytes(bytes(rcv_data[0:2]), 'little')
self.xdata.append(self.time * 0.100)
self.ydata.append(value)
self.time += 1
self.curve.setData(self.xdata, self.ydata)
用 numpy 库随机模拟一些数据,测试一下绘图效果,大功告成。