Source code for Instruments_Libraries.M8070B

# -*- coding: utf-8 -*-
"""
Created on Mon Jul  21 18:56:32 2025

@author: Maxim Weizel
"""


import numpy as np
import pyvisa as visa
import matlab


[docs] class M8070B: """ Start the M8070B Software. Go to Utilities-> SCPI Server Information. Copy the VISA resource string (usually localhost). """ def __init__(self, resource_str="TCPIP0::localhost::hislip0::INSTR"): self._resource = visa.ResourceManager().open_resource(str(resource_str), query_delay=0.5) self._channelLS = [1, 2] # self._StateLS_mapping = {"on": 1, "off": 0, 1: 1, 0: 0, "1": 1, "0": 0, True: 1, False: 0} print(self.getIdn())
[docs] def query(self, command): return self._resource.query(command)
[docs] def write(self, command): return self._resource.write(command)
[docs] def Close(self): self._resource.close()
[docs] def getIdn(self): return self.query("*IDN?").strip()
# ============================================================================= # Check functions # ============================================================================= def _validate_channel(self, channel: int) -> int: channel = int(channel) if channel not in self._channelLS: raise ValueError("Channel must be 1 or 2") return channel def _validate_state(self, state: int | str) -> int: state_normalized = self._StateLS_mapping.get( state.lower() if isinstance(state, str) else int(state) ) if state_normalized is None: raise ValueError("Invalid state given! State can be [on,off,1,0,True,False].") return state_normalized # ============================================================================= # M8199B - Get Values and Modes # =============================================================================
[docs] def get_amplitude(self, channel: int = 1) -> float: """Returns the differential amplitude setting for the selected channel. Parameters ---------- channel : int, optional 1 or 2, by default 1 Returns ------- float Differential amplitude setting. """ channel = self._validate_channel(channel) return float(self.query(f":SOURce:VOLTage:AMPLitude? 'M2.DataOut{channel}'"))
[docs] def get_output_state(self, channel: int) -> int: """Returns the output state for the selected channel. Parameters ---------- channel : int Channel 1 or 2 Returns ------- int Output state. 0 or 1. """ channel = self._validate_channel(channel) return int(self.query(f":OUTPut:STATe? 'M2.DataOut{channel}'"))
[docs] def get_delay(self, channel: int) -> float: """Returns the delay for the selected channel in seconds. Parameters ---------- channel : int Channel 1 or 2 Returns ------- float Delay in seconds. """ channel = self._validate_channel(channel) return float(self.query(f":ARM:DELay? 'M2.DataOut{channel}'"))
# ============================================================================= # M8199B - Set Values and Modes # =============================================================================
[docs] def set_amplitude(self, channel: int, amplitude: int | float) -> None: """Differential amplitude setting for the selected channel. Parameters ---------- channel : int Channel 1 or 2 amplitude : int/float Amplitude setting in V. Must be between 0.1 and 2.7 V """ channel = self._validate_channel(channel) if 0.1 <= amplitude <= 2.7: self.write(f":SOURce:VOLTage:AMPLitude 'M2.DataOut{channel}', {amplitude}") else: raise ValueError(f"Value must be between 0.1 and 2.7 V. You entered: {amplitude} V")
[docs] def set_rf_power(self, channel: int, powerdBm: int | float) -> None: """Sets the Signal Generator Output Power in dBm. Parameters ---------- channel : int Channel 1 or 2 power : int/float Output Power in dBm """ power_watt = 10 ** (powerdBm / 10) * 1e-3 V_rms = (50 * power_watt) ** 0.5 # 50 Ohm System amplitude = V_rms * np.sqrt(2) self.set_amplitude(channel, amplitude)
[docs] def set_OutputPowerLevel(self, channel: int, powerdBm: int | float) -> None: """Sets the Signal Generator Output Power in dBm. Alias for set_rf_power(). Parameters ---------- channel : int Channel 1 or 2 value : int/float Output Power in dBm """ self.set_rf_power(channel, powerdBm)
[docs] def set_output(self, channel: int, state: int | str) -> None: """Activate or deactivate the selected channel output. Parameters ---------- channel : int Channel 1 or 2 state : int | str One of: 0, 1, "off", "on" Raises ------ ValueError Channel must be 1 or 2. State must be 0 or 1. """ channel = self._validate_channel(channel) state_normalized = self._validate_state(state) self.write(f":OUTPut:STATe 'M2.DataOut{channel}', {state_normalized}")
[docs] def set_rf_output(self, channel: int, state: int | str) -> None: """Activate or deactivate the selected channel output. Alias for set_output(). """ self.set_output(channel, state)
[docs] def set_delay(self, channel: int, delay: float) -> None: """Set the delay for the selected channel in seconds. Parameters ---------- channel : int Channel 1 or 2 delay : float Delay in seconds. """ channel = self._validate_channel(channel) if not (-25e-9 <= delay <= 25e-9): raise ValueError(f"Delay must be between -25 and 25ns. You entered: {delay} s") self.write(f":ARM:DELay 'M2.DataOut{channel}',{delay}")
# ============================================================================= # M8008A Clock Module - Get Values and Modes # =============================================================================
[docs] def get_sample_clk_out_frequency(self, channel: int = 1) -> float: """Returns the sample clock OUT1 or OUT2 frequency from the M8008A CLK module. Both freqeuncies are the same. Parameters ---------- channel : int, optional 1 or 2, by default 1 Returns ------- float Sample clock output frequency in Hz. """ channel = self._validate_channel(channel) return float(self.query(f":OUTPut:FREQuency? 'M1.SampleClkOut{channel}'"))
[docs] def get_sample_clk_out2_state(self) -> int: """Returns the sample clock OUT2 state from the M8008A CLK module. Sample clock OUT1 cannot be turned off. Returns ------- int Sample clock output state. 0 or 1. """ channel = self._validate_channel(channel) return int(self.query(f":OUTPut:STATe? 'M1.SampleClkOut2'"))
[docs] def get_sample_clk_out2_power(self) -> float: """Returns the sample clock OUT2 Power in dBm from the M8008A CLK module. Sample clock OUT1 cannot be influenced. Returns ------- float Sample Clock OUT2 Power in dBm. """ return float(self.query(f":OUTPut:POWer? 'M1.SampleClkOut2'"))
# ============================================================================= # M8008A Clock Module - Set Values and Modes # =============================================================================
[docs] def set_sample_clk_out2_state(self, state: int | str) -> None: """Sets the sample clock OUT2 state from the M8008A CLK module. Sample clock OUT1 cannot be turned off. Parameters ---------- state : int | str One of: 0, 1, "off", "on" """ state_normalized = self._validate_state(state) self.write(f":OUTPut:STATe 'M1.SampleClkOut2', {state_normalized}")
[docs] def set_sample_clk_out2_power(self, power: int | float) -> None: """Sets the sample clock OUT2 Power in dBm from the M8008A CLK module. Sample clock OUT1 cannot be influenced. Parameters ---------- power : int | float Sample Clock OUT2 Power in dBm. Must be between -5 and 12 dBm """ if not (-5 <= power <= 12): raise ValueError(f"Power must be between -5 and 12 dBm. You entered: {power} dBm") self.write(f":SOURce:POWer 'M1.SampleClkOut2',{power}")
# ============================================================================= # M8199B Calling IQTools Functions # =============================================================================
[docs] def set_freq_CW( self, matlab_engine, channel: int, frequency: float, correction: int = 0, run: int = 1, fs: float = 256e9, ) -> None: """Set the CW tone frequency on the AWG via MATLAB engine. Parameters ---------- matlab_engine : matlab.engine An active MATLAB engine session. channel : int AWG channel (1 or 2). frequency : float Tone frequency in Hz. correction : int, optional Enable correction (default 0). run : int, optional AWG run number (default 1). fs : float, optional AWG sample rate (default 256e9). """ # 1) Validate channel channel = self._validate_channel(channel) # 2) Define constants # magnitude is zeros(1,1) in MATLAB; make it a 1×1 double magnitude = matlab.double([[0]]) # in dB # fmt: off # 3) Build channelMapping # MATLAB expects numeric arrays, not raw Python lists if channel == 1: py_map = [[1, 0], [0, 0]] else: py_map = [[0, 0], [1, 0]] channel_mapping = matlab.double(py_map) # 4) Call iqtone to generate the IQ vector # We ask for 5 outputs so that the last one is chMap. iqdata, _, _, _, chMap = matlab_engine.iqtone( 'sampleRate', fs, 'numSamples', 0, 'tone', frequency, 'phase', 'Random', 'normalize', 1, 'magnitude', magnitude, 'correction', correction, 'channelMapping', channel_mapping, nargout=5 ) # 6) Push the generated IQ out to the AWG matlab_engine.iqdownload( iqdata, fs, 'channelMapping', chMap, 'segmentNumber', 1, 'run', run, nargout=0 )
# fmt: on
[docs] def iqdownload( self, matlab_engine, iqdata, fs: float, *, segment_number: int = 1, normalize: bool = True, channel_mapping=None, sequence=None, marker=None, arb_config=None, keep_open: bool = False, run: bool = True, segment_length=None, segment_offset=None, lo_amplitude=None, lo_f_center=None, segm_name=None, rms=None, ) -> any: """ Download a pre-generated IQ waveform to the AWG. Parameters ---------- matlab_engine : matlab.engine Active MATLAB engine session. iqdata : array-like Real or complex samples (each column = one waveform). Can be empty for a connection check. fs : float Sample rate in Hz. segment_number : int, optional Which segment to download into (default=1). normalize : bool, optional Auto-scale to DAC range (default=True). channel_mapping : array-like, optional 2xM logical matrix mapping IQ data columns to AWG channels. sequence : any, optional Sequence table descriptor. marker : array-like of int, optional Marker bits per sample. arb_config : struct, optional AWG configuration struct (default from arbConfig file). keep_open : bool, optional If True, leave connection open after download (default=False). run : bool, optional If True, start AWG immediately after download (default=True). segment_length, segment_offset, lo_amplitude, lo_f_center, segm_name, rms : Other advanced options as per MATLAB doc. Returns ------- result The output of the MATLAB `iqdownload` call (empty or status). """ # fmt: off # Build the var/val list args = [ 'segmentNumber', int(segment_number), 'normalize', int(normalize), 'keepOpen', int(keep_open), 'run', int(run) ] # fmt: on if channel_mapping is not None: args += ["channelMapping", channel_mapping] if sequence is not None: args += ["sequence", sequence] if marker is not None: args += ["marker", marker] if arb_config is not None: args += ["arbConfig", arb_config] if segment_length is not None: args += ["segmentLength", segment_length] if segment_offset is not None: args += ["segmentOffset", segment_offset] if lo_amplitude is not None: args += ["loAmplitude", lo_amplitude] if lo_f_center is not None: args += ["loFcenter", lo_f_center] if segm_name is not None: args += ["segmName", segm_name] if rms is not None: args += ["rms", rms] # Call MATLAB result = matlab_engine.iqdownload(iqdata, fs, *args, nargout=1) return result