目录
前言
频谱仪简介
硬件接线图
实现原理
代码详解
步骤1: 建立连接
步骤2: 发送SCPI指令
步骤3: 根据需求封装函数实现功能
项目源码
支持的设备信号
为什么要实现频谱仪的控制呢?起因是因为我司RF的同事们需要对通信模块进行GSM的带内杂散进行测试,由于该测试的频点涉及900多个,所以急需开发一款自动化测试工具。
该工具需同时实现对无线通讯仪和频谱仪的控制,这篇文章主要介绍一下频谱仪的控制!
做通信的应该对频谱仪都不陌生,不过我之前做的都是软件相关行业,所以我对频谱仪其实还是很陌生的;不过好在我的同时们都很热心,加上仪器的文档,一段时间下来,也算是熟悉了一些。
N9010B EXA 信号分析仪,分析的频域范围是10 Hz 至 44 GHz:
如何用python实现对频谱仪的远程控制,这里可以参考我之前的一篇远程Fluke设备的文章,其中原理大致类似。主要是通过pyvisa库实现PC和设备之间的通信,然后再根据设备的官方手册,将其中的SCPI指令按照需求进行封装,从而实现你想要的功能。
由于我这里GPIB线材资源不足,且GPIB的成本较高,所以我这里采用的是Ethernet通信的方式来控制频谱仪。
所以,在开始之前,需要安装NI软件(自行百度),安装完毕后打开NI MAX 添加设备(在此之前需将PC的IP地址改为和频谱仪同一网段,否则无法找到设备)
步骤1:点击展开->右键设备和接口 -> 新建 -> 选择VISA TCP/IP Resources
步骤2: 选择Auto-detect of LAN instrument -> 点击下一步:
步骤3: 选择 Manually specify address information of LAN instrument
步骤4: 点击下一步 -> 输入 Signal Analyzer 的IP地址 -> 点击Validate ->完成:
(若验证失败,请检查PC和Signal Analyzer的IP地址是否在同一网段)。
先写一个连接的底层函数,主要是验证资源名称是否在当前可用资源列表中:
def _sigAnalyzer_connection(cls, resourceName):
# 这里是建立连接的私有函数,主要通过pyvisa库列出所有的资源,判断我的设备是否在已有的资源列表中
resourceName = str(resourceName)
rm = pyvisa.ResourceManager()
resource_list = rm.list_resources(query='?*::INSTR')
if resourceName in resource_list:
cls.inst = rm.open_resource(resourceName)
cls.inst.clear()
cls.inst.timeout = 8000
return cls.inst
else:
raise AssertionError("The signal analyzer is not online.")
然后,将仪器的IP地址按照固定格式组成资源名称,再去调用连接的底层函数封装一个建立仪器连接的函数:
@classmethod
def _open_signal_analyzer_connection(cls, ipAddress):
ipAddress = str(ipAddress)
resourceName = 'TCPIP0::{:s}::inst0::INSTR'.format(ipAddress)
connectionID = cls.get_connectionID(resourceName)
if connectionID not in cls._connections:
cls._connections[connectionID] = cls._sigAnalyzer_connection(resourceName)
cls._active_connection = connectionID
print('Connect to SA successful')
return (connectionID, cls._connections[connectionID])
这里是封装一个发送所有SCPI指令的函数,该函数可将传入的SCPI指令发送至仪表并检查返回是否出错:
@classmethod
def _send_raw_command(cls, command, connectionID=None):
command = str(command)
connectionID = cls._check_connectionID(connectionID)
if not command:
raise ValueError('Command string is empty')
_SAKeywords._connections[connectionID].write(command)
try:
cls._error_query() # 另外封装的私有方法,检查返回内容
except:
raise IOError('Send command ({:s}) to signal analyzer failed.'.format(command))
以上两个步骤完成后剩下的就比较简单了,只需要熟读仪器的手册,根据需求找到相关的SCPI指令,然后进行封装即可,下面简单举几个例子:
(1)设置频谱仪测试模式
这里以设置频谱仪测试模式的函数为例,首先呢,写一个设备支持的测试模式的枚举类:
class TestMode(enum.Enum):
SAN = 0
SPEC = 1
然后,在文档中找到设置测试模式的指令为 “CONF:模式名” ,然后直接调用之前的send_raw_comman()方法向设备发送相应的指令即可:
def set_signal_analyzer_test_mode(self, mode=TestMode.SAN):
mode = Utils.to_enum(mode, TestMode)
command = 'CONF:{:s}'.format(mode.name)
_SAKeywords._send_raw_command(command)
print('Set SA to {:s} mode'.format(mode.name))
所有其他功能都是相似的道理,核心在于熟读设备的官方手册,由于这些仪器仪表的手册多为英文,所以有时候英文水平还是相当重要的!!!
(2) 设置频谱仪的频率
从仪器的官方手册中可得知设置中心频率的SCPI指令为“CENT 频率”,比如“CENT 900MHZ”就是设置中心频率为900MHz。设置扫宽的指令是“SPAN 频率”,所以封装成函数如下:
def set_signal_analyzer_freq_configuration(self, centerFreq=None, spanFreq=None):
"""
:param centerFreq: e.g 10ghz(GHZ), 10mhz(MHZ), 10khz(KHZ), 10hz(HZ)
:param spanFreq: e.g 10ghz(GHZ), 10mhz(MHZ), 10khz(KHZ), 10hz(HZ)
:return:None
"""
centerFreq = str(centerFreq) if centerFreq is not None else centerFreq
spanFreq = str(spanFreq) if spanFreq is not None else spanFreq
freqParams = []
print('Set SA frequency to {}'.format(centerFreq))
if centerFreq is not None:
freqParams.append('CENT {:s}'.format(centerFreq))
if spanFreq is not None:
freqParams.append('SPAN {:s}'.format(spanFreq))
if len(freqParams) >= 1:
_SAKeywords._send_raw_command('FREQ:{:s}'.format(';'.join(freqParams)))
else:
print('No frequency command sent, no parameter was set')
该函数可以设置频谱仪的中心频率和扫宽,直接传入参数即可,关于参数的格式在代码注释中有写。
(3)设置频谱仪的分析带宽RBW和视频带宽VBW
查阅仪器的官方手册可得知设置RBW的SCPI指令是“:BAND 带宽”;设置VBW的指令是“:BAND:VID 带宽”,所以封装成函数如下:
def set_signal_analyzer_bw_configuration(self, resBW=None, videoBW=None):
"""
:param resBW: e.g 10ghz(GHZ), 10mhz(MHZ), 10khz(KHZ), 10hz(HZ)
:param videoBW: e.g 10ghz(GHZ), 10mhz(MHZ), 10khz(KHZ), 10hz(HZ)
:return: None
"""
resBW = str(resBW) if resBW is not None else resBW
videoBW = str(videoBW) if videoBW is not None else videoBW
bwParams = []
print('Set SA RBW to {}, VBW to {}'.format(resBW, videoBW))
if resBW is not None:
bwParams.append(':BAND {:s}'.format(resBW))
if videoBW is not None:
bwParams.append(':BAND:VID {:s}'.format(videoBW))
if len(bwParams) >= 1:
_SAKeywords._send_raw_command('{:s}'.format(';'.join(bwParams)))
else:
print('No BW command sent, no parameter was set')
我这里控制频谱仪是为了开发GSM的带内杂散的测试工具,所以只封装了仪器的部分功能。后面工具开发完成后我会再写一篇文章(包括工具的结构,频谱仪的控制,无线通讯测试仪的控制)。
该频谱仪的功能很全,但是我们常用的只是九牛一毛而已。如果以上内容有错误之处,还请各位指出,感谢!
以上的部分只为了提供思路和方法;如果有需要源码的朋友可以在下方评论或者私信我!
由于我这边设备有限,以上代码只在keysight的N9010B, N9020B, B9030B等仪器上适配过,其他仪器暂未确定是否支持!!!
有需要源码的同学,可以CSDN私聊我或者wx直接加我: xgh321324