Source code for lightlab.equipment.lab_instruments.Agilent_N5222A_NA

from . import VISAInstrumentDriver
from lightlab.equipment.abstract_drivers import Configurable
from lightlab.laboratory.instruments import NetworkAnalyzer

import numpy as np
import time
from lightlab.util.data import Spectrum, FunctionBundle
import matplotlib.pyplot as plt
from IPython import display


[docs]class Agilent_N5222A_NA(VISAInstrumentDriver, Configurable): ''' Agilent PNA N5222A , RF network analyzer `Manual <http://na.support.keysight.com/pna/help/PNAHelp9_90.pdf>`_ WARNING: The address is the same as the slow function generator, so don't use both on andromeda at the same time. Steep learning curve. Usage: :any:`/ipynbs/Hardware/NetworkAnalyzer.ipynb` Todo: All the RF equipment is reusing __enaBlock. Make this a method of Configurable. When setting up general, you have to setup sweep before setting CW frequency, or else the CW freq becomes the start frequency. Why? See hack in sweepSetup. ''' instrument_category = NetworkAnalyzer def __init__(self, name='The network analyzer', address=None, **kwargs): VISAInstrumentDriver.__init__(self, name=name, address=address, **kwargs) Configurable.__init__(self, headerIsOptional=False) self.chanNum = 1 self.traceNum = 1 self.auxTrigNum = 1 self.swpRange = None
[docs] def startup(self): self.measurementSetup('S21')
[docs] def amplitude(self, amp=None): ''' Amplitude is in dBm Args: amp (float): If None, only gets Returns: (float): output power amplitude ''' if amp is not None: if amp > 30: print('Warning: PNA ony goes up to +30dBm, given {}dBm.'.format(amp)) amp = 30 if amp < -30: print('Warning: R&S ony goes down to -30dBm, given {}dBm.'.format(amp)) amp = -30 self.setConfigParam('SOUR:POW', amp) return self.getConfigParam('SOUR:POW')
[docs] def frequency(self, freq=None): ''' Frequency is in Hertz **Setting the frequency takes you out of sweep mode automatically** Args: freq (float): If None, only gets Returns: (float): center frequency ''' if freq is not None: if freq > 26e9: print('Warning: Agilent N5183 ony goes up to 40GHz, given {}GHz.'.format(freq / 1e9)) freq = 26e9 if freq == self.getConfigParam('SENS:FREQ:CW'): return freq if self.sweepEnable(): print('Warning: Agilent N5183 was sweeping when you set frequency, moving to CW mode') # So we need to update this object's internal state too self.sweepEnable(False) # Setting this automatically brings to CW mode self.setConfigParam('SENS:FREQ:CW', freq) return self.getConfigParam('SENS:FREQ:CW')
[docs] def enable(self, enaState=None): ''' Enabler for the entire output Args: enaState (bool): If None, only gets Returns: (bool): is RF output enabled ''' return self.__enaBlock('OUTP:STAT', enaState)
[docs] def run(self): self.setConfigParam('SENS:SWE:MODE', 'CONT')
[docs] def sweepSetup(self, startFreq, stopFreq, nPts=None, dwell=None, ifBandwidth=None): ''' Configure sweep. See instrument for constraints; they are not checked here. **Does not auto-enable. You must also call :meth:`sweepEnable`** Args: startFreq (float): lower frequency in Hz stopFreq (float): upper frequency in Hz nPts (int): number of points dwell (float): time in seconds to wait at each sweep point. Default is minimum. Returns: None ''' self.swpRange = [startFreq, stopFreq] if nPts is not None: self.setConfigParam('SENS:SWE:POIN', nPts) if dwell is not None: self.setConfigParam('SENS:SWE:DWEL', dwell) if ifBandwidth is not None: self.setConfigParam('SENS:IF:FREQ', ifBandwidth) self.getSwpDuration(forceHardware=True)
[docs] def sweepEnable(self, swpState=None): ''' Switches between sweeping (True) and CW (False) modes Args: swpState (bool): If None, only gets, doesn't set. Returns: (bool): is the output sweeping ''' if swpState is not None: self.setConfigParam('SENS:SWE:TYPE', 'LIN' if swpState else 'CW') if self.swpRange is not None: self.setConfigParam('SENS:FREQ:STAR', self.swpRange[0], forceHardware=True) # Hack self.setConfigParam('SENS:FREQ:STOP', self.swpRange[1], forceHardware=True) # Hack return self.getConfigParam('SENS:SWE:TYPE') == 'LIN'
[docs] def normalize(self): pass
[docs] def triggerSetup(self, useAux=None, handshake=None, isSlave=False): prefix = 'TRIG:CHAN{}:AUX{}'.format(self.chanNum, self.auxTrigNum) self.setConfigParam(prefix + ':INT', 'SWE') self.setConfigParam(prefix + ':POS', 'BEF') self.setConfigParam('TRIG:SOUR', 'EXT' if isSlave else 'IMM') self.__enaBlock(prefix + ':HAND', handshake) return self.__enaBlock(prefix, useAux)
[docs] def getSwpDuration(self, forceHardware=False): return float(self.getConfigParam('SENS:SWE:TIME', forceHardware=forceHardware))
[docs] def measurementSetup(self, measType='S21', chanNum=None): if chanNum is None: chanNum = self.chanNum traceNum = chanNum # First let's see the measurements already on this channel retStr = self.query('CALC{}:PAR:CAT:EXT?'.format(chanNum)).strip('"') if retStr == 'NO CATALOG': activeMeasTypes = [] activeMeasNames = [] else: activeMeasTypes = retStr.split(',')[1::2] activeMeasNames = retStr.split(',')[::2] newMeasName = 'ANT{}_{}'.format(chanNum, measType) if len(activeMeasTypes) == 1 and measType == activeMeasTypes[0] and newMeasName == activeMeasNames[0]: # It is already set up changed = False else: # Clear them for mName in activeMeasNames: self.write("CALC{}:PAR:DEL '{}'".format(chanNum, mName)) # make a new measurement self.setConfigParam("CALC{}:PAR:EXT".format(chanNum), "'{}', '{}'".format( newMeasName, measType), forceHardware=True) self.setConfigParam('DISP:WIND:TRACE{}:FEED'.format(traceNum), "'{}'".format(newMeasName), forceHardware=True) changed = True self.setConfigParam('CALC{}:PAR:MNUM'.format(self.chanNum), self.chanNum, forceHardware=changed) # self.setConfigParam('CALC{}:PAR:SEL'.format(self.chanNum), self.chanNum, forceHardware=changed) # wait for changes to take effect # This could be improved by something like *OPC? corresponding to the end # of the first sweep time.sleep(self.getSwpDuration())
[docs] def spectrum(self): # raise NotImplementedError('not working') # self.setConfigParam('SENS:SWE:GRO:COUN', nGroups) self.setConfigParam('SENS:SWE:MODE', 'HOLD') self.write('SENS:SWE:MODE SING') self.query('*OPC?') self.setConfigParam('FORM', 'ASC') self.open() dbm = self.query_ascii_values('CALC{}:DATA? FDATA'.format(self.chanNum)) self.close() fStart = float(self.getConfigParam('SENS:FREQ:STAR')) fStop = float(self.getConfigParam('SENS:FREQ:STOP')) freqs = np.linspace(fStart, fStop, len(dbm)) # return freqs, dbm return Spectrum(freqs, dbm)
# fixme: get this out of here.
[docs] def multiSpectra(self, nSpect=1, livePlot=False): bund = FunctionBundle() for iSpect in range(nSpect): s = self.spectrum() bund.addDim(s) if livePlot: s.simplePlot() display.clear_output() display.display(plt.gcf()) else: print('Took spectrum {} of {}'.format(iSpect + 1, nSpect)) print('done.') return bund
def __enaBlock(self, param, enaState=None, forceHardware=False): ''' Enable wrapper that transitions from bool to whatever the equipment might put out. Args: param (str): the configuration string enaState (bool, None): If None, does not set; only gets forceHardware (bool): feeds through to setConfigParam Returns: (bool): is this parameter enabled ''' wordMap = {True: 'ON', False: 'OFF'} trueWords = [True, 1, '1', 'ON'] if enaState is not None: self.setConfigParam(param, wordMap[enaState], forceHardware=forceHardware) return self.getConfigParam(param, forceHardware=forceHardware) in trueWords