diff --git a/bench/keysight/e36233a.py b/bench/keysight/e36233a.py index 43d0625..f06a3dc 100644 --- a/bench/keysight/e36233a.py +++ b/bench/keysight/e36233a.py @@ -69,7 +69,7 @@ def operational_mode(self) -> str: PAR: Channel is in parallel mode """ self._set_channel() - return str(self.parent._instr.query("OUTP:PAIR?")).replace("\n","") + return str(self.parent._instr.query("OUTP:PAIR?")).replace("\n", "") @operational_mode.setter def operational_mode(self, value: str): diff --git a/bench/keysight/n9040b.py b/bench/keysight/n9040b.py index e1346c0..216254d 100644 --- a/bench/keysight/n9040b.py +++ b/bench/keysight/n9040b.py @@ -1,10 +1,9 @@ import logging -import time import os +import time from typing import Union import pyvisa - from bench.common import Common @@ -14,7 +13,7 @@ class N9040B(Common): id = "N9040B" """Substring returned by IDN query to identify the device""" - _markers = [*range(1,12+1)] + _markers = [*range(1, 12 + 1)] def peak_search_marker(self, marker=1): """Set max peak search for a specific marker""" @@ -37,21 +36,83 @@ def reset(self): def span(self) -> float: """Returns the Span in HZ""" return float(self._instr.query("FREQ:SPAN?")) - + @span.setter def span(self, value: float): """Sets the span in Hz""" - self._instr.write("FREQ:SPAN "+str(value)) + self._instr.write("FREQ:SPAN " + str(value)) @property def center_frequency(self) -> float: """Returns the current center frequency in Hz""" return float(self._instr.query("FREQ:CENT?")) - + @center_frequency.setter def center_frequency(self, value: Union[str, float]): """Sets the center frequency in Hz""" - self._instr.write("FREQ:CENT "+str(value)) + self._instr.write("FREQ:CENT " + str(value)) + + @property + def peak_table(self): + """Get peak table status""" + return self._instr.query(":CALC:MARK:TABLe:STAT?") + + @peak_table.setter + def peak_table(self, value: int): + """Sets the peak table status""" + self._instr.write(f":CALC:MARK:PEAK:TABLe:STAT {value}") + self._instr.write(":CALC:MARK:PEAK:SORT FREQ") + self._instr.write(":CALC:MARK:PEAK:TABLe:READ ALL") + + def get_peak_table( + self, peak_threshold_dBm: float, excursion_dB: float = 5 + ) -> dict: + """Get table of peaks + + Args: + peak_threshold_dBm (float) : Minimum threshold for peak + excursion_dB (float) : Minimum variation to be considered a peak + """ + data = self._instr.query( + f":CALCulate:DATA:PEAKs? {peak_threshold_dBm},{excursion_dB}" + ) + data = data.split(",") + num_peaks = data[0] + if num_peaks == 0: + return None + data = data[1:] + data = [float(d) for d in data] + peaks = [] + for i in range(0, len(data), 2): + peaks.append({"amplitude_dBm": data[i], "frequency_hz": data[i + 1]}) + return peaks + + def measure_sfdr( + self, + span_hz=None, + center_frequency_hz=None, + max_iterations: int = 4, + level_step_dBm: float = 6, + ) -> float: + """Determine SFDR in dBc + + Args: + span_hz (int optional): Set span in Hz + center_frequency_hz (int optional): Set center frequency in Hz + max_iterations (int optional): Maximum number of iterations to get at least 2 peaks + level_step_dBm (float optional): Noise steps to decrease threshold until peak count > 1 + """ + if span_hz: + self.span = span_hz + if center_frequency_hz: + self.center_frequency = center_frequency_hz + level = -100 + level_step_dBm + for _ in range(max_iterations): + level = level - level_step_dBm + peaks = self.get_peak_table(level) + if len(peaks) >= 2: + return peaks[0]["amplitude_dBm"] - peaks[1]["amplitude_dBm"] + raise Exception(f"Not enough peaks found. Min level used ({level} dBm)") def screenshot(self, filename: str = "N9040B_sc.png"): """Takes a screenshot on PXA and returns it locally @@ -64,8 +125,8 @@ def screenshot(self, filename: str = "N9040B_sc.png"): if not os.path.isdir(path): os.mkdir(path) - if '.png' not in filename: - filename = f'{filename}.png' + if ".png" not in filename: + filename = f"{filename}.png" just_filename = os.path.basename(filename) self._instr.write(f':MMEM:STOR:SCR "D:\\{just_filename}"') @@ -74,8 +135,8 @@ def screenshot(self, filename: str = "N9040B_sc.png"): self._instr.write(f':MMEM:DATA? "D:\\{just_filename}"') data = self._instr.read_raw() # Extract PNG data - start = data.index(b'PNG') - 1 + start = data.index(b"PNG") - 1 data = data[start:] - with open(filename, 'wb') as f: + with open(filename, "wb") as f: f.write(data) - print(f"Wrote file: {filename}") \ No newline at end of file + print(f"Wrote file: {filename}") diff --git a/bench/rs/__init__.py b/bench/rs/__init__.py index 9d15050..cc725ff 100644 --- a/bench/rs/__init__.py +++ b/bench/rs/__init__.py @@ -1 +1 @@ -from .sma100a import SMA100A \ No newline at end of file +from .sma100a import SMA100A diff --git a/bench/rs/sma100a.py b/bench/rs/sma100a.py index ef65412..92f73a7 100644 --- a/bench/rs/sma100a.py +++ b/bench/rs/sma100a.py @@ -3,6 +3,7 @@ import pyvisa from bench.common import Common + class SMA100A(Common): """Rohde & Schwarz SMA100A Signal Generator""" @@ -15,30 +16,30 @@ def _post_init_(self, address: str = None, use_config_file=False) -> None: @property def frequency(self): """Get Frequency in Hz""" - return float(self._instr.query('SOUR:FREQ:CW?')) + return float(self._instr.query("SOUR:FREQ:CW?")) @frequency.setter - def frequency(self, value:float): + def frequency(self, value: float): """Set Frequency in Hz""" - self._instr.write(f'FREQ {value}') + self._instr.write(f"FREQ {value}") @property def level(self): """Get output power level in dBm""" - return float(self._instr.query('SOUR:POW:POW?')) + return float(self._instr.query("SOUR:POW:POW?")) @level.setter def level(self, value): """Set output power level in dBm""" - self._instr.write(f'POW {value}') + self._instr.write(f"POW {value}") @property def output_enable(self): """Get output state""" - return bool(self._instr.query('Output1:STATe?')) + return bool(self._instr.query("Output1:STATe?")) @output_enable.setter - def output_enable(self, value:bool): + def output_enable(self, value: bool): """Set output state (True == On, False == Off)""" value = "1" if value else "0" - self._instr.write(f'Output1:STATe {value}') \ No newline at end of file + self._instr.write(f"Output1:STATe {value}")