仪器控制-python串口通信实时获取数据并绘图

本文章是为了记录学习仪器控制的历程,虽然是用于实验室测样品获得数据而设计,但是涉及到xlwings、serial、socket、matplotlib、Qt、多线程、二分法查找数据的应用,对于自己来说确实是不少挑战。

设计的思路也是基于实验测试的要求:仪器为炉子,样品切换控制台、以及测试仪器

1.程序运行后出现选择不同的测试选项,点击便开始运行测试

2.在升温过程中获取当前温度并与对比,如果差值在允许的范围内则触发测试

3.记录不同样品在五个不同频率点的数据,通过给样品切换控制台发送命令得到不同样品的数据

4.将得到的数据写入excel中,并绘制实时数据图

根据1,使用Qt设计mainwindow,设置两个pushButton,用于实现两种不同的测试

仪器控制-python串口通信实时获取数据并绘图_第1张图片

考虑到代码复用性,于是将连接仪器获取数据、根据数据绘制图写成两个类,获取数据用到serial串口连接炉子与样品控制台,仪表用RS232串口线连接并发送命令,根据仪器的通信协议取得自己要的温度值,协议规定仪表读指令和写指令的返回值都为十个字节长,当前温度为前两个字节,这里有个小坑,之前一直用ser.readline()来返回数据,发现总是会出现返回温度明显不符的情况,原来是接受的数据有长有短,于是改成读取十个字节长的命令。

from serial import Serial
import serial.tools.list_ports
import sys
self.sock =socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.ser=serial.Serial('COM4', 9600, timeout=1)       #连接串口
port_list = list(serial.tools.list_ports.comports())  #打印串口信息
self.inputlist = [129, 129, 82, 0, 0, 0, 83, 0]       #给炉子发送读指令
self.binput = bytes(self.inputlist)                   #转换成字节形式
self.ser.write(self.binput)                           #发送命令
self.data=self.ser.read(10)                           #读取返回的结果并赋值

pv=struct.unpack('h', bytes([self.data[0], self.data[1]]))[0]  #将字节数据进行解码

仪器使用GPIB(General-Purpose Interface Bus)通用接口总线-USB与电脑通信,设置局域网,主机发送测试功能命令,发送设置频率命令,发送获取数据命令,得到测试结果

20130612141047593

import socket
import sys
try:
      self.sock.connect(('192.168.1.99', 2000))    #设置局域网,try尝试连接
except socket.timeout:
       print('连接失败')

self.sock.send(b':METER:FUNC:1 C;2 D')  #  发送测试function
time.sleep(0.1)
freq_list = ['100', '1E3', '1E4', '1E5', '1E6']
for i in freq_list:
      freq_setup = ':METER:FREQ '
      freq_setup_b = bytes(freq_setup + i, encoding="utf-8")
      self.sock.send(freq_setup_b)
      time.sleep(0.1)
self.sock.send(b':METER:TRIG')
time.sleep(1.5)
data2 = self.sock.recv(4096)
data_str = data2.decode(encoding="utf-8")

得到数据后写入sheets

import xlwings as xw
self.wb = xw.Book(r'C:\Users\TZDM\Desktop\111.xlsx')         #打开excel新建四个sheets
self.sheet1 = self.wb.sheets.add('A1')
self.sheet2 = self.wb.sheets.add('B2', after='A1')
self.sheet3 = self.wb.sheets.add('C3', after='B2')
self.sheet4 = self.wb.sheets.add('D4', after='C3')
self.sheets_list = [self.sheet1, self.sheet2,self.sheet3,self.sheet4]
mode_list = ['A1', 'B2', 'C3', 'D4']                      #样品切换通道命令为发送*A1#的ASCII
for i in mode_list:                                          
    a = '*'
    b = '#'
    mode_i = a + i + b
    mode = bytes(mode_i, encoding="utf-8")
    self.ser_yp.write(mode)
    t=0
    m=0
    for n in range(5):
        self.sheets_list[m].range(row, Cp_lie[t]).value = self.Cp[t]
        self.sheets_list[m].range(row, D_lie[t]).value = self.D[t]
        self.sheets_list[m].range(row, 1).value = self.pv_str
        self.sheets_list[m].range(row, 16).value = self.pv2_str
        print('成功写入数据')
        t = t + 1
m=m+1

用二分法将当前温度与要采取的温度点做对比,得到便触发上面测试

def dichotomy_pv(self):
        self.temp_set = list(range(28, 700, 2))
        num = 0
        i = 0
        j = len(self.temp_set) - 1
        self.get_temp()                 #获取当前温度的函数
        while (1):
            if (self.pv < (self.temp_set[(i + j) // 2])):
                j = (i + j) // 2
            else:
                i = (i + j) // 2
            if (j - i == 1):
                if ((self.temp_set[j] - self.pv) > (self.pv - self.temp_set[i])):
                    self.result = i
                else:
                    self.result = j
                break
            num = num + 1
        print(f'最接近 {self.pv}的下标为{self.result}')
        print("温度为%d" % self.temp_set[self.result])

当前温度与列表中温度对比若能找到接近值,则触发测试,再次对比温度并写入数据,这边使用二分法也是为了保证如果升温速率过快错过了温度点,至少能下一个温度测试点能正常获取数据,另外if好像只能对比int型数据,因此将引入decimal定义一个frange函数,math.isclose能做两个值的相近比较

import decimal
import math

def frange(self,x,y,jump):
    while x
self.dichotomy_pv()  # 实时温度pv用二分法查找对应温度 查找到并返回对应温度点的索引号
print(float(self.temp_set[self.result]))
if math.isclose(self.pv, float(self.temp_set[self.result]), abs_tol=0.4):  # 对比差值
     print('OK 差值绝对值小于0.4 获取数据并写入sheet')
     self.samples_data()# 获取数据并写入sheet里面

效果图

仪器控制-python串口通信实时获取数据并绘图_第2张图片

得到数据后开线程去绘制数据图:

self.samples_data()# 获取数据并写入sheet里面
th1 = threading.Thread(target=self.draw_pic())
th1.start()

 对于得到数据实施绘图是花了一些时间去解决的,后面想到以运行得到的数据做参数,传入绘制的函数中,四个数据图的话引入matplotlib与PyQt5新建控件,在控件中添加画布,再plot绘制图形到画布上面

import matplotlib
matplotlib.use('Qt5Agg')
# 使用 matplotlib中的FigureCanvas (在使用 Qt5 Backends中 FigureCanvas继承自QtWidgets.QWidget)
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from PyQt5 import QtCore, QtWidgets,QtGui
from PyQt5.QtWidgets import QWidget, QApplication, QGroupBox, QPushButton, QLabel, QHBoxLayout,  QVBoxLayout, QGridLayout, QFormLayout, QLineEdit, QTextEdit
import matplotlib.pyplot as plt
import sys

class my_main_window(QtWidgets.QDialog):
    def __init__(self,parent=None):
        # 父类初始化方法
        super(my_main_window,self).__init__(parent)
        # 几个QWidgets
        self.figure = plt.figure()
        self.fig_demo = plt.gcf()
        self.canvas1 = FigureCanvas(self.fig_demo)


        #```````点击绘制``````````
        self.button_plot = QtWidgets.QPushButton("绘制")

        layout = QtWidgets.QGridLayout()
        layout.addWidget(self.canvas1, 1, 0)
        layout.addWidget(self.button_plot)
        self.setLayout(layout)

仪器控制-python串口通信实时获取数据并绘图_第3张图片

这边是绘制图形函数:

def plot_data(self,Cpp,pvv):
        global a_fig
        global b_fig
        global c_fig
        global d_fig
        a_fig = plt.subplot(2, 2, 1)     #将plot的图排成两列两行四个图
        b_fig = plt.subplot(2, 2, 2)
        c_fig = plt.subplot(2, 2, 3)
        d_fig = plt.subplot(2, 2, 4)

        Cpp1_flo=float(Cpp[0])   #通过计算将数据处理成最后要绘制的数据
        print(Cpp1_flo)
        cal_Cpp1=(144*pow(10,10)*Cpp1_flo)
        jd_a=int(cal_Cpp1)

        self.Cpp_1.append(jd_a)      #添加纵坐标的值
        self.pv_list.append(pvv)     #添加横坐标的值


        a_fig.plot(self.pv_list, self.Cpp_1,"bo")  #绘制点图
        b_fig.plot(self.pv_list, self.Cpp_2,"bo")
        c_fig.plot(self.pv_list, self.Cpp_3, "bo")
        d_fig.plot(self.pv_list, self.Cpp_4, "bo")
        plt.pause(10)                              #暂停十秒以更新图像
        plt.ioff()                                 #确保图形不会出现闪退现象
  

在数据获得类中初始化:

if __name__ == '__main__':

    x= WK6500B_CpD()           #例化数据获得类
    x.connect_6500B()          #连接函数
    app = QtWidgets.QApplication(sys.argv)      
    p = my_main_window()       #例化绘图类
    x.check_pv_write_data()    #触发数据获得类的测试函数
    p.canvas1.draw()           #将图形保存
    p.show()
    app.exec()

最后将绘图类,Qt界面、数据测试类导入另外一个python文件中

from PyQt5.QtWidgets import QApplication, QMainWindow
import sys
from class_6500B_RX import *
from class_6500B_CpD_draw_pic import *



def test_CpD():
    x=WK6500B_CpD()                      #例化测试数据类
    x.connect_6500B()
    print('开始测试CpD')                  
    app = QtWidgets.QApplication(sys.argv)
    p = my_main_window()                  #例化绘图类
    x.check_pv_write_data()               #触发测试
    p.canvas1.draw()
    p.show()
    app.exec()


def test_RX():
    y=WK6500B_RX()
    y.connect_6500B()
    print('开始测试RX')
    y.check_pv_write_data()


app = QApplication(sys.argv)
MainWindow = QMainWindow()              #例化Qt界面
window = Ui_MainWindow()
window.setupUi(MainWindow)

window.pB_CpD.clicked.connect(test_CpD)          #将控件与测试函数连接
window.pB_RX.clicked.connect(test_RX)


MainWindow.show()
MainWindow.setWindowTitle('测试')

sys.exit(app.exec_())

整个代码有些冗杂和繁琐,但是能获取当前数据并绘制图形满足老师的要求,还是那句能跑就行,过程中也遇到了不少坑,收获很大。

你可能感兴趣的:(python,二分法,串口通信,matplotlib)