In [1]:
# Import SPI rack, D4 and S5i module
from spi_rack import SPI_rack
from D5a.D5a_module import D5a_module
from D4.D4_module import D4_module
from D4.D4_constants import *
# Import plotting library
from bokeh.io import show, output_notebook
from bokeh.plotting import *
from bokeh.models import ColumnDataSource
output_notebook()
from IPython.core import display
import numpy as np
from scipy.optimize import curve_fit
import time
import serial
from datetime import datetime
In [2]:
class Keith_Dev(serial.Serial):
def __init__(self, port, baud, time):
super(Keith_Dev, self).__init__(port, baud, timeout = time)
self.setup()
def getVoltage(self):
self.write(':READ?\r\n')
return self.read(25)
def getName(self):
self.write('*IDN?\r\n')
return self.read(50)
def setup(self):
# Set to DC voltage measurement
self.write(':CONF:VOLT:DC\r\n')
# 1 power line cycle integration
self.write('SENS:VOLT:DC:NPLC 5\r\n')
# set autorange offset
self.write('SENS:VOLT:DC:RANG:AUTO 0\r\n')
# set range to include 5 V
self.write('SENS:VOLT:DC:RANG:UPP 5\r\n')
In [3]:
class Prologix(serial.Serial):
def __init__(self, port, baud, time):
super(Prologix, self).__init__(port, baud, timeout = time)
self.active_address = None
self.flushInput()
def setActive(self, address):
self.write("++addr " + str(address) + "\n")
self.active_address = address
def writeData(self, address, data):
if address != self.active_address:
self.setActive(address)
self.write(data)
In [4]:
class HP3458a_Dev(object):
def __init__(self, controller, address):
self.controller = controller
self.address = address
# Default 10 power line cycles and 8.5 (9) digits
self.reset(10, 9)
def reset(self, NPLC, digits):
# Set output voltage to ASCII with \r\n termination
self.controller.writeData(self.address, "OFORMAT ASCII\r")
# Set DC Voltage, auto range
self.controller.writeData(self.address, "DCV 5\r")
# Set trigger arm to hold
self.controller.writeData(self.address, "TARM HOLD\r")
# Set 100 power line cycles (~2 seconds)
self.controller.writeData(self.address, "NPLC " + str(NPLC) + "\r")
# Set 8.5 digit resolution
self.controller.writeData(self.address, "NDIG " + str(digits) + "\r")
# Set take single reading
self.controller.writeData(self.address, "NRDGS 1,AUTO\r")
self.controller.writeData(self.address, "END ALWAYS\r")
# Flush the input buffer with any remaining data
self.controller.flushInput()
def getTemp(self):
self.controller.writeData(self.address, "TEMP?\r")
# Keeps checking if data is ready
while True:
data = self.controller.readline()
if data != '':
break
return data
def getVoltage(self):
self.controller.writeData(self.address, "TARM SGL,1\r")
# Keeps checking if data is ready
while True:
data = self.controller.readline()
if data != '':
break
return data
In [5]:
spi_rack = SPI_rack('COM18', 1000000, 1)
In [6]:
D4 = D4_module(spi_rack, 1)
D5a = D5a_module(spi_rack, 3)
# Use DAC 2 with filtered output @10 KHz
# Actual swing will be +- 2 Volts
D5a.change_span_update(2, D5a.range_4V_bi)
In [26]:
GPIB = Prologix("COM22", 9600, 0.1)
In [27]:
# Init DMM and load default settings
#DMM = Keith_Dev("COM1", 19200, 1)
DMM = HP3458a_Dev(GPIB, 22)
Create array with evenly spaced values. Sweep the DAC and measure both Keithley and ADC. The settling of the DAC (5 tau) is a handfull of microseconds. Time it takes to send data to ADC should be enough to settle. Read Keithley afterwards to not influence ADC. Number of line cycles integrated for keithley mentioned above. ADC filter settling time: 60 ms and 105 dB 50 Hz supression
In [93]:
# Define dac values and step amount
step_amount = 40
DAC_values = np.linspace(0, 2**18-1, step_amount, dtype=np.int32)
#DAC_values = np.linspace(2**18-1, 0, step_amount, dtype=np.int32)
#DAC_values = np.linspace(2**16, 196608, step_amount, dtype=np.int32)
#DAC_values = np.linspace(196608, 2**16, step_amount, dtype=np.int32)
# Create measurement arrays
#ADC_meas = np.zeros(len(DAC_values))
ADC2_meas = np.zeros(len(DAC_values))
DMM_meas = np.zeros(len(DAC_values))
print("Start measurement...")
# Sweep DAC and measure both keithley and ADC
for index in range(len(DAC_values)):
D5a.change_value_update(2, DAC_values[index])
time.sleep(0.1)
#ADC_meas[index] = D4.singleConversion(1)
ADC2_meas[index] = 2*D4.singleConversion(0)
DMM_meas[index] = float(DMM.getVoltage())
print("Done measuring!")
Plot the sweep data for inspection. Can already see an offset between keithley and D4 module. Gain error not immediately obvious
In [94]:
# Plot measurement data
p = figure(title="DAC sweep", x_axis_label='DAC value', y_axis_label='Measured data (V)')
#p.line(DAC_values, ADC_meas, legend="D4-1")
#p.circle(DAC_values, ADC_meas, legend="D4-1", fill_color="white", size=8)
p.line(DAC_values, ADC2_meas, legend="D4-0", color="olive")
p.circle(DAC_values, ADC2_meas, legend="D4-0", fill_color="olive", size=8)
p.line(DAC_values, DMM_meas, legend="DMM", color="firebrick")
p.circle(DAC_values, DMM_meas, legend="DMM", fill_color="firebrick", size=8)
show(p)
Out[94]:
For correct error correction: first correct for difference in gain between the D4 and DMM before subtracting. Calculate slope for both traces and adjust the slope of the D4 until it is equal to the DMM. Then offset can be removed if wanted and traces can be subtracted. Slope has been calculated by linear interpolating of the two end points of both traces: $a=\frac{dy}{dx}$
In [95]:
DMM_slope = (DMM_meas[-1] - DMM_meas[0]) / (DAC_values[-1] - DAC_values[0])
#D41_slope = (ADC_meas[-1] - ADC_meas[0]) / (DAC_values[-1] - DAC_values[0])
D42_slope = (ADC2_meas[-1] - ADC2_meas[0]) / (DAC_values[-1] - DAC_values[0])
#correction_val1 = (DMM_slope/D41_slope)
correction_val2 = (DMM_slope/D42_slope)
In [96]:
ADC_cor1 = ADC_meas * correction_val1
ADC_cor2 = ADC2_meas * correction_val2
#ADC_cor1 = ADC_cor1 + (DMM_meas[0] - ADC_cor1[0])
ADC_cor2 = ADC_cor2 + (DMM_meas[0] - ADC_cor2[0])
#error_values1 = DMM_meas - ADC_cor1
error_values2 = DMM_meas - ADC_cor2
In [97]:
# Plot measurement data
p2 = figure(title="Error values (DMM - D4)", x_axis_label='Measured DMM (V)', y_axis_label='Measurement error (V)')
#p2.line(DMM_meas, error_values1, legend="Error")
#p2.circle(DMM_meas, error_values1, legend="Error", fill_color="white", size=8)
p2.line(DMM_meas, error_values2, legend="Error", color="firebrick")
p2.circle(DMM_meas, error_values2, legend="Error", fill_color="firebrick", size=8)
show(p2)
Out[97]:
In [ ]:
def func(x, a, b, c, d, e, f, g, h, i):
return a*x**8 + b*x**7 + c*x**6 + d*x**5 + e*x**4 + f*x**3 + g*x**2 + h*x + i
In [ ]:
popt, pcov = curve_fit(func, DMM_meas, error_values1)
print popt
error_fit = func(DMM_meas, popt[0], popt[1], popt[2], popt[3], popt[4], popt[5], popt[6], popt[7], popt[8])
In [ ]:
# Plot measurement data
p = figure(title="Error values (DMM - D4)", x_axis_label='Measured DMM (V)', y_axis_label='Measurement error (V)')
p.line(DMM_meas, error_values1, legend="Error")
p.circle(DMM_meas, error_values1, legend="Error", fill_color="white", size=8)
p.line(DMM_meas, error_values2, legend="Error2", color="olive")
p.circle(DMM_meas, error_values2, legend="Error2", fill_color="olive", size=8)
p.line(DMM_meas, error_fit, legend="Error Fit", color="firebrick")
p.circle(DMM_meas, error_fit, legend="Error Fit", fill_color="firebrick", size=8)
show(p)
In [ ]:
error1_corr = error_values1 - error_fit
error2_corr = error_values2 - error_fit
# Plot measurement data
p = figure(title="Error values corrected", x_axis_label='Measured DMM (V)', y_axis_label='Measurement error (V)')
p.line(DMM_meas, error1_corr, legend="Error")
p.circle(DMM_meas, error1_corr, legend="Error", fill_color="white", size=8)
p.line(DMM_meas, error2_corr, legend="Error2", color="firebrick")
p.circle(DMM_meas, error2_corr, legend="Error2", fill_color="firebrick", size=8)
show(p)
In [49]:
D4.filter_val = 16
D4.buf_en = 1
D4.setup_ADC()
In [50]:
print D4.mode
print D4.filter_val
print D4.buf_en
In [20]:
no_points = 100
samples = range(no_points)
ADC_time_meas = np.zeros(no_points)
print("Start measurement...")
for index in samples:
ADC_time_meas[index] = D4.singleConversion(1)
#time.sleep(0.2)
print ADC_time_meas
print("Done measuring!")
In [21]:
# Plot measurement data
p3 = figure(title="Time trace D4", x_axis_label='Sample no', y_axis_label='D4 measurement (V)')
p3.line(samples, ADC_time_meas, legend="D4")
p3.circle(samples, ADC_time_meas, legend="D4", fill_color="white", size=8)
show(p3)
Out[21]:
In [9]:
D4.filter_val = 5
D4.buf_en = 1
D4.setup_ADC()
In [10]:
no_of_points = 150
x = range(no_of_points)
y = np.zeros(no_of_points)
In [11]:
source = ColumnDataSource(data = dict(x=x, y=y))
p = figure(plot_width=900)
p.line(x, y, source=source)
p.circle(x, y, legend="D4", fill_color="white", size=6, source=source)
Out[11]:
In [12]:
def update_plot(new_val):
y = source.data['y']
y = np.roll(y, -1)
y[-1] = new_val
source.data['y'] = y
display.clear_output()
source.push_notebook()
In [13]:
show(p)
Out[13]:
In [15]:
while True:
update_plot(D4.singleConversion(1))
#time.sleep(0.2)
In [ ]:
t_no = 4000
t_val = range(t_no)
t_start = datetime.now()
for index in t_val:
D4.singleConversion(0)
t_stop = datetime.now()
dt = t_stop - t_start
dt_t = dt.seconds+dt.microseconds*1e-6
print("Sample rate: " + str(float(t_no)/dt_t))
In [29]:
spi_rack.close()
In [ ]:
Keithley.close()
In [ ]:
GPIB.close()
In [ ]:
In [7]:
D5a.set_voltage(2,-1)
In [8]:
#print DMM.getVoltage()
#print 2*D4.singleConversion(0)
print D4.singleConversion(1)
In [ ]:
print D4.filter_val
In [89]:
D5a.change_value_update(3,0)
In [ ]: