Python 控制 Keysight N9010B 频谱仪
创始人
2024-06-02 17:33:57
0

目录

前言 

频谱仪简介

硬件接线图

实现原理

代码详解

 步骤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地址是否在同一网段)。

代码详解

 步骤1: 建立连接

	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 = 8000return cls.instelse:raise AssertionError("The signal analyzer is not online.")

然后,封装一个打开连接的函数:

@classmethoddef _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 = connectionIDprint('Connect to SA successful')return (connectionID, cls._connections[connectionID])

步骤2: 发送SCPI指令

@classmethoddef _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))

步骤3: 根据需求封装函数实现功能

这里以设置频谱仪测试模式的函数为例,首先呢,写一个设备支持的测试模式的枚举类:

class TestMode(enum.Enum):SAN = 0SPEC = 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))

所有其他功能都是相似的道理,核心在于熟读设备的官方手册,由于这些仪器仪表的手册多为英文,所以有时候英文水平还是相当重要的!!!

项目源码

 我这里控制频谱仪是为了做GSM的带内杂散测试,所以只封装了仪器的部分功能;该频谱仪的功能很全,但是我们常用的只是九牛一毛而已。下面附上源码,只为抛砖引玉,如有错误或者不足之处,还望各位大佬指出,谢谢!

import enum
import timeimport pyvisa
import hashlibfrom utils import Utilsclass SignalError(Exception):passclass SupportedTimeUnit(enum.Enum):KS = 'Ks'S = 's'MS = 'ms'US = 'us'NS = 'ns'class TestMode(enum.Enum):SAN = 0SPEC = 1class SweepMode(enum.Enum):SINGLE = 0CONTINUOUS = 1class RFCouplingMode(enum.Enum):AC = 0DC = 1class TraceType(enum.Enum):WRIT = 0AVER = 1MAXH = 2MINH = 3class TriggerSources(enum.Enum):IMM = 0VID = 1LINE = 2EXT1 = 3EXT2 = 4RFB = 5PRAM = 6TV = 7class TriggerSlope(enum.Enum):POS = 0NEG = 1class MarkerMode(enum.Enum):OFF = 0POSITION = 1DELTA = 2FIXED = 3class BandFunctionType(enum.Enum):OFF = 0NOISE = 1BPOW = 2BDEN = 3class _SAKeywords(object):_connections = {}_active_connection = Nonedef __init__(self):super().__init__()# region Connection Handling@classmethoddef _check_connectionID(cls, connectionID):if connectionID is None:connectionID = cls._active_connectionif connectionID is None:raise SignalError('No signal analyzer connection established')if connectionID not in cls._connections.keys():raise SignalError('No signal analyzer connection with ID {:s} established'.format(connectionID))return connectionID@classmethoddef _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 = connectionIDprint('Connect to SA successful')return (connectionID, cls._connections[connectionID])@classmethoddef _close_signal_analyzer_connection(cls, connectionID=None):connectionID = cls._check_connectionID(connectionID)try:cls._connections[connectionID].close()except SignalError as err:print('{!s}'.format(err))del cls._connections[connectionID]cls._active_connection = None if not cls._connections else next(iter(cls._connections.keys()))print('Disconnected with SA')return (cls._active_connection, cls._connections[cls._active_connection] if cls._active_connection else None)@classmethoddef _sigAnalyzer_connection(cls, resourceName):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 = 8000return cls.instelse:raise AssertionError("The signal analyzer is not online.")@classmethoddef _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))@classmethoddef _send_query_command(cls, command, connectionID=None):command = str(command)connectionID = cls._check_connectionID(connectionID)data_length = len(command)if data_length != 0:try:content = cls._connections[connectionID].query(command)except Exception as err:raise IOError("Query command ({:s}) from signal analyzer failed, {:s}".format(command, err))else:raise AssertionError('This command is not correct!')return content@staticmethoddef get_connectionID(resourceName):return hashlib.sha1(str(resourceName).lower().encode('utf-8')).hexdigest()@classmethoddef _error_query(cls):content = cls._send_query_command(command=':SYST:ERR?')code = content.split(',')[0]if int(code) != 0:raise AssertionError('Already has error:{}'.format(content))def open_signal_analyzer_connection(self, ipAddress):_SAKeywords._open_signal_analyzer_connection(ipAddress)self.signal_analyzer_initialize()def close_signal_analyzer_connection(self):return _SAKeywords._close_signal_analyzer_connection()def _signal_analyzer_reset(self):_SAKeywords._send_raw_command('*CLS')_SAKeywords._send_raw_command('*RST')_SAKeywords._send_raw_command('*OPC')_SAKeywords._send_raw_command(':SYST:PRES')print('Set SA to default setting')def signal_analyzer_initialize(self, reset=True):data = _SAKeywords._send_query_command('*IDN?')if 'N9010B' in data or 'N9030B' in data or 'N9020B' in data or 'N9040B' in data or 'N9000B' in data:if reset is True:self._signal_analyzer_reset()else:passelse:raise AssertionError('Signal analyzer initialize failed: device general information is wrong, {}'.format(data))# endregion# region Test Preparedef 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))def set_signal_analyzer_sweep_mode(self, mode=SweepMode.CONTINUOUS):mode = Utils.to_enum(mode, SweepMode)command = 'INIT:CONT {:d}'.format(mode.value)_SAKeywords._send_raw_command(command)print('Set SA sweep mode to {:s}'.format(mode.name))def set_signal_analyzer_input_coupling_mode(self, mode=RFCouplingMode.AC):mode = Utils.to_enum(mode, RFCouplingMode)command = 'INP:COUP {:s}'.format(mode.name)_SAKeywords._send_raw_command(command)print('Set SA input coupling mode to {:s}'.format(mode.name))# endregion# region Configurationdef 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 centerFreqspanFreq = str(spanFreq) if spanFreq is not None else spanFreqfreqParams = []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')def set_signal_analyzer_amplitude_configuration(self, refLevel=None, refLevOffset=None, attenuation=None):refLevel = float(refLevel) if refLevel is not None else refLevelrefLevOffset = float(refLevOffset) if refLevOffset is not None else refLevOffsetattenuation = float(attenuation) if attenuation is not None else attenuationscaleParams = []attParams = []print('Set SA ref level offset to {} dB, attenuation to {}'.format(refLevel, attenuation))if refLevel is not None:scaleParams.append('RLEV {:.2f}'.format(refLevel))if refLevOffset is not None:scaleParams.append('RLEV:OFFS {:.2f}'.format(refLevOffset))if attenuation is not None:attParams.append('ATT {:.2f}'.format(attenuation))if len(scaleParams) >= 1:_SAKeywords._send_raw_command(':DISP:WIND:TRAC:Y:{:s}'.format(';'.join(scaleParams)))else:print('No amplitude y scale command sent, no parameter was set')if len(attParams) >= 1:_SAKeywords._send_raw_command('POW:{:s}'.format(';'.join(attParams)))else:print('No attenuation command sent, no parameter was set')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 resBWvideoBW = str(videoBW) if videoBW is not None else videoBWbwParams = []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')# endregion

支持的设备信号

由于我这边设备有限,以上代码只在keysight的N9010B, N9020B, B9030B等仪器上适配过,其他仪器暂未确定是否支持!!!

相关内容

热门资讯

苹果怎么倒进安卓系统,一键倒装... 你有没有想过,把苹果手机里的宝贝倒腾到安卓系统里去?听起来是不是有点像变魔术?别急,今天就来手把手教...
安卓系统都能双系统么吗,揭秘双... 你有没有想过,你的安卓手机是不是也能来个“双胞胎”呢?没错,就是那种一个手机里同时运行两个操作系统,...
长安汽车升级安卓系统,安卓系统... 你知道吗?最近长安汽车可是来了一次大变身呢!没错,就是那个我们熟悉的国产汽车品牌,这次他们竟然升级了...
mac电脑装安卓系统,轻松实现... 亲爱的电脑迷们,你是否曾幻想过在你的Mac电脑上运行安卓系统?想象那些你钟爱的安卓应用,在你的Mac...
安卓p系统流畅吗,畅享无忧 你有没有发现,最近安卓P系统成了大家热议的话题呢?不少朋友都在问,这个新系统到底流畅不流畅啊?今天,...
剑灵2安卓系统,畅游东方奇幻世... 你知道吗?最近在安卓系统上,有一款游戏可是火得一塌糊涂,那就是《剑灵2》!这款游戏不仅画面精美,操作...
安卓系统是否指定品牌,品牌定制... 你有没有想过,为什么你的安卓手机总是那么独特,而别人的安卓手机却看起来差不多呢?这背后,其实隐藏着一...
安卓系统和iso系统 照片共享... 你有没有发现,现在手机拍照功能越来越强大,拍出来的照片美得不要不要的!但是,当你想和朋友们分享这些精...
安卓系统领夹麦,便携式音频解决... 你有没有发现,现在手机通话越来越方便了,但是有时候,手机那点小小的麦克风,真的有点力不从心呢!尤其是...
安卓系统经常无法唤醒,探究系统... 你是不是也遇到过这种情况?手机屏幕黑了,手指在屏幕上疯狂地滑动,却怎么也唤醒不了安卓系统。这可真是让...
自己编译安卓系统源码,编译安卓... 你有没有想过,安卓系统其实就像一个巨大的宝藏,里面藏着无数的秘密和可能性?今天,就让我带你一起探索这...
鸿蒙系统属于安卓,基于安卓的全... 你知道吗?最近有个话题在科技圈里可是闹得沸沸扬扬的,那就是鸿蒙系统。没错,就是那个华为自主研发的操作...
lephone是安卓系统吗,深... 你有没有听说过lephone这个品牌呢?最近,身边的朋友都在讨论这个手机,说它性价比超高,但是,有人...
pc双系统安卓系统下载软件,P... 你有没有想过,在电脑上同时运行安卓系统,是不是就像在手机上玩电脑游戏一样酷炫呢?没错,这就是今天我要...
电量壁纸安卓系统设置,安卓系统... 手机电量告急,是不是又得赶紧找充电宝了?别急,今天就来和你聊聊如何通过设置电量壁纸来给安卓系统来个“...
ip是安卓系统吗,通过IP地址... 你有没有想过,那个陪伴你每天刷剧、玩游戏、办公的IP,它是不是安卓系统呢?别急,今天就来揭开这个谜底...
安卓系统谁负责升级,揭秘幕后负... 你有没有想过,你的安卓手机为什么有时候会突然收到系统更新的通知呢?是不是好奇,是谁在背后默默地为你的...
安卓系统需要降级吗,安卓系统升... 你有没有发现,你的安卓手机最近有点儿“老态龙钟”了呢?运行速度慢吞吞的,有时候还卡个不停。这时候,你...
性价比手机安卓系统,盘点安卓系... 你有没有想过,在这个手机更新换代如此迅速的时代,如何用最少的钱,买到最满意的手机呢?没错,我要说的是...
虚拟大师安卓2.0系统,安卓新... 你有没有听说最近虚拟大师安卓2.0系统火得一塌糊涂?这可不是空穴来风,而是真的让不少手机用户都跃跃欲...