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


Loading BokehJS ...

Keithley device class

Class definition for a Keithley 2000 for easier interfacing. Will be set to 10 Volt range altough ADC voltage is limited to +- 2.5 Volt. DAC therefore will also be limited to +- 2.5 Volt.


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')

HP3485 and GPIB classes

Classes to communicate with the Prologix GPIB to USB converter and the HP3485a 8.5 digit multimeter.


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

Init Keithley, SPI rack and modules

Open SPI rack connection and connect modules with correct module number. The D4 module cannot measure more than 2.5 Volt at the moment, so limit DAC range to +- 2 Volts. Open Keithley on serial port with default settings.


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)

DAC sweep

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!")


Start measurement...
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]:

<Bokeh Notebook handle for In[94]>

Error calculation

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]:

<Bokeh Notebook handle for In[97]>

INL Data fit

Try to fit a polynomal to the measured INL data.


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)

D4 Time trace


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


0
16
1

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!")


Start measurement...
[-0.9796229  -0.9796223  -0.9796223  -0.9796223  -0.9796229  -0.97962379
 -0.9796232  -0.9796232  -0.97962379 -0.9796229  -0.9796226  -0.9796232
 -0.9796223  -0.9796226  -0.97962081 -0.97962081 -0.97962141 -0.97962201
 -0.97962141 -0.97962171 -0.97962201 -0.9796226  -0.97962201 -0.97962171
 -0.97962201 -0.97962439 -0.9796229  -0.97962141 -0.9796223  -0.97962379
 -0.9796232  -0.9796226  -0.9796229  -0.9796226  -0.9796235  -0.97962379
 -0.9796229  -0.9796232  -0.97962469 -0.9796235  -0.9796229  -0.97962409
 -0.97962409 -0.97962201 -0.9796229  -0.97962171 -0.97962171 -0.97962141
 -0.9796226  -0.9796232  -0.9796229  -0.9796229  -0.9796232  -0.9796226
 -0.9796229  -0.9796223  -0.9796226  -0.97962171 -0.9796226  -0.9796226
 -0.97962171 -0.97962171 -0.97962052 -0.97962052 -0.97962052 -0.97962022
 -0.97962111 -0.97962111 -0.97962171 -0.9796223  -0.9796232  -0.97962111
 -0.97962111 -0.97962141 -0.97962201 -0.97962081 -0.97962111 -0.97962201
 -0.97962141 -0.97962052 -0.97962171 -0.97962081 -0.97962141 -0.97962141
 -0.9796223  -0.97962171 -0.97962141 -0.97962141 -0.97962171 -0.9796229
 -0.9796229  -0.9796226  -0.9796229  -0.9796223  -0.9796223  -0.97962201
 -0.9796226  -0.9796226  -0.9796223  -0.9796226 ]
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]:

<Bokeh Notebook handle for In[21]>

Live plot


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]:
<bokeh.models.renderers.GlyphRenderer at 0x38e2860>

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]:

<Bokeh Notebook handle for In[13]>


In [15]:
while True:
    update_plot(D4.singleConversion(1))
    #time.sleep(0.2)


D:\Marijn\Anaconda2\lib\site-packages\ipykernel\__main__.py:7: BokehDeprecationWarning: bokeh.models.sources.push_notebook was deprecated in Bokeh 0.11.0; please use bokeh.io.push_notebook instead
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
<ipython-input-15-f5961b7af75c> in <module>()
      1 while True:
----> 2     update_plot(D4.singleConversion(1))
      3     #time.sleep(0.2)

<ipython-input-12-31a142cd087b> in update_plot(new_val)
      4     y[-1] = new_val
      5     source.data['y'] = y
----> 6     display.clear_output()
      7     source.push_notebook()

D:\Marijn\Anaconda2\lib\site-packages\IPython\core\display.pyc in clear_output(wait)
    912     from IPython.core.interactiveshell import InteractiveShell
    913     if InteractiveShell.initialized():
--> 914         InteractiveShell.instance().display_pub.clear_output(wait)
    915     else:
    916         from IPython.utils import io

D:\Marijn\Anaconda2\lib\site-packages\ipykernel\zmqshell.pyc in clear_output(self, wait)
     89         self.session.send(
     90             self.pub_socket, u'clear_output', content,
---> 91             parent=self.parent_header, ident=self.topic,
     92         )
     93 

D:\Marijn\Anaconda2\lib\site-packages\jupyter_client\session.pyc in send(self, stream, msg_or_type, content, parent, ident, buffers, track, header, metadata)
    671         if self.adapt_version:
    672             msg = adapt(msg, self.adapt_version)
--> 673         to_send = self.serialize(msg, ident)
    674         to_send.extend(buffers)
    675         longest = max([ len(s) for s in to_send ])

D:\Marijn\Anaconda2\lib\site-packages\jupyter_client\session.pyc in serialize(self, msg, ident)
    585 
    586         real_message = [self.pack(msg['header']),
--> 587                         self.pack(msg['parent_header']),
    588                         self.pack(msg['metadata']),
    589                         content,

D:\Marijn\Anaconda2\lib\site-packages\jupyter_client\session.pyc in <lambda>(obj)
     93 # disallow nan, because it's not actually valid JSON
     94 json_packer = lambda obj: jsonapi.dumps(obj, default=date_default,
---> 95     ensure_ascii=False, allow_nan=False,
     96 )
     97 json_unpacker = lambda s: jsonapi.loads(s)

D:\Marijn\Anaconda2\lib\site-packages\zmq\utils\jsonapi.pyc in dumps(o, **kwargs)
     38         kwargs['separators'] = (',', ':')
     39 
---> 40     s = jsonmod.dumps(o, **kwargs)
     41 
     42     if isinstance(s, unicode):

D:\Marijn\Anaconda2\lib\json\__init__.pyc in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, encoding, default, sort_keys, **kw)
    249         check_circular=check_circular, allow_nan=allow_nan, indent=indent,
    250         separators=separators, encoding=encoding, default=default,
--> 251         sort_keys=sort_keys, **kw).encode(obj)
    252 
    253 

D:\Marijn\Anaconda2\lib\json\encoder.pyc in encode(self, o)
    206         # equivalent to the PySequence_Fast that ''.join() would do.
    207         chunks = self.iterencode(o, _one_shot=True)
--> 208         if not isinstance(chunks, (list, tuple)):
    209             chunks = list(chunks)
    210         return ''.join(chunks)

KeyboardInterrupt: 

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))

Close connections


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)


-0.994895100594

In [ ]:
print D4.filter_val

In [89]:
D5a.change_value_update(3,0)

In [ ]: