From 90d6b10902ede07d225d349eac5c3ab9731cd38d Mon Sep 17 00:00:00 2001 From: Martin Killenberg <martin.killenberg@desy.de> Date: Mon, 21 Aug 2023 11:52:33 +0200 Subject: [PATCH] feat: multi frequency measurements for the VNA - introduce multiple measurement sets in the dut_measuremets - create one plot per measurement set for time traces - analysis is showing all sets against temperature/humidity --- Python_script/PostPlot.py | 17 ++-- Python_script/VNA.py | 4 +- Python_script/VNA_dummy.py | 39 +++++---- Python_script/analysis.py | 106 ++++++++++++++---------- Python_script/climate-lab-gui.py | 9 +- Python_script/dut_measurement.py | 12 ++- Python_script/first_tempsweep.txt | 2 +- Python_script/prototype.py | 94 ++++++++++++++------- Python_script/test_stand_parameter.json | 2 +- Python_script/vna_measurement.py | 28 +++---- 10 files changed, 193 insertions(+), 120 deletions(-) diff --git a/Python_script/PostPlot.py b/Python_script/PostPlot.py index 40cb084..3c185ad 100644 --- a/Python_script/PostPlot.py +++ b/Python_script/PostPlot.py @@ -33,7 +33,7 @@ class PostPlot: return data_frame - def plot_frame_data(self, data_frame, title ='', time_unit ='min'): + def plot_frame_data(self, data_frame, title, time_unit ='min', measurement_set=None, ): # set title of plot self.fig.suptitle("Measurement "+title, color="red") @@ -46,8 +46,11 @@ class PostPlot: element.set_xlabel("Time [%s]" %time_unit) # make a copy of data_frame in parameter list without changing original during modification - postplot_data_frame = data_frame.copy() - + if measurement_set is None: + postplot_data_frame = data_frame.copy() + else: + postplot_data_frame = data_frame.loc[data_frame['SET_NAME'] == measurement_set].copy().reset_index() + # time stamp of index = 0 is the start point of time axis time_sec = postplot_data_frame.TIMESTAMP - postplot_data_frame.TIMESTAMP[0] @@ -62,7 +65,7 @@ class PostPlot: self.measplot.draw(postplot_data_frame, pdf_name='') # cal PK2PK values of magnitude and phase - PK2PK = self.calc_mag_phase_pkpk_values(data_frame) + PK2PK = self.calc_mag_phase_pkpk_values(postplot_data_frame) self.edit_annotation_in_plot(annotate_string=PK2PK) @@ -141,7 +144,7 @@ if __name__ == '__main__': time_unit = 'min' - storepath = Results_Path + '\\PostPlots' + storepath = os.path.join(Results_Path, 'PostPlots') # search all csv files in results folder csv_file_list = list(Path(Results_Path).glob("**/*.csv")) @@ -157,7 +160,7 @@ if __name__ == '__main__': trace_selection = "" - plot_obj = PostPlot(trace_subplot5= trace_selection) + plot_obj = PostPlot(trace_subplot5=trace_selection) # empty data frame for concat the data frames from csv import to plot full transition concat_data_frame = pd.DataFrame() @@ -169,7 +172,7 @@ if __name__ == '__main__': title = csv_file.name # concatenate data frames for plotting full transition data - concat_data_frame = pd.concat([concat_data_frame,data_frame], ignore_index=True, sort=False) + concat_data_frame = pd.concat([concat_data_frame, data_frame], ignore_index=True, sort=False) plot_obj.plot_frame_data(data_frame, title, time_unit) diff --git a/Python_script/VNA.py b/Python_script/VNA.py index 35d240c..f85fe26 100755 --- a/Python_script/VNA.py +++ b/Python_script/VNA.py @@ -196,9 +196,7 @@ class Vna: self.vna.write("SYST:PRES; *OPC?") self.vna.read() - #Hack: put in the frequency which is configured in the config file to guess if loading was - #successful. FIXME: Do proper error checking here - def load_config(self, config_file, frequency): + def load_config(self, config_file): """ Load the config from the VNA's internal drive. It must be in the folder C:\\Users\\Public\\Documents\\Rohde-Schwarz\\ZNA\\RecallSets\\ diff --git a/Python_script/VNA_dummy.py b/Python_script/VNA_dummy.py index ab2e317..1429a03 100644 --- a/Python_script/VNA_dummy.py +++ b/Python_script/VNA_dummy.py @@ -23,17 +23,17 @@ class VnaDummy: self.simulated_traces = [('Trc1', 's11')] self.result_format = 'SDAT' self.simulated_power = '10.dBm' - self.simulated_frequency = '10.MHz' + self.simulated_frequency = 1000000 self.simulated_magnitude_difference_temp = 0.0 self.simulated_phase_difference_temp = 0.0 self.simulated_magnitude_difference_hum = 0.0 self.simulated_phase_difference_hum = 0.0 self.simulated_state = shared_simulated_state.get_simulated_state() self.last_simulated_time = self.simulated_state.simulated_time - self.tau_mag_temp = 25 # change rate of magnitude in dependence on temperature - self.tau_phase_temp = 35 # change rate of phase in dependence on temperature - self.tau_mag_hum = 250 # change rate of magnitude in dependence on humidity - self.tau_phase_hum = 350 # change rate of phase in dependence on humidity + self.tau_mag_temp = 25 # change rate of magnitude in dependence on temperature + self.tau_phase_temp = 35 # change rate of phase in dependence on temperature + self.tau_mag_hum = 250 # change rate of magnitude in dependence on humidity + self.tau_phase_hum = 350 # change rate of phase in dependence on humidity self.reference_magnitude = 0.7 # at reference temp and reference hum self.reference_phase = 70 # at reference temp and reference hum self.reference_temp = 25 @@ -65,9 +65,8 @@ class VnaDummy: delta_magnitude_temp * \ (1 - (math.exp(-1. * delta_time / self.tau_mag_temp))) self.simulated_magnitude_difference_hum = self.simulated_magnitude_difference_hum + \ - delta_magnitude_hum * \ - (1 - (math.exp(-1. * delta_time / self.tau_mag_hum))) - + delta_magnitude_hum * \ + (1 - (math.exp(-1. * delta_time / self.tau_mag_hum))) def simulate_phase(self): @@ -75,17 +74,17 @@ class VnaDummy: (self.simulated_state.simulated_temperature - self.reference_temp) delta_phase_temp = target_phase_difference_temp - self.simulated_phase_difference_temp target_phase_difference_hum = self.phase_slope_hum * \ - (self.simulated_state.simulated_humidity - self.reference_hum) + (self.simulated_state.simulated_humidity - self.reference_hum) delta_phase_hum = target_phase_difference_hum - self.simulated_phase_difference_hum delta_time = self.simulated_state.simulated_time - self.last_simulated_time self.simulated_phase_difference_temp = self.simulated_phase_difference_temp + \ - delta_phase_temp *\ + delta_phase_temp * \ (1 - (math.exp(-1. * delta_time / self.tau_phase_temp))) self.simulated_phase_difference_hum = self.simulated_phase_difference_hum + \ - delta_phase_hum *\ - (1 - (math.exp(-1. * delta_time / self.tau_phase_hum))) + delta_phase_hum * \ + (1 - (math.exp(-1. * delta_time / self.tau_phase_hum))) def get_tupple_of_current_traces(self): """ @@ -108,7 +107,9 @@ class VnaDummy: self.simulated_power = power def get_current_cw_frequency(self): - return self.simulated_frequency + # Fixme: This returns a string, just like the real VNA. Needs to be fixed in both. We keep this for + # consistency (although the real thing adapt the unit to orders of magnitude (1.MHz instead of 1000000.Hz + return str(self.simulated_frequency) + 'Hz' def set_cw_frequency(self, frequency): self.simulated_frequency = frequency @@ -123,10 +124,12 @@ class VnaDummy: raise Exception('trace ' + trace + ' is not known to VNA dummy!') self.result_format = result_format - simulated_magnitude = self.reference_magnitude + self.simulated_magnitude_difference_temp + \ - self.simulated_magnitude_difference_hum - simulated_phase = self.reference_phase + self.simulated_phase_difference_temp + \ - self.simulated_phase_difference_hum + simulated_magnitude = (self.reference_magnitude + + (self.simulated_magnitude_difference_temp + self.simulated_magnitude_difference_hum) + * 1.3e9 / self.simulated_frequency) + simulated_phase = (self.reference_phase + + (self.simulated_phase_difference_temp + self.simulated_phase_difference_hum) * + self.simulated_frequency / 1.3e9) phase_radian = simulated_phase / 180. * math.pi real_val = simulated_magnitude * math.cos(phase_radian) imaginary_val = simulated_magnitude * math.sin(phase_radian) @@ -148,7 +151,7 @@ class VnaDummy: result.append(float(x)) return result - def load_config(self, config_file, frequency): + def load_config(self, config_file): pass def do_single_sweep(self): diff --git a/Python_script/analysis.py b/Python_script/analysis.py index fed48b8..1bfdd9a 100644 --- a/Python_script/analysis.py +++ b/Python_script/analysis.py @@ -1,28 +1,29 @@ import pandas as pd import matplotlib.pyplot as plt -import math +import numpy as np +from matplotlib import gridspec -def extract_stable_data(datafile): +def extract_stable_data(datafile, measurement_set): datapoint = {} # df is a pandas data frame df = pd.read_csv(datafile) # extract phase mean and variance for stable measurements (don't ask what loc means) - phases = df.loc[df['EQUILIBRIUM_INDICATOR'] == 31, 'S21_PHASE'] + phases = df.loc[(df['EQUILIBRIUM_INDICATOR'] == 31) & (df['SET_NAME'] == measurement_set), 'S21_PHASE'] if phases.size == 0: return None datapoint['phase_mean']=phases.mean() datapoint['phase_var']=phases.var() - magnitudes = df.loc[df['EQUILIBRIUM_INDICATOR'] == 31, 'S21_MAGNITUDE'] + magnitudes = df.loc[(df['EQUILIBRIUM_INDICATOR'] == 31) & (df['SET_NAME'] == measurement_set), 'S21_MAGNITUDE'] datapoint['magnitude_mean'] = magnitudes.mean() datapoint['magnitude_var'] = magnitudes.var() - temperatures = df.loc[df['EQUILIBRIUM_INDICATOR'] == 31, 'READBACK_TEMPERATURE'] + temperatures = df.loc[(df['EQUILIBRIUM_INDICATOR'] == 31) & (df['SET_NAME'] == measurement_set), 'READBACK_TEMPERATURE'] datapoint['temperature_mean'] = temperatures.mean() datapoint['temperature_var'] = temperatures.var() - humidities = df.loc[df['EQUILIBRIUM_INDICATOR'] == 31, 'READBACK_HUMIDITY'] + humidities = df.loc[(df['EQUILIBRIUM_INDICATOR'] == 31) & (df['SET_NAME'] == measurement_set), 'READBACK_HUMIDITY'] datapoint['humidity_mean'] = humidities.mean() datapoint['humidity_var'] = humidities.var() @@ -30,55 +31,76 @@ def extract_stable_data(datafile): # sweep_type is either 'temperature' or 'humidity' -def plot_sweep(temperatures, humidities, basename, sweep_type): - x_data = [] - phases = [] - phase_vars = [] - magnitudes = [] - magnitude_vars = [] +def plot_sweep(temperatures, humidities, basename, sweep_type, measurement_sets): + set_data = {} + for measurement_set in measurement_sets: + set_data[measurement_set] = {'phases': [], 'phase_vars': [], 'magnitudes': [], 'magnitude_vars': [], + 'x_data': []} + for temp, hum in zip(temperatures, humidities): datafile = basename+'_'+str(temp)+'deg_'+str(hum)+'rh.csv' print(datafile) - datapoint = extract_stable_data(datafile) - if datapoint is None: - continue - - if sweep_type == 'temperature': - x_data.append(datapoint['temperature_mean']) - elif sweep_type == 'humidity': - x_data.append(datapoint['humidity_mean']) - else: - raise Exception('Unknown sweep_type:'+str(sweep_type)) - - phases.append(datapoint['phase_mean']) - phase_vars.append(datapoint['phase_var']) - magnitudes.append(datapoint['magnitude_mean']) - magnitude_vars.append(datapoint['magnitude_var']) - - fig, ax1 = plt.subplots() + for measurement_set in measurement_sets: + datapoint = extract_stable_data(datafile, measurement_set) + if datapoint is None: + continue + + data = set_data[measurement_set] + if sweep_type == 'temperature': + data['x_data'].append(datapoint['temperature_mean']) + elif sweep_type == 'humidity': + data['x_data'].append(datapoint['humidity_mean']) + else: + raise Exception('Unknown sweep_type:'+str(sweep_type)) + + data['phases'].append(datapoint['phase_mean']) + data['phase_vars'].append(datapoint['phase_var']) + data['magnitudes'].append(datapoint['magnitude_mean']) + data['magnitude_vars'].append(datapoint['magnitude_var']) + + #fig = plt.figure() + #gs = fig.add_gridspec(2, 1, hspace=0) + #(ax1, ax2) = gs.subplots(sharex=True) + fig, (ax1, ax2) = plt.subplots(2, sharex=True, gridspec_kw={'hspace': 0}, figsize=(10, 5)) + ax1.tick_params(bottom=True, top=True, direction='in') + ax2.tick_params(bottom=True, top=True, direction='inout') if sweep_type == 'temperature': - plt.title(basename + ': Temperature sweep at ' + str(humidities[0]) + ' % r.h.') - ax1.set_xlabel('temperature [deg C]') + fig.suptitle(basename + ': Temperature sweep at ' + str(humidities[0]) + ' % r.h.') + ax2.set_xlabel('temperature [deg C]') elif sweep_type == 'humidity': - plt.title(basename + ': Humidity sweep at ' + str(temperatures[0]) + ' deg C') - ax1.set_xlabel('relative humidity [%]') + fig.suptitle(basename + ': Humidity sweep at ' + str(temperatures[0]) + ' deg C') + ax2.set_xlabel('relative humidity [%]') else: raise Exception('Unknown sweep_type:'+str(sweep_type)) - ax1.errorbar(x_data, phases, phase_vars, marker='+', linewidth=0) - ax1.set_ylabel('S21 phase [deg]', color='blue') + for measurement_set in measurement_sets: + ax1.errorbar(set_data[measurement_set]['x_data'], + set_data[measurement_set]['phases'], set_data[measurement_set]['phase_vars'], + label=measurement_set, marker='+', linewidth=0) + ax1.set_ylabel('S21 phase [deg]') - ax2 = ax1.twinx() - ax2.errorbar(x_data, magnitudes, magnitude_vars, marker='x', color='red', linewidth=0) - ax2.set_ylabel('S21 magnitude [dB]', color='red') - - fig.tight_layout() # otherwise the right y-label is slightly clipped + for measurement_set in measurement_sets: + ax2.errorbar(set_data[measurement_set]['x_data'], + set_data[measurement_set]['magnitudes'], set_data[measurement_set]['magnitude_vars'], + label=measurement_set, marker='+', linewidth=0) + ax2.set_ylabel('S21 magnitude [dB]') + + # Remove the highest tick label from the lower subplot. It probably overlaps with the lowest one of the upper plot. + yticks = ax2.yaxis.get_major_ticks() + yticks[-1].set_visible(False) + yticks[-2].label1.set_visible(False) # don't ask me why we have to set the last two tick marks to invisible + ax2.legend(loc='center right', bbox_to_anchor=(1.5, 1.0)) + + fig.tight_layout() # otherwise the legend is clipped + plt.subplots_adjust(top=0.9) # avoid overlap with suptitle - fig.savefig(basename+'_analysis.pdf') + fig.savefig(basename+measurement_set+'_analysis.pdf') plt.show() if __name__ == '__main__': - plot_sweep(range(20, 30+1), [40]*11, 'tempsweep1', 'temperature') + print('run \'./prototype.py -t first_tempsweep.txt -p -o tempsweep1\' to get the data needed for this plot.' ) + plot_sweep(np.arange(25., 31.+1.), [35.]*7, 'tempsweep1', 'temperature', + ['1.3GHz', '1.0GHz', '3.0GHz', '6.0GHz', '10.0GHz']) diff --git a/Python_script/climate-lab-gui.py b/Python_script/climate-lab-gui.py index b77c3da..3f58f3e 100755 --- a/Python_script/climate-lab-gui.py +++ b/Python_script/climate-lab-gui.py @@ -85,9 +85,10 @@ class TestStandMainWindow(QMainWindow): for t in temperatures: temp_extensions.append(str(t) + 'deg_' + str(self.fixedParameter.value()) + 'rh') analysis.plot_sweep(temperatures, [self.fixedParameter.value()] * len(temperatures), output_basename, - 'temperature') + 'temperature', meas.dut.get_measurement_set_names()) prototype.plot_output(output_basename, temp_extensions, True, config_data, ext_sensor_channels, + meas.dut.get_measurement_set_names(), output_basename + ': Temperature sweep ' + str(temperatures[0]) + '--' + str(temperatures[-1]) + ' degC @ ' + str(self.fixedParameter.value()) + ' % r.h.') @@ -100,9 +101,10 @@ class TestStandMainWindow(QMainWindow): for h in humidities: hum_extensions.append(str(self.fixedParameter.value()) + 'deg_' + str(h) + 'rh') analysis.plot_sweep([self.fixedParameter.value()] * len(humidities), humidities, output_basename, - 'humidity') + 'humidity', meas.dut.get_measurement_set_names()) prototype.plot_output(output_basename, hum_extensions, True, config_data, ext_sensor_channels, + meas.dut.get_measurement_set_names(), output_basename + ': Humidity sweep ' + str(humidities[0]) + '--' + str(humidities[-1]) + ' % r.h. @ ' + str(self.fixedParameter.value()) + ' degC') @@ -110,7 +112,8 @@ class TestStandMainWindow(QMainWindow): try: n_measurements = meas.perform_measurements(os.path.join(self.start_dir, self.measurementFile.text())) - prototype.plot_output(output_basename, range(n_measurements), True, ext_sensor_channels, + prototype.plot_output(output_basename, range(n_measurements), True, config_data, ext_sensor_channels, + meas.dut.get_measurement_set_names(), output_basename) except FileNotFoundError as e: QtWidgets.QMessageBox.warning(self, 'Warning', str(e)) diff --git a/Python_script/dut_measurement.py b/Python_script/dut_measurement.py index bffb95d..b2f2171 100644 --- a/Python_script/dut_measurement.py +++ b/Python_script/dut_measurement.py @@ -6,9 +6,10 @@ class DutMeasurement(ABC): Interface for DUT measurements """ @abstractmethod - def get_dut_measurements(self): + def get_dut_measurements(self, set_name): """ - Returns a dictionary with column names a keys and scalar values + Performs the measurements according to the set name and + returns a dictionary with signal names a keys and scalar values """ pass @@ -31,3 +32,10 @@ class DutMeasurement(ABC): """ Return the maximum deltas of the reference signals """ + + @abstractmethod + def get_measurement_set_names(self): + """ + Names / string identifiers of the different measurement sets which are performed at each temperature/humidity + """ + pass diff --git a/Python_script/first_tempsweep.txt b/Python_script/first_tempsweep.txt index a9dfacd..9dc362c 100644 --- a/Python_script/first_tempsweep.txt +++ b/Python_script/first_tempsweep.txt @@ -1,2 +1,2 @@ -25.1 35.1 2.5 35 300 30 +25 31 1 35 100 10 diff --git a/Python_script/prototype.py b/Python_script/prototype.py index 2b708cd..13354cd 100755 --- a/Python_script/prototype.py +++ b/Python_script/prototype.py @@ -81,7 +81,7 @@ class Measurements: self.postplot_obj = None self.measurement_plot = MeasurementPlot.MeasurementPlot(trace_subplot5=config_data['trace_subplot5']) - self.data_collection = [] + self.data_collection_for_online_plot = [] def perform_measurements(self, sweep_file): with open(sweep_file) as file: @@ -155,6 +155,11 @@ class Measurements: return sweep_values def perform_single_measurement(self, output, target_temp, target_hum, soaking_time, n_stable_reads): + """ + A "single measurement refers to a single temperature and humidity setting. + This is a "chamber measurement point". It consists out of multiple measurements sets. The data for all + measurement sets of this chamber point are taken for this measurement. + """ with open(output, mode='w', newline='') as csv_file: fieldnames = ['TIMESTAMP', 'TARGET_TEMPERATURE', 'READBACK_TEMPERATURE', 'TARGET_HUMIDITY', 'READBACK_HUMIDITY', 'DUT_IDENTIFIER', 'RUN_ID', 'EQUILIBRIUM_INDICATOR', 'TEMP_HEATER', @@ -165,7 +170,7 @@ class Measurements: # csv.dict writer add adda row wise writer = csv.DictWriter(csv_file, fieldnames=fieldnames) writer.writeheader() - self.data_collection = [] + self.data_collection_for_online_plot = [] plt.ion() set_const_response = self.chamber.set_const((target_temp, target_hum)) @@ -184,7 +189,9 @@ class Measurements: dut_signal_queues = [[], []] while True: - data = self.read_data() + # Only read the climate chamber data once. The equilibrium indicator is only calculated on the + # primary data set. + data = self.read_data(self.dut.get_measurement_set_names()[0]) # if it is not within the target range reset start time self.temperature_stable = False self.humidity_stable = False @@ -214,7 +221,13 @@ class Measurements: ' °C' + ' | Humid: ' + data.hum + '%' + ' | soaking read nr' + str(len(dut_signal_queues[0]))) self.store_and_plot_data(target_temp, target_hum, data, self.cook_up_equi_indicator()) - writer.writerow(self.data_collection[-1]) + writer.writerow(self.data_collection_for_online_plot[-1]) + + # read and store data for all further sets + for set_name in self.dut.get_measurement_set_names()[1:]: + data.dut_data = self.dut.get_dut_measurements(set_name) + writer.writerow(self.create_data_dictionary(target_temp, target_hum, data, + self.cook_up_equi_indicator())) if self.temperature_stable and self.humidity_stable and self.dut_signals_stable[0] and\ self.dut_signals_stable[1]: @@ -229,22 +242,33 @@ class Measurements: # perform the configured number of measurements and check that they are really stable # It started running after everything become stable - supposedly_stable_measurements = [] + supposedly_stable_measurements = {} + for set_name in self.dut.get_measurement_set_names(): + supposedly_stable_measurements[set_name] = [] all_measurements_stable = True + for i in range(0, n_stable_reads): - data = self.read_data() + # create the main data set for the first measurement set + data = self.read_data(self.dut.get_measurement_set_names()[0]) self.temperature_stable = self.calculate_temperature_stability(target_temp, float(data.temp)) self.humidity_stable = self.calculate_humidity_stability(target_hum, float(data.hum)) this_measurement_stable = (self.temperature_stable and self.humidity_stable) for j, max_delta in enumerate(self.max_delta_dut_signals): value = data.dut_data[self.dut.get_dut_reference_signal_names()[j]] self.dut_signals_stable[j] = (reference_values[j] - max_delta <= value) and\ - (value <= reference_values[j] + max_delta) + (value <= reference_values[j] + max_delta) if not self.dut_signals_stable[j]: this_measurement_stable = False self.store_and_plot_data(target_temp, target_hum, data, self.cook_up_equi_indicator()) - supposedly_stable_measurements.append(self.data_collection[-1]) + supposedly_stable_measurements[self.dut.get_measurement_set_names()[0]].append( + self.data_collection_for_online_plot[-1]) + + # read and store data for all further sets + for set_name in self.dut.get_measurement_set_names()[1:]: + data.dut_data = self.dut.get_dut_measurements(set_name) + supposedly_stable_measurements[set_name].append( + self.create_data_dictionary(target_temp, target_hum, data, self.cook_up_equi_indicator())) if this_measurement_stable: print('Stable measurement ' + str(i+1) + '/' + str(n_stable_reads)) @@ -255,25 +279,26 @@ class Measurements: print('Measurement not stable. Retrying.') break - for measurement in supposedly_stable_measurements: - if all_measurements_stable: - measurement['EQUILIBRIUM_INDICATOR'] = TEMPERATURE_STABLE | HUMIDITY_STABLE | \ - DUT_SIGNAL0_STABLE | DUT_SIGNAL1_STABLE | \ - MEASUREMENT_STABLE - do_another_measurement = False + for set_name in self.dut.get_measurement_set_names(): + for measurement in supposedly_stable_measurements[set_name]: + if all_measurements_stable: + measurement['EQUILIBRIUM_INDICATOR'] = TEMPERATURE_STABLE | HUMIDITY_STABLE | \ + DUT_SIGNAL0_STABLE | DUT_SIGNAL1_STABLE | \ + MEASUREMENT_STABLE + do_another_measurement = False - writer.writerow(measurement) + writer.writerow(measurement) def sleep_until(self, wakeup_time): remaining_sleep_time = wakeup_time - self.clock.time() if remaining_sleep_time > 0: self.clock.sleep(remaining_sleep_time) - def read_data(self): + def read_data(self, measurement_set_name): [temp, hum, mode, alarms] = self.chamber.read_monitor().split(',') perc_temp_heater, perc_hum_heater = self.chamber.get_heater_percentage() - dut_data = self.dut.get_dut_measurements() + dut_data = self.dut.get_dut_measurements(measurement_set_name) temp_dut, temp_room, temp_meas_instr, hum_dut, hum_room, hum_meas_instr, air_press_room = \ self.ext_sensors.get_sensor_values() @@ -286,7 +311,10 @@ class Measurements: hum_dut, hum_room, hum_meas_instr, air_press_room, temp_chamber_meas_instr, hum_chamber_meas_instr) - def store_and_plot_data(self, target_temp, target_hum, data, equi_indicator): + def create_data_dictionary(self, target_temp, target_hum, data, equi_indicator): + """ + Create a dictionary out of the MeasurementData object and some extra data, as needed by the writer + """ measurement = { 'TIMESTAMP': data.timestamp, 'TARGET_TEMPERATURE': target_temp, @@ -295,7 +323,7 @@ class Measurements: 'READBACK_HUMIDITY': float(data.hum), 'EQUILIBRIUM_INDICATOR': equi_indicator, 'TEMP_HEATER': data.percent_temp_heater, - 'HUM_HEATER': data.percent_hum_heater, + 'HUM_HEATER': data.percent_hum_heater, 'TEMP_DUT': data.temp_dut, 'TEMP_ROOM': data.temp_room, 'TEMP_MEAS_INSTR': data.temp_meas_instr, @@ -307,8 +335,12 @@ class Measurements: 'READBACK_HUM_MEAS_INSTR': float(data.hum_chamber_meas_instr), } measurement.update(data.dut_data) - self.data_collection.append(measurement) - data_frame = pd.DataFrame(self.data_collection) + return measurement + + def store_and_plot_data(self, target_temp, target_hum, data, equi_indicator): + measurement = self.create_data_dictionary(target_temp, target_hum, data, equi_indicator) + self.data_collection_for_online_plot.append(measurement) + data_frame = pd.DataFrame(self.data_collection_for_online_plot) self.measurement_plot.draw(data_frame) def current_milli_time(self): @@ -340,7 +372,8 @@ class Measurements: (float(readback_hum) <= target_hum+self.max_delta_hum) -def plot_output(output_basename, measurements_appendices, show_blocking_plot, config_data, ext_sens_data, title = ''): +def plot_output(output_basename, measurements_appendices, show_blocking_plot, config_data, ext_sens_data, + measurement_set_names, title=''): time_unit = str(config_data['time_unit']) @@ -352,13 +385,15 @@ def plot_output(output_basename, measurements_appendices, show_blocking_plot, co measurement_name = output_basename+'_'+str(m) list_of_frames.append(pd.read_csv(measurement_name+'.csv')) - post_plot.plot_frame_data(list_of_frames[-1], title, time_unit,) - post_plot.save_fig(storepath, measurement_name+'.pdf') + for set_name in measurement_set_names: + post_plot.plot_frame_data(list_of_frames[-1], title+set_name, time_unit, set_name) + post_plot.save_fig(storepath, measurement_name+set_name+'.pdf') combined_data_frame = pd.concat(list_of_frames, ignore_index=True, sort=False) - post_plot.plot_frame_data(combined_data_frame, title, time_unit) - post_plot.save_fig(storepath, 'FullTransistion.pdf') + for set_name in measurement_set_names: + post_plot.plot_frame_data(combined_data_frame, title+set_name, time_unit, set_name) + post_plot.save_fig(storepath, output_basename+set_name + '.pdf') if show_blocking_plot: plt.ioff() @@ -431,18 +466,19 @@ if __name__ == '__main__': if args.file: n_measurements = mes.perform_measurements(args.file) plot_output(output_basename, range(n_measurements), args.plot, config_data, ext_sensor_channels, - output_basename) + mes.dut.get_measurement_set_names(), output_basename) if args.temperaturesweepfile: temperatures, humidity = run_temperature_sweep_from_file(args.temperaturesweepfile, mes) # run analysis here temp_extensions = [] for t in temperatures: temp_extensions.append(str(t)+'deg_'+str(humidity)+'rh') - analysis.plot_sweep(temperatures, [humidity]*len(temperatures), output_basename, 'temperature') + analysis.plot_sweep(temperatures, [humidity]*len(temperatures), output_basename, 'temperature', + mes.dut.get_measurement_set_names()) plot_output(output_basename, temp_extensions, args.plot, config_data, ext_sensor_channels, + mes.dut.get_measurement_set_names(), output_basename + ': Temperature sweep ' + str(temperatures[0]) + ' degC to ' + str(temperatures[-1]) + ' degC') - print(str(temp_extensions)) finally: mes.chamber.close() mes.ext_sensors.close() diff --git a/Python_script/test_stand_parameter.json b/Python_script/test_stand_parameter.json index 11ddfeb..56a0a24 100644 --- a/Python_script/test_stand_parameter.json +++ b/Python_script/test_stand_parameter.json @@ -5,7 +5,7 @@ "type": "VNA", "delta_mag": 0.13, "delta_phase": 1.5, - "frequency": 1300000000, + "frequencies": [1300000000, 1000000000, 3000000000, 6000000000, 10000000000], "vna_ip": "mskzna43-lab", "vna_config_file": "CalSetup2.znxml" }, diff --git a/Python_script/vna_measurement.py b/Python_script/vna_measurement.py index 8185049..f6be599 100644 --- a/Python_script/vna_measurement.py +++ b/Python_script/vna_measurement.py @@ -7,15 +7,6 @@ import cmath import dut_measurement -class VnaData: - def __init__(self, power, frequency, s11, s21, s12, s22): - self.power = power - self.frequency = frequency - self.s11 = s11 - self.s21 = s21 - self.s12 = s12 - self.s22 = s22 - class VnaMeasurement(dut_measurement.DutMeasurement): def __init__(self, config_data, target_accuracy): @@ -24,21 +15,26 @@ class VnaMeasurement(dut_measurement.DutMeasurement): self.vna = VNA.create_vna(config_data['vna_ip'], target_accuracy) - self.vna.load_config(config_data['vna_config_file'], config_data['frequency']) + self.vna.load_config(config_data['vna_config_file']) self.vna.create_new_trace("Trace1", "S11") self.vna.create_new_trace("Trace2", "S12") self.vna.create_new_trace("Trace3", "S21") self.vna.create_new_trace("Trace4", "S22") + self.frequencies = {} + for f in config_data['frequencies']: + self.frequencies[str(f / 1e9) + 'GHz'] = f + def _get_trace_data(self, trace): return self.vna.get_list_of_measurement_values(trace, "SDAT") - def get_dut_measurements(self): + def get_dut_measurements(self, set_name): # FIXME: The try/catch should be way down in the VNA class for iteration in range(10): try: power = self.vna.get_current_power() - frequency = self.vna.get_current_cw_frequency() + frequency = self.frequencies[set_name] + self.vna.set_cw_frequency(frequency) self.vna.do_single_sweep() s11 = self._get_trace_data("Trace1") s12 = self._get_trace_data("Trace2") @@ -53,7 +49,8 @@ class VnaMeasurement(dut_measurement.DutMeasurement): 'S21_MAGNITUDE': self.calculate_mean_magnitude_db(s21), 'S21_PHASE': self.calculate_mean_phase(s21), 'S22_MAGNITUDE': self.calculate_mean_magnitude_db(s22), - 'S22_PHASE': self.calculate_mean_phase(s21)} + 'S22_PHASE': self.calculate_mean_phase(s21), + 'SET_NAME': set_name} # exception for pyvisa error or timeout except pyvisa.errors.VisaIOError: @@ -68,7 +65,7 @@ class VnaMeasurement(dut_measurement.DutMeasurement): def get_dut_signal_names(self): return ['RF_POWER', 'RF_FREQUENCY', 'S11_MAGNITUDE', 'S11_PHASE', 'S12_MAGNITUDE', - 'S12_PHASE', 'S21_MAGNITUDE', 'S21_PHASE', 'S22_MAGNITUDE', 'S22_PHASE'] + 'S12_PHASE', 'S21_MAGNITUDE', 'S21_PHASE', 'S22_MAGNITUDE', 'S22_PHASE', 'SET_NAME'] def get_dut_reference_signal_names(self): return ['S21_MAGNITUDE', 'S21_PHASE'] @@ -104,3 +101,6 @@ class VnaMeasurement(dut_measurement.DutMeasurement): def calculate_mean_phase(self, values_list): phases = self.calculate_phases(values_list) return numpy.mean(phases) + + def get_measurement_set_names(self): + return list(self.frequencies.keys()) -- GitLab