import serial from serial.tools.list_ports import comports import numpy as np import time import matplotlib.pyplot as plt def get_comports(): return [c.name for c in comports()] class miniVNA_PRO2(): '''All information here was ripped from the "open source" JAR that mRS provides''' port = '' data = np.array([]) freqs = np.array([]) infostr = 'uninitialised' # calibration e00 = 0 e11 = 0 d_e = 0 calibrated = False properties = { 'max_freq': 220_000_000, #Hz 'min_freq': 10_000, 'min_samplerate': 0, 'max_samplerate': 4, 'generator_ports': 2, # num 'mult': 8.25955249230769, } def __init__(self, port=''): if(port == ''): cs = get_comports() if(len(cs) == 1): print('miniVNA PRO2: No COM port provided. Using the only one available, ' + cs[0]) port = cs[0] else: print('miniVNA PRO2: Please provide a COM port from the following:\n' + str(cs)) raise Exception self.port = serial.Serial(port, 921600, timeout=1, parity=serial.PARITY_NONE) self.reset() print('miniVNA PRO2 connected with serial information:') print(self.get_info()) def __del__(self): self.port.close() def calibrate(self, freqs): #https://k6jca.blogspot.com/2019/12/vna-notes-on-12-term-error-model-and.html self.calibrated_freqs = freqs Gm1 = np.loadtxt('short.txt', dtype=np.complex128) Gm2 = np.loadtxt('load.txt', dtype=np.complex128) Gm3 = np.loadtxt('open.txt', dtype=np.complex128) G1 = -1 G2 = 0.1 G3 = +1 self.e00 = (G1*G2*Gm1*Gm3 - G1*G3*Gm1*Gm2 - G1*G2*Gm2*Gm3 + G2*G3*Gm1*Gm2 + G1*G3*Gm2*Gm3 - G2*G3*Gm1*Gm3)/(G1*G2*Gm1 - G1*G2*Gm2 - G1*G3*Gm1 + G1*G3*Gm3 + G2*G3*Gm2 - G2*G3*Gm3) self.e11 = -(G1*Gm2 - G2*Gm1 - G1*Gm3 + G3*Gm1 + G2*Gm3 - G3*Gm2)/(G1*G2*Gm1 - G1*G2*Gm2 - G1*G3*Gm1 + G1*G3*Gm3 + G2*G3*Gm2 - G2*G3*Gm3) self.d_e = -(G1*Gm1*Gm2 - G1*Gm1*Gm3 - G2*Gm1*Gm2 + G2*Gm2*Gm3 + G3*Gm1*Gm3 - G3*Gm2*Gm3)/(G1*G2*Gm1 - G1*G2*Gm2 - G1*G3*Gm1 + G1*G3*Gm3 + G2*G3*Gm2 - G2*G3*Gm3) self.calibrated = True def write_cmd(self, args): if not(isinstance(args, list)): args = [args] args = [ str(a) for a in args ] c = '\r'.join(args) + '\r' self.port.write(bytes(c, 'utf-8')) def read_line(self): return self.port.readline()[:-2].decode('utf-8') # last two bytes are always \r\n def get_info(self): self.write_cmd('9') self.port.timeout = 3 self.infostr = self.read_line() return self.infostr def reset(self): self.write_cmd([9,9]) time.sleep(3) self.port.reset_input_buffer() self.port.reset_output_buffer() def sweep(self, f0: float, f1: float, N: int = 128, sample_rate: int = 0, mode = 1): '''Mode 1 designates that we're reading the DET (reflection) port... I think''' print('Starting sweep') st = time.time() d = (f1 - f0) / (N - 1) f0 -= 4 * d N += 4 assert(sample_rate <= self.properties['max_samplerate'] and sample_rate >= self.properties['min_samplerate']) if(d > f1 - 10_000): d = 0.0 if(d == 0.0): i = N else: i = N+1 self.write_cmd(['10' + str(mode), (f0-d) * self.properties['mult'], sample_rate, N+1, (f1-f0+d)/N * self.properties['mult']]) self.port.set_buffer_size(rx_size = 12*(N+10)) while(self.port.in_waiting < 12*N): # prepare the first read. It takes a second, okay? time.sleep(0.1) percent = self.port.in_waiting/(12*N) L = 50 print(f'\rminiVNA PRO2: Progress: {int(100*percent):>3d}% [{"".join(["="]*int(percent*L) + [" "]*(L-int(percent*L)))}]', end='') raw_data = np.zeros((N, 12), dtype=np.uint8) progress = 0 failures = 0 while(progress < N): incoming = self.port.read(12) conv = np.frombuffer(incoming, dtype=np.uint8) raw_data[progress,:len(conv)] = conv progress += 1 et = time.time() print(f'\nSweep took {et-st:.2f}s') self.data = np.zeros((N,), dtype=np.complex128) # processing one at a time to avoid overflows self.data += 1j/2 * raw_data[:,0] self.data += 1j/2 * raw_data[:,1]*256 self.data += 1j/2 * raw_data[:,2]*65536 self.data += 1/2 * raw_data[:,3] self.data += 1/2 * raw_data[:,4]*256 self.data += 1/2 * raw_data[:,5]*65536 self.data -= 1j/2 * raw_data[:,6] self.data += 1j/2 * raw_data[:,7]*256 self.data += 1j/2 * raw_data[:,8]*65536 self.data -= 1/2 * raw_data[:,9] self.data += 1/2 * raw_data[:,10]*256 self.data += 1/2 * raw_data[:,11]*65536 self.freq = np.linspace(f0, f1, N)[4:] self.data = self.data[4:]# / 777_472_127_994 # normalize #self.data = np.convolve(self.data, np.exp(-np.square(1/2 * e00 = np.interp(self.freq, self.calibrated_freqs, self.e00) if self.calibrated else 0 e11 = np.interp(self.freq, self.calibrated_freqs, self.e11) if self.calibrated else 0 d_e = np.interp(self.freq, self.calibrated_freqs, self.d_e) if self.calibrated else -1 self.data = (self.data - e00) / (self.data * e11 - d_e) # (f1 - f0) / (N-1) * i + f0 et = time.time() print(f'Completed sweep. Full function took {et-st:.2f}s') def start_generator(self, freq_I, freq_Q, att_I, att_Q, phase): self.write_cmd([2, freq_I, freq_Q, phase, 3, att_Q, att_I]) time.sleep(3) while(self.port.in_waiting > 0): print(self.port.read(1), end='') m = miniVNA_PRO2('COM9') #plt.plot(m.freq, np.real(m.data), label='0') #plt.plot(m.freq, np.imag(m.data), label='0') #plt.plot(m.freq, np.abs(m.data), label='0', color='k') #m.calibrate(np.linspace(40_000_000, 50_000_000, 512)) #m.start_generator(40_000_000, 50_000_000, 30, 30, 0) #m.test_gen() f0 = 15_000_000 f1 = 60_000_000 m.sweep(f0, f1, N=512, sample_rate=4) if(False): short = m.data for i in range(10): m.sweep(f0, f1, N=512, sample_rate=4) short += m.data short /= 11 if(True): plt.plot(m.freq, np.imag(m.data)) plt.plot(m.freq, np.real(m.data)) plt.plot(m.freq, np.abs(m.data)) plt.show() if(False): input('attach device') m.sweep(f0, f1, N=512, sample_rate=4) full_data = m.data plt.plot(m.freq, np.abs(full_data)) plt.plot(m.freq, np.abs(cord_data)) plt.plot(m.freq, np.abs(full_data - cord_data)) plt.legend() plt.show() inp = input('save?') if(inp == 'y'): np.savetxt(input('fn') + '.txt', m.data)