本文讲的是如何使用python来取代Labview去控制仪器,包括了对Pyserial 串口通信和 Pyvisa 应用编程的讲解,最后还简单介绍了用户界面GUI。总之较全面地介绍了如何用Python这种开源免费的语言来取代Labview的图形编程,使其同等地实现仪器的自动化控制。
目录
仪器控制相关的 Python 基础
pySerial 串口通信
PyVisa 应用编程
PyVisa 和 PySerial 组合
Python 代码
Pyserial 串口通信实例
Pyvisa 控制Keithley数字万用表实例
Python GUI
总结
近年来,Python 已成为世界上最流行的编程语言之一。Python 是一种通用语言,这意味着它可以用于各种应用,包括数据科学、软件和网络开发、自动化以及一般的完成工作。本文将介绍如何利用Python 实现仪器的自动化控制。下面将介绍两种仪器自动化控制类型,一个是使用简单好使的pySerial 串口通信,另一个是更规范全面的应用编程接口Pyvisa,具体使用哪种类型取决于所控制的仪器哪种更方便。
Pyserial 模块封装了串行端口的访问权限。它可以在Python 2.7 或 Python 3.4 及更新版本被使用。可以使用如下指令import pyserial:
python -m pip install pyserial
pyserial 模块可以实现串口通信,而所谓的串口通信是一种使用一条或两条传输线发送和接收数据的通信方法,数据每次以一个比特为单位连续发送和接收。
常用的串口通信协议如 RS-232C/RS-422A/RS-485, 他们都是是 EIA(电子工业协会)通信标准。这里将不详细介绍他们,可以参考:Serial communication Basic Knowledge -RS-232C/RS-422A/485- | CONTEC
了解了pySerial串口通信,它可以实现了电脑端和仪器端的指令和数据的通信。随后章节会介绍pySerial的具体实现,以及主要涉及使用Hex十六进制格式下的收发数据的处理。
PyVISA 是运用 Python 控制各种测量设备的应用编程库,不受接口(如 GPIB、RS232、USB、以太网)的限制。很明显PyVisa要较Pyserial应用更为全面,它不仅支持串口RS232通信,还支持GPIB, USB,Ethernet 的通信。在运用PyVisa时需要用到SCPI 命令也就是可编程仪器标准命令,关于PyVisa的具体内容见之前的文章。下图是PyVisa应用的示意图:
图上的HP BASIC commands 也就是SCPI 命令类型,PyVisa在GUI和仪器之间建立起了通信桥梁。Python GUI 也就是用户界面,实现更为方便地控制仪器,采集数据等操作。
通常在一个完整的控制体系中,仪器控制和数据采集都是必须的,仪器控制上可以使用PyVisa,其不受接口限制,当然也可以仅仅使用PySerial串口通信进行指令传输。而数据采集通常情况是使用串口通信传输到电脑端,这里就需要用到PySerial。下图是两者结合的一个应用实例:
简单来讲,就是用户可以运用PyVisa在USB接口下控制信号发生器产生激励信号,然后测试电路对激励作出的应答信号通过RS232串行通信传输给用户进行数据分析,感兴趣这具体实例的参考:A Python Instrument Control and Data Acquisition Suite for Reproducible Research (Journal Article) | OSTI.GOV
这里不详细介绍,仅说明PyVisa和Pyserial的结合使用,可使得系统自动化一体化。
在运用Python编程中,为了方便复用,方便扩展,方便维护,往往会用到Python Class, 在主程序中通过import类就可以很好地使用代码的复用。
import serial
import serial.tools.list_ports
from binascii import b2a_hex, a2b_hex
import threading
首先需要import必要的python库,如serial, threading等。 其中serial.tools.list_ports 是用来检测串行通信接口有哪些。串口通信接口的列表可以通过 get_serial_port_list()来实现:
def get_serial_port_list():
port_list_temp = list(serial.tools.list_ports.comports())
if len(port_list_temp) == 0:
print("No available serial port!")
return False
else:
print("Available serial ports exist:")
for my_port in port_list_temp:
print(my_port)
return True
接下来就是创建一个关于仪器的类,实现打开串口,写入数据的功能。
class Device_name:
def __init__(self):
self.device_name = "Device_name"
print(self.device_name)
def list_ports(self):
self.port_list = get_serial_port_list()
return True
def open_port(self,portx):
port_temp = portx.split()
port = port_temp[0]
self.successful = False
bps = 9600
timeout = 1
stopbits = 1
bytesize = 8
parity = 'N'
try:
self.ser = serial.Serial(port, bps, timeout=timeout, stopbits=stopbits, bytesize=bytesize, parity=my_parity)
if (self.ser.is_open):
self.successful = True
th = threading.Thread(target=read_from_serial_port, args=(self.ser,))
# 创建一个子线程去等待读数据
th.start()
except Exception:
print("open_serial_port error!")
return self.successful
def write_code(self,text):
if self.successful == True:
byte_num_sent = self.ser.write(a2b_hex(text))
def read_code(self,text):
if self.ser.in_waiting:
data = b2a_hex(self.ser.read(self.ser.in_waiting)).decode('utf-8')
def close_port(self):
self.ser.close()
关于串口的配置最重要的就是波特率和8N1。其中8 是数据的字节大小,N是奇偶校验,1是停止位。从用户介绍输入串口名如COM6,有时可能会因为格式有问题而报错,因此添加了重新转换为字符串的代码:
port_temp = portx.split()
port = port_temp[0]
在打开串口后创建了一个线程去等待读取数据,其中线程的创建中target后面的是函数名,切记不可有()!!!在数据的写入和读取中间用到的都是hex十六进制格式。在数据的写入中需要提取计算出crc校验码,下面是modbus的crc校验码计算:
def calc_crc(string):
data = bytearray.fromhex(string)
crc = 0xFFFF
for pos in data:
crc ^= pos
for i in range(8):
if (crc & 1) != 0:
crc >>= 1
crc ^= 0xA001
else:
crc >>= 1
hex_crc = hex(((crc & 0xff) << 8) + (crc >> 8))
return hex_crc
在这个过程中还会遇到想要去除十六进制的0x,下面是代码:
def remove_0x(data):
temp = ['%04X' % i for i in data]
hex_new = " ".join(temp)
return hex_new
注意的是变量data必须是整数,比如1000结果就是03E8。
以上就是串口通信的代码内容。
import pyvisa
首先import pyvisa 库,然后创建一个keithely的类。
class Keithely:
def __init__(self):
self.address = 'USB0::0x05E6::0x2110::8002753::INSTR'
self.resourceManager = pyvisa.ResourceManager()
# self.resourceManager.list_resources()
print(self.address)
其中self.address是通过resourceManager.list_resources()获取到的,每一个设备都有一个唯一的地址用于pyvisa通信。然后打开端口,连接数字万用表和关闭连接。
def open(self):
self.instance = self.resourceManager.open_resource(self.address)
self.idn = self.instance.query('*IDN?')
print(self.idn)
return True
def close(self):
if self.instance is not None:
self.instance.close()
self.instance = None
在连接成功后可以通过下面函数恢复仪器的默认设置状态。
def reset(self):
self.instance.write('*rst; status:preset; *cls')
def opc(self):
self.instance.write('*opc')
接下来是数字万用表的触发模式配置:
def trigger_config(self):
number_of_readings = 20
self.instance.write("trigger:source bus")
self.instance.write("trigger:delay:auto ON")
self.instance.write("sample:count %d" % number_of_readings)
self.instance.write("trigger:count 1")
上述代表表示触发源是bus总线,软件触发。触发后延迟是自动的,完成一次触发,采样点为20就结束触发。也意味着得到20个采样点。下面是测直流电流的函数:
def curr_meas_DC(self):
self.instance.write(':sense:function "current:DC"')
self.instance.write(':current:DC:range:auto ON')
self.instance.write("initiate")
self.instance.assert_trigger()
currents = self.instance.query_ascii_values('fetch?')
curr = sum(currents) / len(currents)
print("Average current: ", curr)
return curr
其中电流值通过query_ascii_values()方法返回,返回的20个数据求平均就是测量的电流值了。测量电流电压,交直流的函数都类似,只需设置正确的测量函数即可,这里不展示了。
if __name__ == "__main__":
instr = Keithely()
instr.open()
instr.reset()
instr.opc()
instr.trigger_config()
instr.curr_meas_DC()
instr.close()
这里是类实例化,运行后即可实现直流电流的测量。
为了方便用户操作仪器,一个图像用户界面GUI是必不可少的。常用的方法是使用Tkinter库来创建一个GUI。
import tkinter as tk
if __name__ == "__main__":
window = tk.Tk()
window.geometry("800x600")
window.title('Title')
window.configure(background='LightGrey')
window.mainloop()
上面是简单创建一个窗口,在窗口建立后可以设置frame, button,text 等区域,丰富控制界面,这里不进行详细说明。具体参考:Create UI using Tkinter in Python。一个简单的tkinter得到的UI界面如下:
在这里也可能会进行数据处理,如使用matplotlib.pyplot进行画图。使用如下库可以将画布放到tkinter的窗口中,这里也不详细介绍了。
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
基于上面的介绍可知,python完全可以用于仪器的自动化控制,通过Pyserial和Pyvisa实现了仪器端和用户端的通信。同时python可以DIY自己的用户界面,方便用户图形化对仪器操作。此外,对数据的处理python也完全可以胜任,上文并没有具体内容,但是使用python也可以实现数据的分析,绘制等等。总之Python容易上手,应用前景巨大。