pyinsts 0.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- __init__.py +0 -0
- _version.py +15 -0
- pyinsts/__init__.py +0 -0
- pyinsts/common/__init__.py +3 -0
- pyinsts/common/log_data.py +96 -0
- pyinsts/libs/__init__.py +11 -0
- pyinsts/libs/baseinstrument.py +232 -0
- pyinsts/libs/data/__init__.py +3 -0
- pyinsts/libs/data/load_config.py +47 -0
- pyinsts/libs/keysight/N9020B.py +359 -0
- pyinsts/libs/keysight/__init__.py +3 -0
- pyinsts/libs/rs/Fswp.py +494 -0
- pyinsts/libs/rs/__init__.py +3 -0
- pyinsts-0.0.3.dist-info/METADATA +43 -0
- pyinsts-0.0.3.dist-info/RECORD +18 -0
- pyinsts-0.0.3.dist-info/WHEEL +5 -0
- pyinsts-0.0.3.dist-info/licenses/LICENSE +21 -0
- pyinsts-0.0.3.dist-info/top_level.txt +3 -0
__init__.py
ADDED
|
File without changes
|
_version.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
def _get_version() -> str:
|
|
2
|
+
|
|
3
|
+
from importlib.resources import files
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import versioningit
|
|
7
|
+
|
|
8
|
+
module_path = files("pyinsts")
|
|
9
|
+
if isinstance(module_path, Path):
|
|
10
|
+
return versioningit.get_version(project_dir=Path(module_path).parent.parent)
|
|
11
|
+
else:
|
|
12
|
+
return "0.0"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
__version__ = "0.0.3"
|
pyinsts/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""
|
|
2
|
+
日志处理
|
|
3
|
+
"""
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
from logging.handlers import RotatingFileHandler
|
|
7
|
+
import colorlog # 导入 colorlog
|
|
8
|
+
def setup_logging(log_file=None, log_level=logging.INFO, max_bytes=1 * 1024 * 1024, backup_count=5):
|
|
9
|
+
"""
|
|
10
|
+
配置日志记录,包括日志轮换和控制台输出,支持彩色日志
|
|
11
|
+
:param log_file: 日志文件路径,如果为None则使用默认路径
|
|
12
|
+
:param log_level: 日志等级
|
|
13
|
+
:param max_bytes: 单个日志文件的最大字节数,默认为1MB
|
|
14
|
+
:param backup_count: 保留的日志文件备份数
|
|
15
|
+
"""
|
|
16
|
+
if log_file is None:
|
|
17
|
+
log_file = os.getenv('LOG_FILE_PATH', 'docs/log/test.log')
|
|
18
|
+
|
|
19
|
+
# 确保日志文件夹存在
|
|
20
|
+
log_dir = os.path.dirname(log_file)
|
|
21
|
+
if not os.path.exists(log_dir):
|
|
22
|
+
os.makedirs(log_dir)
|
|
23
|
+
|
|
24
|
+
# 创建根记录器
|
|
25
|
+
logger = logging.getLogger()
|
|
26
|
+
logger.setLevel(log_level)
|
|
27
|
+
|
|
28
|
+
# 清除已有的处理器,避免重复添加
|
|
29
|
+
if logger.hasHandlers():
|
|
30
|
+
logger.handlers.clear()
|
|
31
|
+
|
|
32
|
+
# 创建文件处理器,支持日志轮换
|
|
33
|
+
file_handler = RotatingFileHandler(log_file, mode='w', maxBytes=max_bytes, backupCount=backup_count)
|
|
34
|
+
file_handler.setLevel(log_level)
|
|
35
|
+
|
|
36
|
+
# 创建控制台处理器,日志输出到控制台
|
|
37
|
+
console_handler = logging.StreamHandler()
|
|
38
|
+
console_handler.setLevel(log_level)
|
|
39
|
+
|
|
40
|
+
# 定义日志格式
|
|
41
|
+
log_format = '%(asctime)s - %(levelname)-8s - %(message)-60s - [%(filename)-15s:%(lineno)-4d]'
|
|
42
|
+
color_format = "%(log_color)s" + log_format
|
|
43
|
+
|
|
44
|
+
# 设置文件处理器的格式
|
|
45
|
+
file_formatter = logging.Formatter(log_format)
|
|
46
|
+
file_handler.setFormatter(file_formatter)
|
|
47
|
+
|
|
48
|
+
# 设置控制台处理器的彩色格式
|
|
49
|
+
color_formatter = colorlog.ColoredFormatter(
|
|
50
|
+
color_format,
|
|
51
|
+
log_colors={
|
|
52
|
+
'DEBUG': 'cyan',
|
|
53
|
+
'INFO': 'green',
|
|
54
|
+
'WARNING': 'yellow',
|
|
55
|
+
'ERROR': 'red',
|
|
56
|
+
'CRITICAL': 'bold_red',
|
|
57
|
+
}
|
|
58
|
+
)
|
|
59
|
+
console_handler.setFormatter(color_formatter)
|
|
60
|
+
|
|
61
|
+
# 将处理器添加到记录器中
|
|
62
|
+
logger.addHandler(file_handler)
|
|
63
|
+
logger.addHandler(console_handler)
|
|
64
|
+
|
|
65
|
+
# 添加日志分隔符
|
|
66
|
+
logger.info("=" * 60)
|
|
67
|
+
logger.info("新日志开始".center(60, ' '))
|
|
68
|
+
logger.info("=" * 60)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def log_data(data):
|
|
72
|
+
"""
|
|
73
|
+
记录数据的日志
|
|
74
|
+
"""
|
|
75
|
+
logging.info(f"数据记录: {data}")
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# 通用异常处理装饰器
|
|
79
|
+
def log_exceptions(func):
|
|
80
|
+
def wrapper(*args, **kwargs):
|
|
81
|
+
try:
|
|
82
|
+
return func(*args, **kwargs)
|
|
83
|
+
except Exception as e:
|
|
84
|
+
logging.error(f"{func.__name__} 执行时发生错误", exc_info=True)
|
|
85
|
+
raise e
|
|
86
|
+
|
|
87
|
+
return wrapper
|
|
88
|
+
|
|
89
|
+
# 示例:调用日志设置
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
setup_logging(log_file='/Users/zhangxianping/Desktop/pythonProject/SiCore_ChipTest/docs/log//test.log', log_level=logging.DEBUG)
|
|
92
|
+
log_data("这是一个测试数据记录")
|
|
93
|
+
logging.debug("这是一条调试信息")
|
|
94
|
+
logging.warning("这是一条警告信息")
|
|
95
|
+
logging.error("这是一条错误信息")
|
|
96
|
+
logging.critical("这是一条严重错误信息")
|
pyinsts/libs/__init__.py
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
import pyvisa
|
|
5
|
+
from typing import Callable, Optional
|
|
6
|
+
import os
|
|
7
|
+
import inspect
|
|
8
|
+
from functools import wraps
|
|
9
|
+
|
|
10
|
+
from pyvisa import VisaIOError
|
|
11
|
+
|
|
12
|
+
from src.pyinsts.libs.data import load_config
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def handle_instrument_error(func: Callable) -> Callable:
|
|
16
|
+
"""仪器操作错误处理装饰器"""
|
|
17
|
+
@wraps(func)
|
|
18
|
+
def wrapper(*args, **kwargs):
|
|
19
|
+
try:
|
|
20
|
+
return func(*args, **kwargs)
|
|
21
|
+
except Exception as e:
|
|
22
|
+
logging.error(f"{args[0].model} {func.__name__}失败: {e}")
|
|
23
|
+
raise
|
|
24
|
+
|
|
25
|
+
return wrapper
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class BaseInstrument:
|
|
29
|
+
"""基础仪器控制类"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, address: Optional[str] = None, config_path: str = "config.yaml",
|
|
32
|
+
model: str = "E5052B"):
|
|
33
|
+
"""初始化仪器连接
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
address: 仪器地址,例如:"USB0::0x0957::0x0E09::MY47402409::0::INSTR"
|
|
37
|
+
config_path: 配置文件路径
|
|
38
|
+
model: 仪器型号
|
|
39
|
+
"""
|
|
40
|
+
# 设置基本属性
|
|
41
|
+
self.model = model
|
|
42
|
+
self.address = self._get_instrument_address(address, config_path)
|
|
43
|
+
|
|
44
|
+
# 设置默认超时参数
|
|
45
|
+
|
|
46
|
+
self.opc_timeout = 100 # 默认OPC超时时间100秒
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
self.opc_poll_interval = 0.01 # 默认OPC轮询间隔0.01秒
|
|
50
|
+
|
|
51
|
+
# 连接仪器
|
|
52
|
+
self.idn = self._connect_instrument()
|
|
53
|
+
|
|
54
|
+
# def __del__(self):
|
|
55
|
+
# """
|
|
56
|
+
# del的调用时机不确定,依赖于Python的垃圾回收机制。
|
|
57
|
+
# :return:
|
|
58
|
+
# """
|
|
59
|
+
# self.close()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _get_instrument_address(self, address: str, config_path: str) -> str:
|
|
63
|
+
"""获取仪器地址
|
|
64
|
+
|
|
65
|
+
优先使用传入的地址,否则从配置文件读取
|
|
66
|
+
支持.yaml和.json格式的配置文件
|
|
67
|
+
"""
|
|
68
|
+
if address:
|
|
69
|
+
return address
|
|
70
|
+
|
|
71
|
+
# 处理配置文件路径
|
|
72
|
+
if not os.path.isabs(config_path):
|
|
73
|
+
# 获取继承类的文件所在目录
|
|
74
|
+
derived_class_file = inspect.getmodule(self.__class__).__file__
|
|
75
|
+
derived_class_dir = os.path.dirname(
|
|
76
|
+
os.path.abspath(derived_class_file))
|
|
77
|
+
config_path = os.path.join(derived_class_dir, config_path)
|
|
78
|
+
|
|
79
|
+
# 如果没有指定扩展名,先尝试.yaml,再尝试.json
|
|
80
|
+
if not os.path.splitext(config_path)[1]:
|
|
81
|
+
yaml_path = config_path + '.yaml'
|
|
82
|
+
json_path = config_path + '.json'
|
|
83
|
+
|
|
84
|
+
if os.path.exists(yaml_path):
|
|
85
|
+
config_path = yaml_path
|
|
86
|
+
elif os.path.exists(json_path):
|
|
87
|
+
config_path = json_path
|
|
88
|
+
|
|
89
|
+
config = load_config(config_path)
|
|
90
|
+
address = config.get("instruments", {}).get(self.model)
|
|
91
|
+
if not address:
|
|
92
|
+
raise ValueError(f"未找到{self.model}的仪器地址配置")
|
|
93
|
+
|
|
94
|
+
return address
|
|
95
|
+
|
|
96
|
+
@handle_instrument_error
|
|
97
|
+
def _connect_instrument(self) -> None:
|
|
98
|
+
"""建立仪器连接,返回仪器标识"""
|
|
99
|
+
max_retries = 3 # 最大重试次数
|
|
100
|
+
|
|
101
|
+
retry_count = 0
|
|
102
|
+
|
|
103
|
+
while retry_count < max_retries:
|
|
104
|
+
try:
|
|
105
|
+
print(f"型号:{self.model}, 地址:{self.address}")
|
|
106
|
+
self.rm = pyvisa.ResourceManager()
|
|
107
|
+
self.instrument = self.rm.open_resource(self.address)
|
|
108
|
+
|
|
109
|
+
self.instrument.write('*CLS')
|
|
110
|
+
logging.info('清除仪器寄存器信息')
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# 验证连接
|
|
114
|
+
idn = self.instrument.query("*IDN?")
|
|
115
|
+
logging.info(f'仪器标识:{idn}, 成功连接{self.model}')
|
|
116
|
+
return idn
|
|
117
|
+
except(pyvisa.VisaIOError, pyvisa.VisaTypeError, TimeoutError):
|
|
118
|
+
retry_count += 1
|
|
119
|
+
if retry_count < max_retries:
|
|
120
|
+
logging.info(f"等待1秒后重试...")
|
|
121
|
+
time.sleep(1)
|
|
122
|
+
# 确保之前的连接被关闭
|
|
123
|
+
if hasattr(self, 'instrument'):
|
|
124
|
+
self.instrument.close()
|
|
125
|
+
if hasattr(self, 'rm'):
|
|
126
|
+
self.rm.close()
|
|
127
|
+
else:
|
|
128
|
+
logging.error(f"连接失败次数超过最大重试次数({max_retries})")
|
|
129
|
+
raise
|
|
130
|
+
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
@handle_instrument_error
|
|
135
|
+
def write(self, command: str,
|
|
136
|
+
check_complete: bool = False) -> None:
|
|
137
|
+
"""写入命令
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
command: SCPI命令
|
|
141
|
+
check_complete: 是否等待命令完成
|
|
142
|
+
|
|
143
|
+
example:
|
|
144
|
+
self.write(f'SENSE1:FREQ:CENT 1GHz')
|
|
145
|
+
"""
|
|
146
|
+
self.instrument.timeout = 100000
|
|
147
|
+
self.instrument.write(command)
|
|
148
|
+
if check_complete:
|
|
149
|
+
self.wait_opc()
|
|
150
|
+
logging.debug(f"{self.model} 写入: {command}")
|
|
151
|
+
|
|
152
|
+
@handle_instrument_error
|
|
153
|
+
def query(self, command: str,
|
|
154
|
+
check_complete: bool = False) -> str:
|
|
155
|
+
"""查询命令
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
command: SCPI命令
|
|
159
|
+
check_complete: 是否等待命令完成
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
查询结果
|
|
163
|
+
|
|
164
|
+
example:
|
|
165
|
+
result = self.query("*IDN?")
|
|
166
|
+
print(result)
|
|
167
|
+
"""
|
|
168
|
+
self.instrument.timeout = 20000
|
|
169
|
+
try:
|
|
170
|
+
result = self.instrument.query(command)
|
|
171
|
+
if check_complete:
|
|
172
|
+
self.wait_opc()
|
|
173
|
+
logging.debug(f"{self.model} 查询: {command}, 结果: {result}")
|
|
174
|
+
return result
|
|
175
|
+
except VisaIOError as e:
|
|
176
|
+
logging.error(f"{self.model} 查询失败: {e}")
|
|
177
|
+
raise
|
|
178
|
+
|
|
179
|
+
@handle_instrument_error
|
|
180
|
+
def set_opc_timeout(self, timeout: float=0.1, poll_interval: float = None):
|
|
181
|
+
"""临时设置OPC超时时间
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
timeout: 新的超时时间(秒)
|
|
185
|
+
poll_interval: 新的轮询间隔(秒),可选
|
|
186
|
+
"""
|
|
187
|
+
self.opc_timeout = timeout
|
|
188
|
+
if poll_interval is not None:
|
|
189
|
+
self.opc_poll_interval = poll_interval
|
|
190
|
+
logging.info(f"{self.model} 设置超时时间: {timeout}秒, 轮询间隔: {poll_interval or self.opc_poll_interval}秒")
|
|
191
|
+
|
|
192
|
+
@handle_instrument_error
|
|
193
|
+
def wait_opc(self, timeout=None, opc_poll_interval=None):
|
|
194
|
+
"""等待OPC完成
|
|
195
|
+
Args:
|
|
196
|
+
timeout: 超时时间(秒),默认使用self.timeout
|
|
197
|
+
opc_poll_interval: 轮询间隔(秒),默认使用self.poll_interval
|
|
198
|
+
"""
|
|
199
|
+
self.opc_timeout = timeout or self.opc_timeout
|
|
200
|
+
try:
|
|
201
|
+
start_time = time.time()
|
|
202
|
+
logging.info(f"开始等待opc?")
|
|
203
|
+
|
|
204
|
+
while True:
|
|
205
|
+
try:
|
|
206
|
+
opc = self.instrument.query("*OPC?")
|
|
207
|
+
if opc.strip() == '1':
|
|
208
|
+
logging.info("opc运行完成")
|
|
209
|
+
break
|
|
210
|
+
except VisaIOError as e:
|
|
211
|
+
logging.warning(f"OPC查询失败: {str(e)}")
|
|
212
|
+
# break # 通信异常时终止
|
|
213
|
+
|
|
214
|
+
elapsed = time.time() - start_time
|
|
215
|
+
if elapsed >= self.opc_timeout:
|
|
216
|
+
logging.warning(f"运行超时(已耗时:{elapsed:.1f}秒)")
|
|
217
|
+
break
|
|
218
|
+
time.sleep(opc_poll_interval or self.opc_poll_interval)
|
|
219
|
+
end_time = time.time()
|
|
220
|
+
total_time = end_time - start_time
|
|
221
|
+
logging.info(f"总运行时间: {total_time:.2f}秒")
|
|
222
|
+
return total_time # 返回时间供外部使用
|
|
223
|
+
except Exception as e:
|
|
224
|
+
logging.error(f"发生错误: {str(e)}")
|
|
225
|
+
return str(e)
|
|
226
|
+
|
|
227
|
+
@handle_instrument_error
|
|
228
|
+
def close(self) -> None:
|
|
229
|
+
"""关闭仪器连接"""
|
|
230
|
+
self.instrument.close()
|
|
231
|
+
logging.info(f"{self.model} 连接已关闭")
|
|
232
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import yaml
|
|
4
|
+
import logging
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def load_config(config_path="config.yaml"):
|
|
8
|
+
"""加载配置文件,支持YAML和JSON格式
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
config_path: 配置文件路径(.yaml或.json)
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
配置字典
|
|
15
|
+
"""
|
|
16
|
+
if not os.path.exists(config_path):
|
|
17
|
+
logging.error(f"配置文件未找到: {config_path}")
|
|
18
|
+
raise FileNotFoundError(f"配置文件未找到: {config_path}")
|
|
19
|
+
|
|
20
|
+
file_ext = os.path.splitext(config_path)[1].lower()
|
|
21
|
+
|
|
22
|
+
with open(config_path, "r", encoding="utf-8") as file:
|
|
23
|
+
try:
|
|
24
|
+
if file_ext in ['.yaml', '.yml']:
|
|
25
|
+
return yaml.safe_load(file)
|
|
26
|
+
elif file_ext == '.json':
|
|
27
|
+
return json.load(file)
|
|
28
|
+
else:
|
|
29
|
+
error_msg = f"不支持的配置文件格式: {file_ext}"
|
|
30
|
+
logging.error(error_msg)
|
|
31
|
+
raise ValueError(error_msg)
|
|
32
|
+
except (yaml.YAMLError, json.JSONDecodeError) as e:
|
|
33
|
+
error_msg = f"配置文件格式错误: {e}"
|
|
34
|
+
logging.error(error_msg)
|
|
35
|
+
raise ValueError(error_msg)
|
|
36
|
+
|
|
37
|
+
def get_config_path(config_path):
|
|
38
|
+
"""获取当前工作目录"""
|
|
39
|
+
# 获取当前工作目录
|
|
40
|
+
current_dir = os.getcwd()
|
|
41
|
+
# 拼接配置文件的路径
|
|
42
|
+
config_path = os.path.join(current_dir, config_path)
|
|
43
|
+
return config_path
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
if __name__ == '__main__':
|
|
47
|
+
config = get_config_path("config.yaml")
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
# 将项目根目录(pyinsts)添加到Python路径
|
|
9
|
+
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../../')))
|
|
10
|
+
from src.pyinsts.libs.baseinstrument import BaseInstrument
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class N9020b(BaseInstrument):
|
|
16
|
+
def __init__(self, address: str = None, config_path="config.yaml", model: str = "N9030B"):
|
|
17
|
+
super().__init__(address=address, config_path=config_path, model=model)
|
|
18
|
+
|
|
19
|
+
self.model = model
|
|
20
|
+
def set_mode_spectrum_analyzer(self, mode:Literal["SAN"]):
|
|
21
|
+
"""
|
|
22
|
+
设置频谱模式
|
|
23
|
+
:param mode:
|
|
24
|
+
:return:
|
|
25
|
+
"""
|
|
26
|
+
self.write(f'CONF:{mode}; *WAI') # 设置后使用 *WAI 等待命令执行完成 {mode}')
|
|
27
|
+
|
|
28
|
+
def set_freq(self, freq:float, unit:Literal['GHz', 'MHz', 'kHz', 'Hz']):
|
|
29
|
+
"""
|
|
30
|
+
设置频率
|
|
31
|
+
:param unit:
|
|
32
|
+
:param freq:
|
|
33
|
+
:return:
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
self.write(f'SENS:FREQ {freq}{unit}')
|
|
37
|
+
logging.info(f'设置频率: {freq}{unit}')
|
|
38
|
+
|
|
39
|
+
def set_freq_cent(self, freq:float, unit:Literal['GHz', 'MHz', 'kHz', 'Hz']):
|
|
40
|
+
"""
|
|
41
|
+
设置中心频率
|
|
42
|
+
:param unit:
|
|
43
|
+
:param freq:
|
|
44
|
+
:return:
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
self.write(f'FREQ:CENT {freq}{unit}',True)
|
|
48
|
+
logging.info(f'设置中心频率: {freq}{unit}')
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def set_freq_span(self, freq:float , unit:Literal['GHz', 'MHz', 'kHz', 'Hz']):
|
|
52
|
+
"""
|
|
53
|
+
设置频谱的跨度 (span),单位为 GHz
|
|
54
|
+
:param unit:
|
|
55
|
+
:param freq:
|
|
56
|
+
:return:
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
self.write(f'FREQ:SPAN {freq}{unit}; *WAI') # 设置后使用 *WAI 等待命令执行完成
|
|
60
|
+
|
|
61
|
+
logging.info(f'已设置频谱跨度为: {freq}{unit}')
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def set_freq_span_max(self):
|
|
65
|
+
"""
|
|
66
|
+
设置频谱的跨度 (span)为最大
|
|
67
|
+
:return:
|
|
68
|
+
"""
|
|
69
|
+
self.write(f'FREQ:SPAN MAX; *WAI') # 设置后使用 *WAI 等待命令执行完成
|
|
70
|
+
time.sleep(0.001) # 稍作延迟以确保指令被正确执行
|
|
71
|
+
logging.info(f'已设置频谱跨度为max')
|
|
72
|
+
|
|
73
|
+
def set_freq_start(self, freq: float, unit:Literal['GHz', 'MHz', 'kHz', 'Hz']):
|
|
74
|
+
"""
|
|
75
|
+
设置开始频率
|
|
76
|
+
:param unit:
|
|
77
|
+
:param freq:
|
|
78
|
+
:return:
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
self.write(f'FREQ:STAR {freq}{unit}')
|
|
82
|
+
logging.info(f'设置起始频率: {freq}{unit}')
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def set_freq_stop(self, freq: float, unit:Literal['GHz', 'MHz', 'kHz', 'Hz']):
|
|
86
|
+
"""
|
|
87
|
+
设置截止频率
|
|
88
|
+
:param unit:
|
|
89
|
+
:param freq:
|
|
90
|
+
:return:
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
self.write(f'FREQ:STOP {freq}{unit}')
|
|
94
|
+
logging.info(f'设置结束频率: {freq}{unit}')
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def set_ref_level(self, level: float):
|
|
98
|
+
"""
|
|
99
|
+
设置参考电平 (Reference Level)
|
|
100
|
+
:param level: 参考电平值,单位 dBm
|
|
101
|
+
:return:
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
self.write(f'DISP:WIND:TRAC:Y:RLEV {level}dBm') # 设置参考电平
|
|
105
|
+
logging.info(f'已设置参考电平为: {level} dBm')
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def set_auto_tune(self):
|
|
109
|
+
"""
|
|
110
|
+
自动设置参考电平
|
|
111
|
+
:return:
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
self.write('SENS:FREQ:TUNE:IMM; *WAI') # 设置后使用 *WAI 等待命令执行完成
|
|
115
|
+
logging.info('已设置自动调频')
|
|
116
|
+
|
|
117
|
+
def set_rbw(self, rbw: float, unit:Literal['MHz', 'kHz', 'Hz']):
|
|
118
|
+
"""
|
|
119
|
+
设置分辨带宽 (Resolution Bandwidth, RBW)
|
|
120
|
+
:param rbw: 分辨带宽值
|
|
121
|
+
:param unit:
|
|
122
|
+
:return:
|
|
123
|
+
"""
|
|
124
|
+
self.write(f'BAND:RES {rbw}{unit}; *WAI') # 设置后使用 *WAI 等待命令执行完成
|
|
125
|
+
logging.info(f'已设置分辨带宽为: {rbw}{unit}')
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def set_rbw_auto(self):
|
|
129
|
+
"""
|
|
130
|
+
设置自动rbw
|
|
131
|
+
:return:
|
|
132
|
+
"""
|
|
133
|
+
try:
|
|
134
|
+
self.write('BWID:AUTO ON') # 设置 RBW 为自动模式并等待
|
|
135
|
+
logging.info('已将分辨率带宽设置为自动模式')
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logging.error(f"设置 RBW 时发生错误: {e}")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def set_vbw(self, vbw: float, unit:Literal['MHz', 'kHz', 'Hz']):
|
|
141
|
+
"""
|
|
142
|
+
设置视频带宽 (Video Bandwidth, VBW)
|
|
143
|
+
:param vbw: 视频带宽值
|
|
144
|
+
:param unit: 带宽的单位,默认是 Hz(可选 kHz, MHz)
|
|
145
|
+
:return:
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
# 构建 SCPI 命令
|
|
150
|
+
self.write(f'BAND:VID {vbw}{unit}; *WAI') # 设置后使用 *WAI 等待命令执行完成
|
|
151
|
+
logging.info(f'已设置视频带宽为: {vbw} {unit}')
|
|
152
|
+
except Exception as e:
|
|
153
|
+
logging.error(f"设置视频带宽时发生错误: {e}")
|
|
154
|
+
|
|
155
|
+
def set_vbw_auto(self):
|
|
156
|
+
"""
|
|
157
|
+
自动设置vbw
|
|
158
|
+
:return:
|
|
159
|
+
"""
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
self.write('BAND:VID AUTO; *WAI') # 设置 VBW 为自动模式并等待
|
|
163
|
+
logging.info('已将视频带宽设置为自动模式')
|
|
164
|
+
except Exception as e:
|
|
165
|
+
logging.error(f"设置 VBW 时发生错误: {e}")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def set_peak_search(self):
|
|
169
|
+
"""
|
|
170
|
+
peak search
|
|
171
|
+
:return:
|
|
172
|
+
"""
|
|
173
|
+
self.instrument.write(f"CALC:MARK1:MAX:PEAK")
|
|
174
|
+
logging.info('执行peak search')
|
|
175
|
+
def set_next_peak(self):
|
|
176
|
+
"""
|
|
177
|
+
next peak
|
|
178
|
+
:return:
|
|
179
|
+
"""
|
|
180
|
+
self.write(f"CALC:MARK1:MAX:NEXT")
|
|
181
|
+
logging.info('执行next peak')
|
|
182
|
+
|
|
183
|
+
def set_marker_freq(self, frequency: float,unit: Literal['GHz', 'MHz', 'kHz', 'Hz'] = 'Hz', mark_number: int = 1):
|
|
184
|
+
"""
|
|
185
|
+
设置指定mark标记的频率
|
|
186
|
+
:param unit:
|
|
187
|
+
:param frequency: 频率值
|
|
188
|
+
:param mark_number: 标记编号,默认为 1
|
|
189
|
+
:return:
|
|
190
|
+
"""
|
|
191
|
+
try:
|
|
192
|
+
self.write(f'CALC:MARK{mark_number}:FREQ {frequency}{unit}; *WAI') # 设置指定标记的频率并等待
|
|
193
|
+
logging.info(f'已将标记{mark_number}的频率设置为: {frequency}{unit}')
|
|
194
|
+
except Exception as e:
|
|
195
|
+
logging.error(f"设置标记{mark_number}频率时发生错误: {e}")
|
|
196
|
+
|
|
197
|
+
def set_mark_delt_on(self, x):
|
|
198
|
+
"""
|
|
199
|
+
开启delt标记
|
|
200
|
+
:param x:
|
|
201
|
+
:return:
|
|
202
|
+
"""
|
|
203
|
+
self.write(f"CALC:MARK{x}:MODE DELT")
|
|
204
|
+
logging.info(f'开启delt标记: MARK{x}')
|
|
205
|
+
|
|
206
|
+
def set_mark_all_off(self):
|
|
207
|
+
"""
|
|
208
|
+
关闭所有mark
|
|
209
|
+
:return:
|
|
210
|
+
"""
|
|
211
|
+
self.write(f"CALC:MARK:AOFF")
|
|
212
|
+
logging.info('关闭所有标记')
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def set_mark_del_freq(self, mark_number: int, freq:float, unit: Literal['GHz', 'MHz', 'kHz', 'Hz']):
|
|
216
|
+
"""
|
|
217
|
+
delt频率
|
|
218
|
+
:param unit:
|
|
219
|
+
:param mark_number:1,2,3
|
|
220
|
+
:param freq:
|
|
221
|
+
:return:
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
self.write(f"CALC:MARK{mark_number}:X {freq}{unit}")
|
|
225
|
+
logging.info(f'设置MARK{mark_number}的delt频率为: {freq}{unit}')
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def query_mark_delta_x(self, mark_number: int, unit: Literal['GHz', 'MHz', 'kHz', 'Hz']):
|
|
229
|
+
"""
|
|
230
|
+
读取mark delt
|
|
231
|
+
:param mark_number:
|
|
232
|
+
:param unit:
|
|
233
|
+
:return:
|
|
234
|
+
"""
|
|
235
|
+
value = self.query(f'CALC:MARK{mark_number}:X?')
|
|
236
|
+
if unit == 'GHz':
|
|
237
|
+
value = float(value) / 1e9
|
|
238
|
+
elif unit == 'MHz':
|
|
239
|
+
value = float(value) / 1e6
|
|
240
|
+
elif unit == 'kHz':
|
|
241
|
+
value = float(value) / 1e3
|
|
242
|
+
elif unit == 'Hz':
|
|
243
|
+
value = float(value)
|
|
244
|
+
logging.info(f'读取MARK{mark_number}的偏移频率: {value}{unit}')
|
|
245
|
+
return value, unit
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
def query_mark_x_freq(self, mark_number: int, unit: Literal['GHz', 'MHz', 'kHz', 'Hz']):
|
|
249
|
+
"""
|
|
250
|
+
读取mark频率
|
|
251
|
+
:param mark_number:
|
|
252
|
+
:param unit:
|
|
253
|
+
:return: 返回频率值和单位
|
|
254
|
+
"""
|
|
255
|
+
|
|
256
|
+
value = self.query(f"CALC:MARK{mark_number}:X?")
|
|
257
|
+
if unit == 'GHz':
|
|
258
|
+
value = float(value.replace("\n", "").replace("\r", "")) / 1e9 # 删除字符串中的换行符和回车符
|
|
259
|
+
elif unit == 'MHz':
|
|
260
|
+
value = float(value.replace("\n", "").replace("\r", "")) / 1e6
|
|
261
|
+
elif unit == 'kHz':
|
|
262
|
+
value = float(value.replace("\n", "").replace("\r", "")) / 1e3
|
|
263
|
+
elif unit == 'Hz':
|
|
264
|
+
value = float(value.replace("\n", "").replace("\r", ""))
|
|
265
|
+
logging.info(f'读取MARK频率为:{value}{unit}')
|
|
266
|
+
return value, unit
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def query_mark_y_power(self):
|
|
270
|
+
"""
|
|
271
|
+
读取mark功率,单位dBm
|
|
272
|
+
:return:
|
|
273
|
+
"""
|
|
274
|
+
value = self.query(f"CALC:MARK:Y?")
|
|
275
|
+
value = float(value.replace("\n", "").replace("\r", "")) # 删除字符串中的换行符和回车符
|
|
276
|
+
logging.info(f'读取MARK频率为:{value} dBm')
|
|
277
|
+
return value
|
|
278
|
+
|
|
279
|
+
def set_peak_search_continuous_on(self):
|
|
280
|
+
"""
|
|
281
|
+
设置peak search连续最终打开
|
|
282
|
+
:return:
|
|
283
|
+
"""
|
|
284
|
+
self.write(f"CALC:MARK:CPS ON")
|
|
285
|
+
logging.info(f'set_peak_search_continuous 成功')
|
|
286
|
+
|
|
287
|
+
def set_mark_to_cf(self,mark_number: int = 1):
|
|
288
|
+
"""
|
|
289
|
+
将指定mark设置到中心频率
|
|
290
|
+
:param mark_number:
|
|
291
|
+
:return:
|
|
292
|
+
"""
|
|
293
|
+
self.write(f"CALC:MARK{mark_number}:CENT")
|
|
294
|
+
logging.info(f'CALC:MARK{mark_number}:CENT 成功')
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def save_png(self, file_path):
|
|
298
|
+
"""
|
|
299
|
+
保存数据图片和csv文件
|
|
300
|
+
:param file_path:
|
|
301
|
+
:return:
|
|
302
|
+
"""
|
|
303
|
+
self.write(f'MMEM:STOR:TRAC:DATA TRACE1, "{file_path}.csv"; *WAI')
|
|
304
|
+
self.write(f'MMEM:STOR:SCR "{file_path}.PNG"; *WAI')
|
|
305
|
+
|
|
306
|
+
logging.info(f'保存屏幕截图到: {file_path}.PNG')
|
|
307
|
+
|
|
308
|
+
time.sleep(1)
|
|
309
|
+
|
|
310
|
+
def set_single(self):
|
|
311
|
+
"""
|
|
312
|
+
单次运行
|
|
313
|
+
:return:
|
|
314
|
+
"""
|
|
315
|
+
self.write(f"INIT:CONT 0")
|
|
316
|
+
logging.info(f'set_single 成功')
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def set_cont(self):
|
|
320
|
+
"""
|
|
321
|
+
连续运行
|
|
322
|
+
:return:
|
|
323
|
+
"""
|
|
324
|
+
self.write(f"INIT:CONT 1")
|
|
325
|
+
logging.info(f'set_cont 成功')
|
|
326
|
+
|
|
327
|
+
def set_cal_all(self):
|
|
328
|
+
"""
|
|
329
|
+
自校准all
|
|
330
|
+
:return:
|
|
331
|
+
"""
|
|
332
|
+
self.write(f"*CAL?",True)
|
|
333
|
+
logging.info(f'自校准all 成功')
|
|
334
|
+
|
|
335
|
+
def close(self):
|
|
336
|
+
"""
|
|
337
|
+
关闭仪器端口
|
|
338
|
+
:return:
|
|
339
|
+
"""
|
|
340
|
+
self.close()
|
|
341
|
+
logging.info(f'关闭频谱仪{self.model}端口')
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
if __name__ == '__main__':
|
|
345
|
+
n9020b = N9020b(address="USB0::0x2A8D::0x1D0B::MY55480186::INSTR", model='N9020B')
|
|
346
|
+
n9020b.set_rbw_auto()
|
|
347
|
+
n9020b.set_freq_cent(1,'GHz') # 设置中心频率
|
|
348
|
+
# n9020b.set_cont()
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
|
pyinsts/libs/rs/Fswp.py
ADDED
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
"""
|
|
2
|
+
@date: 2024-11-24
|
|
3
|
+
@author: zhangxp93
|
|
4
|
+
@contact: 937097345@qq.com
|
|
5
|
+
|
|
6
|
+
您可以免费地使用、修改和分发PyInst ,但必须包含原始作者的版权声明和下列许可声明。
|
|
7
|
+
|
|
8
|
+
You may use, modify, and distribute pyinsts free of charge, but you must include
|
|
9
|
+
the original author's copyright notice and the following license notice.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
MIT License
|
|
14
|
+
|
|
15
|
+
Copyright (c) [2024] [zhangxp93]
|
|
16
|
+
|
|
17
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
18
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
19
|
+
in the Software without restriction, including without limitation the rights
|
|
20
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
21
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
22
|
+
furnished to do so, subject to the following conditions:
|
|
23
|
+
|
|
24
|
+
The above copyright notice and this permission notice shall be included in all
|
|
25
|
+
copies or substantial portions of the Software.
|
|
26
|
+
|
|
27
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
28
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
29
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
30
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
31
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
32
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
33
|
+
SOFTWARE.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
import logging
|
|
37
|
+
import pyvisa
|
|
38
|
+
import time
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# 定义Fswp类,连接Fswp信号源分析仪
|
|
43
|
+
class Fswp:
|
|
44
|
+
def __init__(self, instrument_address):
|
|
45
|
+
"""
|
|
46
|
+
设置Fswp仪器地址,连接Fswp信号源分析仪
|
|
47
|
+
:param instrument_address:写入仪器地址,例如:"USB0::0x0957::0x0E09::MY47402409::0::INSTR"
|
|
48
|
+
"""
|
|
49
|
+
self.instrument_address = instrument_address
|
|
50
|
+
self.rm = pyvisa.ResourceManager()
|
|
51
|
+
self.Fswp = self.rm.open_resource(self.instrument_address)
|
|
52
|
+
time.sleep(0.01)
|
|
53
|
+
response = self.Fswp.query("*IDN?")
|
|
54
|
+
response = response.replace("\n", "").replace("\r", "") # 删除字符串中的换行符和回车符
|
|
55
|
+
logging.info(f'Instrument identification:{response}')
|
|
56
|
+
logging.info('FSWP连接成功')
|
|
57
|
+
|
|
58
|
+
def run_cont(self):
|
|
59
|
+
"""
|
|
60
|
+
cont运行
|
|
61
|
+
:return:
|
|
62
|
+
"""
|
|
63
|
+
self.Fswp.write('INIT:CONT OFF')
|
|
64
|
+
self.Fswp.write('INIT:CONT ON')
|
|
65
|
+
|
|
66
|
+
def run_single(self):
|
|
67
|
+
"""
|
|
68
|
+
RUN SINGLE
|
|
69
|
+
:return:
|
|
70
|
+
"""
|
|
71
|
+
self.Fswp.write('INIT:CONT OFF')
|
|
72
|
+
self.Fswp.write('INIT:IMM')
|
|
73
|
+
|
|
74
|
+
def set_run_single1(self):
|
|
75
|
+
"""
|
|
76
|
+
不使用了
|
|
77
|
+
RUN SINGLE
|
|
78
|
+
:return:
|
|
79
|
+
"""
|
|
80
|
+
self.Fswp.write('INIT:CONT OFF')
|
|
81
|
+
self.Fswp.write('INIT:IMM')
|
|
82
|
+
#
|
|
83
|
+
star_time = time.time()
|
|
84
|
+
while True:
|
|
85
|
+
opc = self.Fswp.query("*OPC?")
|
|
86
|
+
if opc.strip() == '1':
|
|
87
|
+
logging.info('run single运行完成')
|
|
88
|
+
break
|
|
89
|
+
if time.time() - star_time > 60:
|
|
90
|
+
logging.error(f'运行超时')
|
|
91
|
+
time.sleep(1)
|
|
92
|
+
|
|
93
|
+
def set_run_single(self):
|
|
94
|
+
"""
|
|
95
|
+
设置仪器为单次运行模式,并等待运行完成,同时记录运行时间。
|
|
96
|
+
"""
|
|
97
|
+
try:
|
|
98
|
+
# 设置 PyVISA 的超时时间为 10 秒
|
|
99
|
+
self.Fswp.timeout = 10000 # 单位为毫秒,10 秒
|
|
100
|
+
|
|
101
|
+
# 记录开始时间
|
|
102
|
+
start_time = time.time()
|
|
103
|
+
|
|
104
|
+
# 发送 SCPI 指令:关闭连续运行,启动单次运行
|
|
105
|
+
self.Fswp.write('INIT:CONT OFF')
|
|
106
|
+
self.Fswp.write('INIT:IMM')
|
|
107
|
+
|
|
108
|
+
# 轮询 OPC 状态
|
|
109
|
+
while True:
|
|
110
|
+
opc = self.Fswp.query("*OPC?")
|
|
111
|
+
if opc.strip() == '1': # 操作完成
|
|
112
|
+
logging.info('run single 运行完成')
|
|
113
|
+
break
|
|
114
|
+
|
|
115
|
+
# 检查是否超时(超时时间为 60 秒)
|
|
116
|
+
if time.time() - start_time > 60:
|
|
117
|
+
logging.error('运行超时')
|
|
118
|
+
break
|
|
119
|
+
|
|
120
|
+
# 每 1 秒检查一次
|
|
121
|
+
time.sleep(0.5)
|
|
122
|
+
|
|
123
|
+
# 记录结束时间并计算运行时间
|
|
124
|
+
end_time = time.time()
|
|
125
|
+
elapsed_time = end_time - start_time # 运行时间,单位为秒
|
|
126
|
+
logging.info(f'运行时间: {elapsed_time:.2f} 秒')
|
|
127
|
+
|
|
128
|
+
except pyvisa.errors.VisaIOError as e:
|
|
129
|
+
logging.error(f"VisaIOError: {e}")
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logging.error(f"运行出错: {e}")
|
|
132
|
+
|
|
133
|
+
def set_cent_freq(self, freq):
|
|
134
|
+
"""
|
|
135
|
+
设置中心频率,单位GHz,PN和SP模式都可用
|
|
136
|
+
:param freq:
|
|
137
|
+
:return:
|
|
138
|
+
"""
|
|
139
|
+
self.Fswp.write(f'FREQ:CENT {freq}GHz')
|
|
140
|
+
time.sleep(0.001)
|
|
141
|
+
|
|
142
|
+
def set_window(self, window):
|
|
143
|
+
"""
|
|
144
|
+
选择窗口
|
|
145
|
+
:param window:
|
|
146
|
+
:return:
|
|
147
|
+
"""
|
|
148
|
+
self.Fswp.write(f'INST "{window}"')
|
|
149
|
+
|
|
150
|
+
def set_marker_x(self, x, offset):
|
|
151
|
+
"""
|
|
152
|
+
设置marker
|
|
153
|
+
:param x:
|
|
154
|
+
1,2,3,4,5,6,7
|
|
155
|
+
:param offset:1K,10K,100K,1M,10M,20M,100M
|
|
156
|
+
:return:
|
|
157
|
+
"""
|
|
158
|
+
marker_on = self.Fswp.write(f'CALC:MARK{x} ON') # 打开marker
|
|
159
|
+
self.Fswp.write(f'DISP:MTAB ON') # 显示marker表
|
|
160
|
+
self.Fswp.write(f'CALC:MARK{x}:X {offset}Hz')
|
|
161
|
+
time.sleep(0.01)
|
|
162
|
+
|
|
163
|
+
def query_marker_x(self, x):
|
|
164
|
+
"""
|
|
165
|
+
读取mark值
|
|
166
|
+
:param x: 1,2,3,4,5,6,7
|
|
167
|
+
:return:
|
|
168
|
+
"""
|
|
169
|
+
marker_value = self.Fswp.query(f'CALC:MARK{x}:Y?')
|
|
170
|
+
return marker_value
|
|
171
|
+
|
|
172
|
+
def query_rms(self):
|
|
173
|
+
"""
|
|
174
|
+
读取积分抖动rms,fs
|
|
175
|
+
:return:
|
|
176
|
+
"""
|
|
177
|
+
jitter_value = float(self.Fswp.query('FETC:RANG2:PNO2:RMS?')) * 1e15
|
|
178
|
+
print('jitter时间:', jitter_value)
|
|
179
|
+
return jitter_value
|
|
180
|
+
|
|
181
|
+
def save_csv_png(self, filename):
|
|
182
|
+
"""
|
|
183
|
+
存csv与png
|
|
184
|
+
:param filename: 文件名需包含路径,'Z:\\DATA\\186_5514\\186_5514_SIOA201P8_GND_REF300_12000_+35'
|
|
185
|
+
:return:
|
|
186
|
+
"""
|
|
187
|
+
self.Fswp.write(f'MMEM:STOR1:TRAC 1,"{filename}.csv"')
|
|
188
|
+
self.Fswp.write(f'MMEM:NAME "{filename}.png"')
|
|
189
|
+
self.Fswp.write('HCOP:IMM')
|
|
190
|
+
logging.info('save_csv_png 运行完成')
|
|
191
|
+
|
|
192
|
+
def save_png(self, filename):
|
|
193
|
+
"""
|
|
194
|
+
存csv与png
|
|
195
|
+
:param filename: 文件名需包含路径,'Z:\\DATA\\186_5514\\186_5514_SIOA201P8_GND_REF300_12000_+35'
|
|
196
|
+
:return:
|
|
197
|
+
"""
|
|
198
|
+
self.Fswp.write(f'MMEM:NAME "{filename}.png"')
|
|
199
|
+
self.Fswp.write('HCOP:IMM')
|
|
200
|
+
logging.info('save_png 运行完成')
|
|
201
|
+
|
|
202
|
+
def set_dc_power(self, on_off):
|
|
203
|
+
"""
|
|
204
|
+
设置DC POWER
|
|
205
|
+
:param on_off:ON,OFF
|
|
206
|
+
:return:
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
self.Fswp.write(f'SOUR:VOLT {on_off}')
|
|
210
|
+
|
|
211
|
+
def set_dc_supply_volt(self, volt):
|
|
212
|
+
"""
|
|
213
|
+
设置DC voltage supply
|
|
214
|
+
:param volt:电压值
|
|
215
|
+
:return:
|
|
216
|
+
"""
|
|
217
|
+
|
|
218
|
+
self.Fswp.write(f'SOUR:VOLT:POW:LEV:AMPL {volt}')
|
|
219
|
+
|
|
220
|
+
def set_dc_power_on(self):
|
|
221
|
+
"""
|
|
222
|
+
设置DC POWER ON
|
|
223
|
+
:return:
|
|
224
|
+
"""
|
|
225
|
+
self.Fswp.write(f'SOUR:VOLT ON')
|
|
226
|
+
|
|
227
|
+
def set_dc_power_off(self):
|
|
228
|
+
"""
|
|
229
|
+
设置DC POWER off
|
|
230
|
+
:return:
|
|
231
|
+
"""
|
|
232
|
+
self.Fswp.write(f'SOUR:VOLT OFF')
|
|
233
|
+
|
|
234
|
+
def set_vtune(self, vt):
|
|
235
|
+
"""
|
|
236
|
+
设置vt电压
|
|
237
|
+
:param vt:
|
|
238
|
+
:return:
|
|
239
|
+
"""
|
|
240
|
+
self.Fswp.write(f'SOUR:VOLT:CONT:LEV:AMPL {vt}')
|
|
241
|
+
|
|
242
|
+
def set_auto_search_off(self):
|
|
243
|
+
"""
|
|
244
|
+
auto_search关闭
|
|
245
|
+
:return:
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
self.Fswp.write(f'SENS:ADJ:CONF:FREQ:AUT:STAT OFF')
|
|
249
|
+
|
|
250
|
+
def query_freq_cent(self):
|
|
251
|
+
"""
|
|
252
|
+
查询频率
|
|
253
|
+
:return:
|
|
254
|
+
"""
|
|
255
|
+
freq = float(self.Fswp.query(f'FREQ:CENT?')) / 1e9
|
|
256
|
+
return freq
|
|
257
|
+
|
|
258
|
+
def query_singal_power(self):
|
|
259
|
+
power = float(self.Fswp.query(f'POW:RLEV?'))
|
|
260
|
+
return power
|
|
261
|
+
|
|
262
|
+
def close(self):
|
|
263
|
+
self.Fswp.close()
|
|
264
|
+
# self.rm.close()
|
|
265
|
+
# print('关闭Fswp连接')
|
|
266
|
+
logging.info('关闭Fswp连接')
|
|
267
|
+
|
|
268
|
+
def set_select_pn(self):
|
|
269
|
+
"""
|
|
270
|
+
选择相噪模式窗口
|
|
271
|
+
:return:
|
|
272
|
+
"""
|
|
273
|
+
self.Fswp.write('INST PNO')
|
|
274
|
+
|
|
275
|
+
def set_select_sp(self):
|
|
276
|
+
"""
|
|
277
|
+
选择频谱模式窗口
|
|
278
|
+
:return:
|
|
279
|
+
"""
|
|
280
|
+
self.Fswp.write('INST SAN')
|
|
281
|
+
|
|
282
|
+
def set_peak_search(self):
|
|
283
|
+
self.Fswp.write('CALC:MARK:MAX')
|
|
284
|
+
|
|
285
|
+
def set_mark_delt(self):
|
|
286
|
+
self.Fswp.write('CALC:DELT ON')
|
|
287
|
+
|
|
288
|
+
def set_mark_delt_freq(self, x, freq):
|
|
289
|
+
self.Fswp.write(f'CALC:DELT{x}:X {freq}MHz')
|
|
290
|
+
|
|
291
|
+
def query_mark_delt_x(self, x):
|
|
292
|
+
"""
|
|
293
|
+
读取偏移频率
|
|
294
|
+
:return:
|
|
295
|
+
"""
|
|
296
|
+
value = self.Fswp.query(f'CALC:DELT{x}:X:REL?')
|
|
297
|
+
return value
|
|
298
|
+
|
|
299
|
+
def query_mark_delt_y(self, y):
|
|
300
|
+
value = float(self.Fswp.query(f'CALC:DELT{y}:Y?'))
|
|
301
|
+
return value
|
|
302
|
+
|
|
303
|
+
def set_continuous_peak(self):
|
|
304
|
+
self.Fswp.write('CALC:MARK:MAX:AUTO ON')
|
|
305
|
+
|
|
306
|
+
def set_mark_all_off(self):
|
|
307
|
+
"""
|
|
308
|
+
关闭所有mark
|
|
309
|
+
:return:
|
|
310
|
+
"""
|
|
311
|
+
self.Fswp.write('CALC:MARK:AOFF')
|
|
312
|
+
|
|
313
|
+
def set_freq_span(self, span):
|
|
314
|
+
"""
|
|
315
|
+
sp模式设置span,MHz
|
|
316
|
+
:return:
|
|
317
|
+
"""
|
|
318
|
+
self.Fswp.write(f'SENS:FREQ:SPAN {span}MHz')
|
|
319
|
+
|
|
320
|
+
def set_bw(self, bw):
|
|
321
|
+
self.Fswp.write(f'SENS:BAND:RES {bw}Hz')
|
|
322
|
+
|
|
323
|
+
def set_mark_on(self, x):
|
|
324
|
+
self.Fswp.write(f'CALC:MARK{x}:STAT ON')
|
|
325
|
+
time.sleep(0.01)
|
|
326
|
+
|
|
327
|
+
def set_mark_del_on(self, x):
|
|
328
|
+
self.Fswp.write(f'CALC:DELT{x}:STAT ON')
|
|
329
|
+
time.sleep(0.01)
|
|
330
|
+
|
|
331
|
+
def set_disp_rlev(self, value):
|
|
332
|
+
self.Fswp.write(f'DISP:WIND:TRAC:Y:SCAL:RLEV {value}')
|
|
333
|
+
|
|
334
|
+
def set_signal_source_freq(self, freq):
|
|
335
|
+
"""
|
|
336
|
+
设置源频率,GHz
|
|
337
|
+
:param freq:
|
|
338
|
+
:return:
|
|
339
|
+
"""
|
|
340
|
+
self.Fswp.write(f'SOUR:GEN:FREQ {freq}MHz')
|
|
341
|
+
|
|
342
|
+
def set_signal_source_pow(self, power):
|
|
343
|
+
"""
|
|
344
|
+
设置源功率,dBm
|
|
345
|
+
:return:
|
|
346
|
+
"""
|
|
347
|
+
self.Fswp.write(f'SOUR:GEN:LEV {power}')
|
|
348
|
+
|
|
349
|
+
def set_signal_source_on(self, on_off):
|
|
350
|
+
"""
|
|
351
|
+
设置源功率开关,ON,OFF
|
|
352
|
+
:return:
|
|
353
|
+
"""
|
|
354
|
+
self.Fswp.write(f'SOUR:GEN:STAT {on_off}')
|
|
355
|
+
|
|
356
|
+
def query_vcochar_freq(self):
|
|
357
|
+
"""
|
|
358
|
+
读取trac1,freq
|
|
359
|
+
:return:
|
|
360
|
+
"""
|
|
361
|
+
freq_value = self.Fswp.query("TRAC1? TRACE1")
|
|
362
|
+
freq_value = freq_value.split(',')
|
|
363
|
+
freq_value = [freq_value[i] for i in range(1, len(freq_value), 2)] # 第一个值为vt电压,第二个值为freq,步进为2
|
|
364
|
+
freq_value = [float(item) / 1e9 for item in freq_value]
|
|
365
|
+
return freq_value
|
|
366
|
+
|
|
367
|
+
def query_vcochar_power(self):
|
|
368
|
+
"""
|
|
369
|
+
读取trac2,功率
|
|
370
|
+
:return:
|
|
371
|
+
"""
|
|
372
|
+
power = self.Fswp.query("TRAC2? TRACE1")
|
|
373
|
+
power = power.split(',')
|
|
374
|
+
power = [power[i] for i in range(1, len(power), 2)]
|
|
375
|
+
return power
|
|
376
|
+
|
|
377
|
+
def query_vcochar_sen(self):
|
|
378
|
+
"""
|
|
379
|
+
读取trac3,调谐灵敏度
|
|
380
|
+
:return:
|
|
381
|
+
"""
|
|
382
|
+
sen = self.Fswp.query(f"TRAC3? TRACE1")
|
|
383
|
+
sen = sen.split(',')
|
|
384
|
+
sen = [sen[i] for i in range(1, len(sen), 2)]
|
|
385
|
+
sen = [float(item) / 1e6 for item in sen]
|
|
386
|
+
return sen
|
|
387
|
+
|
|
388
|
+
def query_vcochar_vt(self):
|
|
389
|
+
"""
|
|
390
|
+
读取trac1
|
|
391
|
+
:return:
|
|
392
|
+
"""
|
|
393
|
+
vt = self.Fswp.query("TRAC1? TRACE1")
|
|
394
|
+
vt = vt.split(',')
|
|
395
|
+
vt = [vt[i] for i in range(0, len(vt), 2)]
|
|
396
|
+
return vt
|
|
397
|
+
|
|
398
|
+
def query_vcochar_icc(self):
|
|
399
|
+
"""
|
|
400
|
+
读取trac4
|
|
401
|
+
:return:
|
|
402
|
+
"""
|
|
403
|
+
icc = self.Fswp.query(f"TRAC4? TRACE1")
|
|
404
|
+
icc = icc.split(',')
|
|
405
|
+
icc = [icc[i] for i in range(1, len(icc), 2)]
|
|
406
|
+
icc = [float(item) / 1e6 for item in icc]
|
|
407
|
+
return icc
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
class FswpVcoChar(Fswp):
|
|
411
|
+
def __init__(self, instrument_address):
|
|
412
|
+
super().__init__(instrument_address)
|
|
413
|
+
|
|
414
|
+
def query_freq(self):
|
|
415
|
+
"""
|
|
416
|
+
读取trac1
|
|
417
|
+
:return:
|
|
418
|
+
"""
|
|
419
|
+
freq = self.Fswp.query("TRAC1? TRACE1")
|
|
420
|
+
freq = freq.split(',')
|
|
421
|
+
freq = [freq[i] for i in range(1, len(freq), 2)]
|
|
422
|
+
freq = [float(item) / 1e9 for item in freq]
|
|
423
|
+
return freq
|
|
424
|
+
|
|
425
|
+
def query_vt(self):
|
|
426
|
+
"""
|
|
427
|
+
读取trac1
|
|
428
|
+
:return:
|
|
429
|
+
"""
|
|
430
|
+
vt = self.Fswp.query("TRAC1? TRACE1")
|
|
431
|
+
vt = vt.split(',')
|
|
432
|
+
vt = [vt[i] for i in range(0, len(vt), 2)]
|
|
433
|
+
return vt
|
|
434
|
+
|
|
435
|
+
def query_power(self):
|
|
436
|
+
"""
|
|
437
|
+
读取trac2
|
|
438
|
+
:return:
|
|
439
|
+
"""
|
|
440
|
+
power = self.Fswp.query("TRAC2? TRACE1")
|
|
441
|
+
power = power.split(',')
|
|
442
|
+
power = [power[i] for i in range(1, len(power), 2)]
|
|
443
|
+
return power
|
|
444
|
+
|
|
445
|
+
def query_sen(self):
|
|
446
|
+
"""
|
|
447
|
+
读取trac3,调谐灵敏度
|
|
448
|
+
:return:
|
|
449
|
+
"""
|
|
450
|
+
sen = self.Fswp.query(f"TRAC3? TRACE1")
|
|
451
|
+
sen = sen.split(',')
|
|
452
|
+
sen = [sen[i] for i in range(1, len(sen), 2)]
|
|
453
|
+
sen = [float(item) / 1e6 for item in sen]
|
|
454
|
+
return sen
|
|
455
|
+
|
|
456
|
+
def query_icc(self):
|
|
457
|
+
"""
|
|
458
|
+
读取trac4
|
|
459
|
+
:return:
|
|
460
|
+
"""
|
|
461
|
+
icc = self.Fswp.query(f"TRAC4? TRACE1")
|
|
462
|
+
icc = icc.split(',')
|
|
463
|
+
icc = [icc[i] for i in range(1, len(icc), 2)]
|
|
464
|
+
icc = [float(item) / 1e6 for item in icc]
|
|
465
|
+
return icc
|
|
466
|
+
|
|
467
|
+
def set_freq_search_range(self, freq_low, freq_high):
|
|
468
|
+
self.Fswp.write(f"ADJ:CONF:FREQ:LIM:LOW {freq_low}GHz")
|
|
469
|
+
self.Fswp.write(f"ADJ:CONF:FREQ:LIM:HIGH {freq_high}GHz")
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
class SpotNoiseTune(Fswp):
|
|
473
|
+
def __init__(self, instrument_address):
|
|
474
|
+
super().__init__(instrument_address)
|
|
475
|
+
|
|
476
|
+
def query_phase(self, trace):
|
|
477
|
+
phase = self.Fswp.query(f"TRAC1? TRACE{trace}")
|
|
478
|
+
phase = phase.split(',')
|
|
479
|
+
phase = [phase[i] for i in range(1, len(phase), 2)]
|
|
480
|
+
phase[-1] = phase[-1].rstrip("\n")
|
|
481
|
+
return phase
|
|
482
|
+
|
|
483
|
+
def query_phase_vt(self, trace):
|
|
484
|
+
phase_vt = self.Fswp.query(f"TRAC1? TRACE{trace}")
|
|
485
|
+
phase_vt = phase_vt.split(',')
|
|
486
|
+
phase_vt = [phase_vt[i] for i in range(0, len(phase_vt), 2)]
|
|
487
|
+
return phase_vt
|
|
488
|
+
|
|
489
|
+
|
|
490
|
+
if __name__ == '__main__':
|
|
491
|
+
addr_fswp = 'TCPIP0::172.16.30.150::inst0::INSTR'
|
|
492
|
+
fswp = Fswp(addr_fswp)
|
|
493
|
+
fswp.set_cent_freq(10)
|
|
494
|
+
fswp.close()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyinsts
|
|
3
|
+
Version: 0.0.3
|
|
4
|
+
Summary: PyVISA仪器控制库,支持Keysight和Rohde&Schwarz仪器
|
|
5
|
+
Author-email: zhangxp93 <937097345@qq.com>
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: colorlog>=6.9.0
|
|
10
|
+
Requires-Dist: pyvisa>=1.14.1
|
|
11
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
12
|
+
Requires-Dist: versioningit>=3.3.0
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
[**简体中文**](README.md) | [**English**](README_en.md)
|
|
16
|
+
|
|
17
|
+
<div align="center">
|
|
18
|
+
<h1>
|
|
19
|
+
<strong>PyVISA仪器控制</strong>
|
|
20
|
+
</h1>
|
|
21
|
+
<h5>
|
|
22
|
+
<strong>适用于 Windows、MacOs、Linux</strong><br>
|
|
23
|
+
</h5>
|
|
24
|
+
</div><br>
|
|
25
|
+
|
|
26
|
+
___
|
|
27
|
+
|
|
28
|
+
- **基于pyvisa库对仪器进行控制,支持USB、GPIB、TCP/IP等连接方式。**
|
|
29
|
+
- **Rohde & Schwarz SCPI**
|
|
30
|
+
- **KeySight SCPI**
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## 必要依赖库安装
|
|
34
|
+
PyVISA 是一个用于控制和通信仪器的 Python 库,使用下面的代码安装:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
pip install pyvisa
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
## 🏷[更新日志](CHANGELOG.md)
|
|
43
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
_version.py,sha256=9a6AUtX5RMk6nKDHIwIdcsJY8-zWnhr61ZpohhMkAm8,338
|
|
3
|
+
pyinsts/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
pyinsts/common/__init__.py,sha256=qHSEnlpEwke0twHSg678zQbSz7OlUB_FRSlK5rA9eBc,64
|
|
5
|
+
pyinsts/common/log_data.py,sha256=iaLQp6axCARV25c8MOtmaHhV4KaWMridtiYC6m2W3I0,3099
|
|
6
|
+
pyinsts/libs/__init__.py,sha256=LHXk5Ux-MH-q5zfM0V-4mwgYfLwbxUT_GHWfUBcmhfA,179
|
|
7
|
+
pyinsts/libs/baseinstrument.py,sha256=MklBXzN-WPV60YMBHv-V4R6ZKAbuY6_zeCgTpKtvJ_Q,7680
|
|
8
|
+
pyinsts/libs/data/__init__.py,sha256=Ka8CExqoyIVlfptIfIlaFd2eGQABoo1BF_RDrvRn_Iw,63
|
|
9
|
+
pyinsts/libs/data/load_config.py,sha256=WCeqG7jky4_WtaRqnebMRfaYCvGqJxTBDrevxTektRs,1441
|
|
10
|
+
pyinsts/libs/keysight/N9020B.py,sha256=OVzJkOODIrMfSSQEz4cDKB7DmkJ0vNOTvQefQXiKFr4,10303
|
|
11
|
+
pyinsts/libs/keysight/__init__.py,sha256=Mzcm-eHk-n8cqhEDPRDe-jbL_yHGtSgGOC6mV3aWPCw,48
|
|
12
|
+
pyinsts/libs/rs/Fswp.py,sha256=aCeHSNMcdSsd5h5k4OLSwK2nGx2Y-AU7FGfMUpKvnPY,14051
|
|
13
|
+
pyinsts/libs/rs/__init__.py,sha256=S3NnN2_9fWoTi3k8PUtUpSrMg0xJ-HJ-BKQbif43d-c,68
|
|
14
|
+
pyinsts-0.0.3.dist-info/licenses/LICENSE,sha256=LnqRwuLNDI3-4wjKjtLUgtWqhxmt7B6f-sUPzVOD2KM,1069
|
|
15
|
+
pyinsts-0.0.3.dist-info/METADATA,sha256=S4AiYbhRpD_ajIK0D37u7YxVAInU34sYvRfJ0AcgHCc,933
|
|
16
|
+
pyinsts-0.0.3.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
17
|
+
pyinsts-0.0.3.dist-info/top_level.txt,sha256=jlNIE-E363pzxWpTItZ2PrDPVouOHl38W77Hme3buys,26
|
|
18
|
+
pyinsts-0.0.3.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [2024] [zhangxp93]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|