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等仪器上适配过,其他仪器暂未确定是否支持!!!

相关内容

热门资讯

恋夜视频安卓系统Uc,恋夜视频... 亲爱的读者,你是否曾在深夜时分,被手机屏幕上跳动的视频吸引?今天,就让我带你一探究竟,揭开恋夜视频安...
鸿蒙套娃安卓系统视频,融合与创... 你知道吗?最近科技圈可是炸开了锅,因为华为的新操作系统鸿蒙OS又有了新动作。这不,他们竟然把鸿蒙套娃...
xp系统连接安卓手机问题,实用... 你有没有遇到过这样的情况:你的电脑上还运行着那个经典的XP系统,而你的安卓手机却时不时地想要和你亲密...
压缩安卓系统储存空间,高效管理... 手机里的照片越来越多,游戏也越玩越上瘾,可这安卓系统的储存空间却越来越紧张,是不是感觉像是在挤牙膏?...
安卓手游转苹果系统教程,轻松实... 你是不是也和我一样,手头有一堆安卓手游,突然之间想换换口味,体验一下苹果系统的魅力呢?别急,今天就来...
安卓原生系统锁屏暗,安卓系统锁... 亲爱的手机控们,你是否曾为安卓手机锁屏时的暗模式而感到好奇?那种在夜晚或光线不足的环境中,屏幕自动调...
安卓系统表情包下载地址,安卓系... 你是不是也和我一样,对安卓系统的表情包爱不释手?那些搞笑的、可爱的、甚至是有点小调皮的表情,总能让我...
原生安卓系统声音bug,揭秘那... 你有没有遇到过这种情况?手机里突然传来一阵奇怪的声音,让你瞬间从美梦中惊醒,或者正在专心工作时被打扰...
水果收银机安卓系统,便捷高效的... 你有没有想过,在繁忙的超市里,那些摆满新鲜水果的摊位,背后竟然隐藏着一个小小的科技秘密?没错,就是那...
安卓系统变苹果界面了吗,苹果界... 最近手机界可是炸开了锅,不少安卓用户都在议论纷纷:“安卓系统变苹果界面了吗?”这事儿可真不简单,得好...
miui操作系统与安卓系统吗,... 亲爱的读者,你是否曾在手机上看到过MIUI操作系统和安卓系统这两个名字,好奇它们之间有什么区别?今天...
安卓系统怎么卡道具界面,探究原... 手机用久了,是不是感觉安卓系统越来越卡?尤其是那个道具界面,点开就慢吞吞的,真是让人头疼。别急,今天...
安卓系统红包加速器,畅享无阻新... 你有没有发现,现在用手机抢红包简直是一场速度与激情的较量?别急,别急,让我来给你揭秘一款神器——安卓...
安卓经典版系统更新时间,从首次... 你有没有发现,最近你的安卓手机又悄悄地变了个样?没错,就是那个陪伴我们多年的经典版系统,它又来更新啦...
安卓系统开发要多久,约需1-2... 你有没有想过,自己动手开发一个安卓应用,究竟需要多长时间呢?这可是个让人好奇的问题,毕竟安卓系统开发...
原生安卓系统手机壁纸图片,探索... 亲爱的手机控们,你是否曾为寻找一款独特的壁纸而烦恼?今天,就让我带你走进原生安卓系统手机壁纸的奇幻世...
bmw安卓互联系统,智能驾驶新... 你有没有发现,现在开车已经不仅仅是驾驶那么简单了?一辆好车,还得有个好“大脑”,这样才能让你的驾驶体...
安卓手机升级系统卡吗,安卓手机... 你有没有遇到过这种情况:安卓手机升级系统后,突然感觉手机像蜗牛一样慢吞吞的,心里那个急啊!今天,就让...
无线麦克风安卓系统,轻松实现无... 你有没有想过,在一场热闹的K歌派对或者重要的演讲场合,无线麦克风简直就是救星啊!想象你手握麦克风,自...
怎么重新定制安卓系统,打造专属... 你有没有想过,你的安卓手机其实可以变得独一无二,就像是你自己的小宇宙一样?没错,就是重新定制安卓系统...