PyVISA

仪表多采用 VISA 协议 不是信用卡的那个,VISA 协议可以在 Matlab 下使用,当然也能在 VB、C、C++、Python 下正常使用。考虑到后期出图采用 Python 会比较简单,后面均以 PyVISA 做示范。

章节 内容
读取示波器数据 通过 VISA 标准命令获取示波器数据
获取截屏 通过 VISA 获取截屏,并输出为文件
数据处理 获取数据,处理成表格,箱线图生成
读取示波器波形 读取示波器波形,并绘制出来

环境准备

以上的每个软件都是必须安装的,特别是 NI-VISA 驱动,安装后建议重启电脑,避免遇上找不到 pyvisa 之类的报错。

使用镜像快速安装依赖

1
pip install numpy matplotlib pyvisa -i https://mirrors.cloud.tencent.com/pypi/simple

读取示波器数据

命令解释

示波器支持 30+ 测量方式,都是可以通过 VISA 指令对仪器进行操作。在 pyvisa 里面,连接上仪器后,直接使用 query_ascii_values 函数,就可以获取到返回值。具体命令支持可以看示波器厂商提供的编程手册比方说 普源的 DS1000ZEProgrammingGuideCN_tcm4-2850.pdf (rigol.com)

比方说这里的手册的里面说了:

1
:MEASure:ITEM? VPP,CHANnel1

这条命令是查询 CHANnel1 通道 1 的峰峰值 VPP

在 pyvisa 里面,也就是只需要执行这条函数,就可以获得通道 1 的峰峰值:

1
2
3
rscope.query_ascii_values(
":MEASure:ITEM? FREQuency,CHANnel1"
)[0]

由于输出的是 list 类型的数据,取第一个 [0] 就好。

连接到仪器可以采用网线或者 USB 接口:

1
2
3
4
vrm = visa.ResourceManager()
rscope = vrm.open_resource("TCPIP0::10.22.33.216::INSTR")
rscope.timeout = 30000
# USB/RS232 连接可以用 list_resources 查找#print(vrm.list_resources())#rscope = vrm.open_resource("GPIB0::14::INSTR")#rscope = vrm.open_resource("USB0::0xxxxx::0xxxxx::DS1ZExxxx::INSTR")

网络的 IP 地址可以在示波器的 Utility -> 接口 -> IP 地址中找到,不同的示波器应该差不多。

USB 的地址需要用 vrm.list_resources() 指令查询,找到对应的 VISA 地址,填入即可:

寻找支持的仪器
1
2
3
4
5
print(vrm.list_resources())
# 连接到示波器
rscope = vrm.open_resource("USB0::0xxxxx::0xxxxx::DS1ZExxxx::INSTR")
# 检查仪器是否在线
print(rscope.query("*IDN?"))

即可。同时也适用于其他仪器,只需要查阅对应仪器的编程手册即可。

一般在上电时都喜欢一键 AUTO 解决所有问题,但是在 VISA 命令层面最好是先重置仪器,否则不会完全等效于按下 AUTO 键。

比方说 :AUToscale 只是打开自动模式,在仪器上按下 STOP 按钮,再使用 :AUToscale 命令不会让仪器重新 RUN,因此需要如下代码,来强制示波器运行:

重置仪器
1
2
3
4
5
rscope.write('*RST;*CLS')
# 全自动开
rscope.write(":AUToscale")
# 自动光标开
rscope.write(":CURSor:MODE AUTO")

自动光标指令最好打开,可以有效避免各种读数时的玄学错误。

同样可以在后面加上自动光标的追踪,可以根据需求自动追踪测量值:

光标1 追踪 CH1
1
2
3
4
5
6
7
8
9
rscope.write(":CURSor:AUTO:SOURce1 CHANnel1")
# 光标2 追踪 CH2
rscope.write(":CURSor:AUTO:SOURce2 CHANnel2")
# 获取 1-2, 的 X 值# 获取 Y 轴上的差值:CURSor:TRACk:YDELta?
DS1_S2 = rscope.query_ascii_values(
":CURSor:AUTO:XDELta?"
)[0]
# 在屏幕上实时显示第五个测量项目的自动光标追踪
rscope.write(":CURSor:AUTO:ITEM5")

会在示波器上直观第显示两条追踪线。

实际上不介意小插曲的话,不重置仪器也是可以的,直接使用全自动 :AUToscale 会更快。重置 *RST;*CLS 仪器后,再进行全自动 :AUToscale 会有 3~7 秒时间,也就是信号继电器会开开关关,哒~哒~哒~~ 几声 。这段时间是无法读取数据的,因此最好等待 5 秒,避免读数出错。

1
time.sleep(5)

最后就可以读取数据,并写入 CSV 当中去了,但注意写入数据时候的单位,一般是采用科学计数法,并且是标准单位,不带 mV 之类的。直接使用 Python 的 numpy 库可以自动处理,没有符号上的问题,但是在别的地方处理时候应该要稍微注意一下:

读取 CH1 的频率
1
2
3
4
5
6
7
8
9
10
11
12
13
FRE_CH1 = rscope.query_ascii_values(
":MEASure:ITEM? FREQuency,CHANnel1"
)[0]
# 组合成 List
simple_data = [
FRE_CH1,
VPP_CH1,
VPP_CH2,
RDELay_CH1_CH2,
FDELay_CH1_CH2
]
# 写入 CSV
writer.writerow(simple_data)

示例

这里给出完整的代码示例,运行后,轻轻按下 Ctrl + C 一次,即可退出并保存数据:
scope.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
"""
Title: Pyvisa,针对 RIGOL DS1102 Z-E 的截图
Author: 0xac <0xac.cn>
Data: 20230527

Pyvisa,针对 RIGOL DS1102 Z-E 的数据采样
数据采集:
+ Freq CH1,
+ Vpp CH1,
+ Vpp CH2,
+ Rise Delay CH1 -> CH2,
+ Fail Delay CH1 -> CH2

必须安装 NI-VISA 驱动
https://www.ni.com/zh-cn/support/downloads/drivers/download.ni-visa.html#346210
依赖:
pip install pyvisa

This code is licensed under the GNU GPLv3 License
<https://www.gnu.org/licenses/gpl-3.0.txt>.
You are free to use it under the condition of compliance with the license.

Copyright 2023 (C) 0xac <0xac.cn> All rights reserved.
"""

import time
import csv

import pyvisa as visa


vrm = visa.ResourceManager()
rscope = vrm.open_resource("TCPIP0::10.22.33.216::INSTR")
rscope.timeout = 30000
# USB/RS232 连接可以用 list_resources 查找#print(vrm.list_resources())#rscope = vrm.open_resource("GPIB0::14::INSTR")#rscope = vrm.open_resource("USB0::0xxxxx::0xxxxx::DS1ZExxxx::INSTR")
# 查询远端仪器是否在线
print(rscope.query("*IDN?")) # RIGOL TECHNOLOGIES,DS1102Z-E,DS1ZExxxx,00.06.02
# 复位仪器,减少魔法问题
print(rscope.write('*RST;*CLS'))# 12# 全自动模式
print(rscope.write(":AUToscale"))# 14# 打开自动光标,使测量有效
print(rscope.write(":CURSor:MODE"))# 11

time.sleep(5)
print("等待 自动检测 完成, 3~7s")

filename = 'data.csv'
with open(filename, 'w', newline='') as file:
writer = csv.writer(file)
count = 0
try:
print("按一次 Ctrl + C 结束采集")
while(True):
# 查询 通道 1 VPP
#print(rscope.query_ascii_values(
# ":MEASure:ITEM? VPP,CHANnel1"
# ))
# 查询 通道 2 VPP
#print(rscope.query_ascii_values(
# ":MEASure:ITEM? VPP,CHANnel2"
# ))
# 查询相位 通道 1 - 通道 2
#print(rscope.query_ascii_values(
# ":MEASure:ITEM? RPHase,CHANnel1,CHANnel2"
# ))
#print(rscope.query_ascii_values(
# ":MEASure:ITEM? FPHase,CHANnel1,CHANnel2"
# ))
# 查询频率 通道1
#print(rscope.query_ascii_values(
# ":MEASure:ITEM? FREQuency,CHANnel1"
# ))

# 每 0.2 秒进行采样
time.sleep(0.2)
print("第 %d 次采样" % count)
count += 1
try:
FRE_CH1 = rscope.query_ascii_values(
":MEASure:ITEM? FREQuency,CHANnel1"
)[0] # 通道 1 频率
VPP_CH1 = rscope.query_ascii_values(
":MEASure:ITEM? VPP,CHANnel1"
)[0] # 通道 1 Vpp
VPP_CH2 = rscope.query_ascii_values(
":MEASure:ITEM? VPP,CHANnel2"
)[0] # 通道 2 Vpp
RDELay_CH1_CH2 = rscope.query_ascii_values(
":MEASure:ITEM? RDELay,CHANnel1,CHANnel2"
)[0] # 通道 1 和 2 上升沿相位差
FDELay_CH1_CH2 = rscope.query_ascii_values(
":MEASure:ITEM? FDELay,CHANnel1,CHANnel2"
)[0] # 通道 1 和 2 下降沿相位差
#RPHase_CH1_CH2 = rscope.query_ascii_values(
# ":MEASure:ITEM? RPHase,CHANnel1,CHANnel2"
# )[0] # 通道 1 和 2 上升沿相位差
#RPHase_CH1_CH2 = rscope.query_ascii_values(
# ":MEASure:ITEM? FPHase,CHANnel1,CHANnel2"
# )[0] # 通道 1 和 2 下降沿相位差
simple_data = [
FRE_CH1,
VPP_CH1,
VPP_CH2,
RDELay_CH1_CH2,
FDELay_CH1_CH2
]
writer.writerow(simple_data)
except Exception as e:
print("请求可能超时 %s" % e)
except KeyboardInterrupt as e :
print("程序停止")
rscope.close()

方便观察的功能

如果有 FFT(快速傅里叶变换)需求,要打印频谱到示波器上的,可以打开 FFT:

打开 CH1 上的 MATH,设置为 FFT
1
2
3
print(rscope.write(":MATH:FFT:SOURce CHANnel1"))
# 打开全屏显示
print(rscope.write(":MATH:FFT:SPLit OFF"))

杂波多,波形余晖有很多跳动的曲线怎么办,就用滤波器:

1
2
3
4
5
6
7
8
9
print(rscope.write("::MATH:FILTer:TYPE"))# TYPE 可以为以下四种#LPASs 低通,ω < ωc1
#HPASs 高通,ω > ωc1
#BPASs 带通,ωc1 < ω < ωc2
#BSTOP 带阻,ω < ωc1, ωc2 < ω

# 设置 ωc1 截止频率
print(rscope.write(":MATH:FILTer:W1 <freq1>"))
# 设置 ωc2 截止频率
print(rscope.write(":MATH:FILTer:W2 <freq1>"))

低通滤波器建议在示波器上设置,因为有些参数有范围限制,代码里不好体现。

获取截屏

截屏比较复杂,这里只需要运行程序就可以截屏。
screen.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
"""
Title: Pyvisa,针对 RIGOL DS1102 Z-E 的截图
Author: 0xac <0xac.cn>
Data: 20230527

必须安装 NI-VISA 驱动
https://www.ni.com/zh-cn/support/downloads/drivers/download.ni-visa.html#346210
依赖:
pip install pyvisa

This code is licensed under the GNU GPLv3 License
<https://www.gnu.org/licenses/gpl-3.0.txt>.
You are free to use it under the condition of compliance with the license.

Copyright 2023 (C) 0xac <0xac.cn> All rights reserved.
"""

import time
import csv
from datetime import datetime

import pyvisa as visa

vrm = visa.ResourceManager()
# USB/RS232 连接可以用 list_resources 查找
print(vrm.list_resources())
rscope = vrm.open_resource("TCPIP0::10.22.33.216::INSTR")
#rscope = vrm.open_resource("USB0::0xxxxx::0xxxxx::DS1ZExxxx::INSTR")

rscope.timeout = 30000

# 查询远端仪器是否在线
print(rscope.query("*IDN?"))
# RIGOL TECHNOLOGIES,DS1102Z-E,DS1ZExxx,00.06.02


# STM 协议解析
# :DISPlay:DATA? <彩色>,<反相>,<格式>
rscope.write(':DISPlay:DATA? ON,OFF,PNG')
data = rscope.read_raw()
# SMT 协议 #N 中 N 代表有效数据长
n = int(data[1:2])
# 头部长度 = 全数据长度 - 有效数据长
len = (len(data)) - int(data[2:n+2])
# 先导长 2,头部 len(data) - N,有效 N
data = data[2+n:]


now = datetime.now()
date_string = now.strftime("%Y-%m-%d_%H-%M-%S")
screenPath = r'screen-'+date_string+'.png'
with open(screenPath, "wb") as file:
file.write(data)

rscope.close()

数据处理

数据处理用到了 mathplotlib 库,建议在稍微了解的基础上 从 Cheatsheet 上入手:
Matplotlib cheatsheets — Visualization with Python

普通绘图

数据处理是针对一阶二阶线性系统实验进行的,可以自动分析出图形,用箱形图的分析在路上了(新建文件夹)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
"""
Title: 一个数据处理的示例
Author: 0xac <0xac.cn>
Data: 20230527

依赖:
pip install numpy matplotlib

This code is licensed under the GNU GPLv3 License
<https://www.gnu.org/licenses/gpl-3.0.txt>.
You are free to use it under the condition of compliance with the license.

Copyright 2023 (C) 0xac <0xac.cn> All rights reserved.
"""

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt

# 数据读入
filename = 'data.csv'
data = np.loadtxt(open(filename,"rb"),delimiter=",")

# 转换
Freq = np.array([sublist[0] for sublist in data])
Vpp1 = np.array([sublist[1] for sublist in data])
Vpp2 = np.array([sublist[2] for sublist in data])
RPHase12 = np.array([sublist[3] for sublist in data])
FPHase12 = np.array([sublist[4] for sublist in data])

# 运算
Hj = Vpp1 / Vpp2
a1 = RPHase12 * Freq * 2 * np.pi
a2 = FPHase12 * Freq * 2 * np.pi

# 设置中文字体
plt.rcParams['font.family'] = 'Microsoft YaHei'
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']

fig, axs = plt.subplots(2, 1)

axs[0].scatter(Freq, Hj, marker='+')
axs[0].set_title("幅频特性")
axs[0].set_xlabel("Freq(Hz)")
axs[0].set_ylabel(r"$H(j \omega)$")
# 需要约束绘制范围,避免绘图时起飞,[xmin, xmax, ymin, ymax]
axs[0].axis([1000, 600000, -0.5, 1.5])


axs[1].scatter(Freq, a1, marker='+')
axs[1].set_title("相频特性")
axs[1].set_xlabel("Freq(Hz)")
axs[1].set_ylabel(r"$\alpha$")
axs[1].axis([1000, 600000, -0.5, 1.5])

plt.subplots_adjust(hspace=1)

plt.show()

箱线绘图

更高级的可以用箱线图:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
"""
Title: 一个箱线图的示例
Author: 0xac <0xac.cn>
Data: 20230527

依赖:
pip install numpy matplotlib

This code is licensed under the GNU GPLv3 License
<https://www.gnu.org/licenses/gpl-3.0.txt>.
You are free to use it under the condition of compliance with the license.

Copyright 2023 (C) 0xac <0xac.cn> All rights reserved.
"""

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt

# 数据读入
filename = 'data.csv'
data = np.loadtxt(open(filename,"rb"),delimiter=",")

# 转换
Freq = np.array([sublist[0] for sublist in data])
Vpp1 = np.array([sublist[1] for sublist in data])
Vpp2 = np.array([sublist[2] for sublist in data])
RPHase12 = np.array([sublist[3] for sublist in data])
FPHase12 = np.array([sublist[4] for sublist in data])

# 运算
Hj = Vpp1 / Vpp2
a1 = RPHase12 * Freq * 2 * np.pi
a2 = FPHase12 * Freq * 2 * np.pi


# 频率合并,把相近频率对应的数据合并到一个数组
# data_f[i] - F_STEP/2 <= Freq[j] <= data_f[i] + F_STEP/2
# 然后把每个频率合并的数据组成二维数组
F_MAX = 1750 * 10**3
F_MINI = 10 * 10**3
F_STEP = 10 * 10**3

# 标准差过滤掉仪器测量误差(信号未稳定产生的尖刺)
#filtered = lambda data: data[:, np.all(np.abs((np.std(data, axis=0) - np.mean(data, axis=0)) / np.reshape(-1, 1)) < 3, axis=0)]
#Hj = filtered(Hj)
#a1 = filtered(a1)
#a2 = filtered(a2)
# 这个方法不好,会导致数组长度不一致


# 这里就是在设置分散的频率
data_f = [ x for x in range(F_MINI, F_MAX, F_STEP)]

data_h = []
data_a1 = [] # 2维
data_a2 = [] # 2维

for i in range(0, len(data_f)):
h_box = []
a1_box = []
a2_box = []
# 合并每组频率
for j in range(0, len(Hj)):
if (data_f[i] - F_STEP/2 <= Freq[j] <= data_f[i] + F_STEP/2):
h_box.append(Hj[j])
a1_box.append(a1[j])
a2_box.append(a2[j])
data_h.append(h_box)
data_a1.append(a1_box)
data_a2.append(a2_box)

# 重排列 x 轴标签,暂时没有更好的方法了。
xtick_lablels = [ x for x in range(int(F_MINI/1000), int(F_MAX/1000), int(F_STEP/1000))]

# 设置中文字体
plt.rcParams['font.family'] = 'Microsoft YaHei'
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']

# 多元绘图
fig, axs = plt.subplots(2, 1)

# boxplot 不建议这个参数,容易生成看不懂的图形
#positions=data_f
# 约束数据范围,有效防止起飞
#whis=1.5, showfliers=False
axs[0].boxplot(data_h, whis=1.5, showfliers=False)
axs[0].set_title("幅频特性")
axs[0].set_xlabel("Freq(Hz)")
axs[0].set_ylabel(r"$kH(j \omega)$")
axs[0].axis([0, len(data_f)+1, 0, 1])
axs[0].set_xticklabels(xtick_lablels, rotation=45)

axs[1].boxplot(data_a1, whis=1.5, showfliers=False)
axs[1].set_title("幅频特性")
axs[1].set_xlabel("Freq(Hz)")
axs[1].set_ylabel(r"$kH(j \omega)$")
axs[1].axis([0, len(data_f)+1, -0.5, 1])
axs[1].set_xticklabels(xtick_lablels, rotation=45)

plt.subplots_adjust(hspace=1)
plt.show()

理论上可以用 VISA 协议控制信号发生器,进行进一步的波形生成,但因为预算不足,就没有买了。

读取示波器数据
数据处理
其他语言

读取示波器波形

TMC 头部全长 11Bit,结构如下

字节位(16bit) 0 1 2 ~ 10 11 ~ (n-1) n
解释 # TMC 头长度 数据长度 n 数据 data 0x0A (结束符)

在使用 :wav:data? 指令查询到数据后,返回的是带 TMC 头的数据,需要从中解析出数据。

简单的图形读取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
"""
Title: Pyvisa,针对 RIGOL DS1102 Z-E 波形读取
Author: 0xac <0xac.cn>
Data: 20230824

必须安装 NI-VISA 驱动
https://www.ni.com/zh-cn/support/downloads/drivers/download.ni-visa.html#346210
依赖:
pip install pyvisa numpy matplotlib

This code is licensed under the GNU GPLv3 License
<https://www.gnu.org/licenses/gpl-3.0.txt>.
You are free to use it under the condition of compliance with the license.

Copyright 2023 (C) 0xac <0xac.cn> All rights reserved.
"""
import pyvisa as visa
import numpy as np
import matplotlib.pyplot as plt

vrm = visa.ResourceManager()

try:
# 创建 VISA 对象
rscope = vrm.open_resource('USB0::0x1AB1::0x0517::DS1ZE225115369::INSTR')
except Exception as e:
print("打开仪器失败:", str(e))
print("目前在线的仪器为:", vrm.list_resources())
exit(1)

# 设置设备属性
rscope.timeout = 5000

print("连接到:", rscope.query("*IDN?"))

# 打开设备
rscope.open()

# 读取波形
rscope.write(':wav:data?')
data = rscope.read_raw(2048)


# 关闭设备
rscope.close()
vrm.close()


# TMC 头解析,头部全长 11Bit
# Byte: | # | TMC 头长度 | 数据长度 n | 数据 data | 0x0A (结束符)
# 8位 | 0 | 1 | 2 - 10 | 11 - (n-1) | n
# 波形数据在 data 内。
wave = np.array(list(data[11:-1]), dtype=np.uint8)
wave = wave.view(dtype=np.int8)
wave = wave.tolist()


fft_res = np.fft.fft(wave, 2048)
magnitude = np.abs(fft_res) # 幅度谱
phase = np.angle(fft_res) # 相位谱
magnitude_log = 20 * np.log10(magnitude) # 幅度取对数

# 中文字体,防止混沌
plt.rcParams['font.family'] = 'Microsoft YaHei'
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']

# 绘图
fig, axs = plt.subplots(2, 2)

# 绘制原始信号
axs[0][0].plot(wave, linewidth=0.5)
axs[0][0].set_xlabel('采样')
axs[0][0].set_ylabel('幅度')
axs[0][0].set_title('原始信号')

# 幅度谱 - 对数坐标
axs[0][1].plot(magnitude_log, linewidth=0.5)
axs[0][1].set_xlabel('频率(偏移量)')
axs[0][1].set_ylabel('幅度 (dB)')
axs[0][1].set_title('幅度谱')

# 幅度谱
axs[1][0].plot(magnitude, linewidth=0.5)
axs[1][0].set_xlabel('频率(偏移量)')
axs[1][0].set_ylabel('幅度')
axs[1][0].set_title('频率谱')

# 相位谱
axs[1][1].plot(phase, linewidth=0.5)
axs[1][1].set_xlabel('频率(偏移量)')
axs[1][1].set_ylabel('相位')
axs[1][1].set_title('相位谱')

# 图形调节
plt.subplots_adjust(hspace=0.5)
# 绘图
plt.show()

连续图形读取绘制

既然单独捕获有了,那么必然有连续捕获的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
"""
Title: Pyvisa,针对 RIGOL DS1102 Z-E 的波形连续读取
Author: 0xac <0xac.cn>
Data: 20230824

必须安装 NI-VISA 驱动
https://www.ni.com/zh-cn/support/downloads/drivers/download.ni-visa.html#346210
依赖:
pip install pyvisa numpy matplotlib

This code is licensed under the GNU GPLv3 License
<https://www.gnu.org/licenses/gpl-3.0.txt>.
You are free to use it under the condition of compliance with the license.

Copyright 2023 (C) 0xac <0xac.cn> All rights reserved.
"""
import pyvisa as visa
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

vrm = visa.ResourceManager()
#

try:
# 创建 VISA 对象
rscope = vrm.open_resource('USB0::0x1AB1::0x0517::DS1ZE225115369::INSTR')
except Exception as e:
print("打开仪器失败:", str(e))
print("目前在线的仪器为:", vrm.list_resources())
exit(1)

# 设置设备属性
rscope.timeout = 5000

print("连接到:", rscope.query("*IDN?"))

# 打开设备
rscope.open()

# 查询并显示采样率
rscope.write(':ACQuire:SRATe?')
sample_rate = rscope.read()
# 查询并显示垂直偏移
rscope.write(':CHANnel1:OFFSet?')
vertical_offset = rscope.read()
# 查询并显示垂直分度
rscope.write(':CHANnel1:SCALe?')
vertical_scale = rscope.read()
# 查询并显示垂直单位
rscope.write(':CHANnel1:UNITs?')
vertical_units = rscope.read()
# 查询并显示水平偏移
rscope.write(':TIMebase:DELay:OFFSet?')
horizontal_offset = rscope.read()
# 查询并显示水平分度
rscope.write(':TIMebase:DELay:SCALe?')
horizontal_scale = rscope.read()

print(f"采样率 (Sa/s): {sample_rate} ")
print(f"垂直偏移 (V): {vertical_offset}")
print(f"垂直分度 (V): {vertical_scale}")
print(f"垂直单位: {vertical_units}")
print(f"水平偏移 (ns/div): {horizontal_offset}")
print(f"水平分度 (ns/div): {horizontal_scale} ")

# 中文字体,防止混沌
plt.rcParams['font.family'] = 'Microsoft YaHei'
plt.rcParams['font.sans-serif'] = ['Microsoft YaHei']

# 绘图
fig, axs = plt.subplots(2, 2)
lines = [1, 2, 3, 4]

# 绘制原始信号
lines[0], = axs[0][0].plot([], [], linewidth=0.5)
axs[0][0].set_xlabel('采样')
axs[0][0].set_ylabel('幅度')
axs[0][0].set_title('原始信号')


# 幅度谱 - 对数坐标
lines[1], = axs[0][1].plot([], [], linewidth=0.5)
axs[0][1].set_xlabel('频率(偏移量)')
axs[0][1].set_ylabel('幅度 (dB)')
axs[0][1].set_title('幅度谱')

# 幅度谱
lines[2], = axs[1][0].plot([], [], linewidth=0.5)
axs[1][0].set_xlabel('频率(偏移量)')
axs[1][0].set_ylabel('幅度')
axs[1][0].set_title('频率谱')

# 相位谱
lines[3], = axs[1][1].plot([], [], linewidth=0.5)
axs[1][1].set_xlabel('频率(偏移量)')
axs[1][1].set_ylabel('相位')
axs[1][1].set_title('相位谱')

def update(frame):
# 读取波形
rscope.write(':wav:data?')
data = rscope.read_raw(2048)

# TMC 头解析,头部全长 11Bit
wave = np.array(list(data[11:-1]), dtype=np.uint8)
wave = wave.view(dtype=np.int8)
wave = wave.tolist()

# FFT 信号处理
fft_res = np.fft.fft(wave, 2048)
magnitude = np.abs(fft_res) # 幅度谱
phase = np.angle(fft_res) # 相位谱
magnitude_log = 20 * np.log10(magnitude) # 幅度取对数

# 绘制原始信号
axs[0][0].set_xlim(0, len(wave))
axs[0][0].set_ylim(min(wave), max(wave))
lines[0].set_data(range(len(wave)), wave)

# 幅度谱 - 对数坐标
axs[0][1].set_xlim(0, len(magnitude_log))
axs[0][1].set_ylim(min(magnitude_log), max(magnitude_log))
lines[1].set_data(range(len(magnitude_log)), magnitude_log)

# 幅度谱
axs[1][0].set_xlim(0, len(magnitude))
axs[1][0].set_ylim(min(magnitude), max(magnitude))
lines[2].set_data(range(len(magnitude)), magnitude)

# 相位谱
axs[1][1].set_xlim(0, len(phase))
axs[1][1].set_ylim(min(phase), max(phase))
lines[3].set_data(range(len(phase)), phase)
return lines,

# 动画对象
ani = FuncAnimation(fig, update, frames=10, interval=100)

# 图形调节
plt.subplots_adjust(hspace=0.5)
# 绘图
plt.show()

# 关闭设备
rscope.close()
vrm.close()

其他语言

不止用 Python 可以处理数据,使用 Matlab,C++,甚至 Excel 都可以。

具体而言,详见各厂商提供的示波器编程手册。