---+ Sense Channel Calibration

There are two dominant types of inaccuracy at DC:

  1. Errors in (voltage and current) measurements, and
  2. Errors in the drive current sent through the sensor.
These errors come from a number of sources:

  • The references voltages aren't 100% accurate,
  • The ADC has an intrinsic offset, and
  • The op-amps each have offset voltages (dominated by U5's offset voltage, I think.)
In any case, these offsets may be calibrated by applying a DC offset at both the DACs and ADCs. This document describes a simple two-step process for this calibration.

IDEA! If you are using firmware released on or after Nov. 9, 2011, you should use the calibration scripts checked into the crython repository, in mcgill/gsmecher/cal/. These scripts may be moved from my sandbox into a common path, in which case, you can figure out where they went by searching the output of "git log --stat" for cal1.py and cal2.py.

Step 1: Calibrate the ADCs

Fig. 1 shows the drive channel, simplified, and with the important path highlighted.

Fig. 1: Drive Circuit; Current-Measurement Path Highlighted

When the drive circuit is DC-biased with no sensor connected, we may assume no current flows across R7. Thus U4 sees a 0V input, and generates a 1.25v output (this chip's output is relative to 1.25v, as supplied on pin 5. You can ignore this fact, since firmware hides the conversion.)

The CurrSense output from this circuit goes directly into the current-sense ADC. Thus, any non-zero DC measurement indicates an error at the ADC, which may be directly canceled.

We calibrate this away with the following script:

'''
   This calibration script adjusts the sensor ADC offsets, so that they
   really measure 0 when they say they do.

   To run this calibration script, remove both cables from the cryo
   board's front panel. This is meant to run while the drive and sense
   circuits are OPEN.

   Follow this script with the "sensor_dc_cal_closed.py" script, which
   manages DAC offsets and requires this one to run before it.
'''

import crython

cb = crython.CryoBoard('192.168.0.189')

channels = range(cb.NUM_SENSOR_CHANNELS)

###
### Set channels to DC, 0 DAC counts
###

# This is the boot-up default, and accounts for the ADC's zero reference.
default_trim=0xe00000

for c in channels:
   cb.set_sensor_dac_waveform(c+1, cb.DC)
   cb.set_sensor_adc_waveform(c+1, cb.DC)
   cb.set_sensor_dac_amplitude(c+1, 0, cb.DAC_COUNTS);
   cb.set_sensor_adc_voltage_trim(c+1, default_trim, cb.RAW)
   cb.set_sensor_adc_current_trim(c+1, default_trim, cb.RAW)

###
### With no dongle connected, calibrate 0 current and voltage
###

s = cb.get_samples(25)

i_averages = [ average(s['ii'][c]) for c in channels ]
for c in channels:
   # SNEAKY TRICK: the voltage readout is floating, so we can't
   # trust that it's actually measuring 0. We reuse the current
   # measurement, since it's always on the same ADC -- it's pretty
   # accurate.
   cb.set_sensor_adc_voltage_trim(c+1, default_trim-i_averages[c])
   cb.set_sensor_adc_current_trim(c+1, default_trim-i_averages[c])

# With an open circuit, this script should produce parser results like:
#
#   $ ./parser -t 8888
#   00 vi:     1623   vq:        0   vrms:     1623   ii:       -8   iq:        0   irms:        8
#   01 vi:     1544   vq:        0   vrms:     1544   ii:        3   iq:        0   irms:        3
#   02 vi:     1367   vq:        0   vrms:     1367   ii:      -16   iq:        0   irms:       16
#   03 vi:     1512   vq:        0   vrms:     1512   ii:       -6   iq:        0   irms:        6
#   04 vi:     1290   vq:        0   vrms:     1290   ii:       19   iq:        0   irms:       19
#   05 vi:     1188   vq:        0   vrms:     1188   ii:       16   iq:        0   irms:       16
#   06 vi:     1478   vq:        0   vrms:     1478   ii:       14   iq:        0   irms:       14
#   07 vi:     1255   vq:        0   vrms:     1255   ii:       18   iq:        0   irms:       18
#   08 vi:     1636   vq:        0   vrms:     1636   ii:        9   iq:        0   irms:        9
#   09 vi:     1344   vq:        0   vrms:     1344   ii:        1   iq:        0   irms:        1
#   10 vi:     1236   vq:        0   vrms:     1236   ii:        6   iq:        0   irms:        6
#   11 vi:     5111   vq:        0   vrms:     5111   ii:        0   iq:        0   irms:        0
#   12 vi:     5517   vq:        0   vrms:     5517   ii:        1   iq:        0   irms:        1
#   13 vi:     4463   vq:        0   vrms:     4463   ii:        1   iq:        0   irms:        1
#   14 vi:    -2098   vq:        0   vrms:     2098   ii:      -15   iq:        0   irms:       15
#   15 vi:     3581   vq:        0   vrms:     3581   ii:       -1   iq:        0   irms:        1
#   IRIG_TEST: s 17987 sub 67454472
#
# (i.e. some non-zero but small-ish amount on the voltage readouts, but a
# *very* small number on the current readouts.)

Step 2: Calibrate the DACs

We now close the feedback loop by installing loads across the drive circuit. It is actually not important for this calibration step what the loads are, so long as the feedback loop is operating correctly.

Fig. 2: Drive Circuit; DAC Offset Path Highlighted

As long as the feedback loop is closed, the op-amp U5 generates a virtual ground across pins 8 and 2. Thus, any incorrect DAC offset will be directly registered by the CurrSense ADC. All we need to do is measure that signal, and apply its inverse (converted back to DAC counts) to properly null away the incorrectly applied current.

The following script does exactly that:

'''
   This calibration script adjusts the sensor DAC offsets, so that they
   really generate 0 when they say they do.

   To use this script,

      * to first run "sensor_dc_cal_open.py"
      * install a dongle or sensors with finite resistances

   Then, run this script. The resulting errors are placed in the DAC
   amplitudes, so the currents are approximately zeroed. They're also
   kept in "dac_errors".
'''

import sys, time, math
sys.path.append('../../..')
import crython
from pylab import *

cb = crython.CryoBoard('192.168.0.189')

channels = range(cb.NUM_SENSOR_CHANNELS)

###
### Make sure there aren't any amplitudes set between runs
###

[ cb.set_sensor_dac_amplitude(c+1, 0, cb.DAC_COUNTS) for c in channels ]
time.sleep(1)

###
### Figure out how incorrect we are, in DAC counts
###

s = cb.get_samples(25)
current_errors = [ average(s['ii'][c]) for c in channels ]
dac_errors = [ int(i / 64) for i in current_errors ]

def set_sensor_dac_amplitude(self, c, amp, units):
   amp = self.convert_sensor_dac_amplitude(c, amp, units, cb.DAC_COUNTS)
   amp -= dac_errors[c-1]
   self.set_sensor_dac_amplitude(c, amp, cb.DAC_COUNTS)

# Force the board to "real" 0 current.
for c in channels:
   set_sensor_dac_amplitude(cb, c+1, 0, cb.DAC_COUNTS)

# With a dongle installed, this script should produce parser results like:
#
#   $ ./parser -t 8888
#   00 vi:      -23   vq:        0   vrms:       23   ii:       72   iq:        0   irms:       72
#   01 vi:     -174   vq:        0   vrms:      174   ii:     -333   iq:        0   irms:      333
#   02 vi:     -171   vq:        0   vrms:      171   ii:     -150   iq:        0   irms:      150
#   03 vi:      148   vq:        0   vrms:      148   ii:     -139   iq:        0   irms:      139
#   04 vi:    -1320   vq:        0   vrms:     1320   ii:      -97   iq:        0   irms:       97
#   05 vi:    -1500   vq:        0   vrms:     1500   ii:      -28   iq:        0   irms:       28
#   06 vi:    -1385   vq:        0   vrms:     1385   ii:     -114   iq:        0   irms:      114
#   07 vi:    -1571   vq:        0   vrms:     1571   ii:     -195   iq:        0   irms:      195
#   08 vi:      305   vq:        0   vrms:      305   ii:     -433   iq:        0   irms:      433
#   09 vi:      311   vq:        0   vrms:      311   ii:     -135   iq:        0   irms:      135
#   10 vi:       22   vq:        0   vrms:       22   ii:      -62   iq:        0   irms:       62
#   11 vi:     -196   vq:        0   vrms:      196   ii:      -79   iq:        0   irms:       79
#   12 vi:      518   vq:        0   vrms:      518   ii:       -8   iq:        0   irms:        8
#   13 vi:      168   vq:        0   vrms:      168   ii:     -193   iq:        0   irms:      193
#   14 vi:     -113   vq:        0   vrms:      113   ii:      -51   iq:        0   irms:       51
#   15 vi:     -208   vq:        0   vrms:      208   ii:      -90   iq:        0   irms:       90
#   IRIG_TEST: s 25091 sub 78609588
#
# (i.e. the adjusted amplitudes on the voltage readouts, and *very* small
# numbers on the current readouts.)

What Now?

You now have a correctly nulled board. However, while the ADC offsets are hidden from view, the DAC offsets aren't part of the firmware yet. You'll have to adjust these yourself when setting DAC amplitudes.

Note that the webpage readout is actually correct, since it uses current and voltage measurements, not settings, when determining resistances and temperatures.

Topic attachments
I Attachment Action Size Date Who Comment
PNGpng drive_currpath.png manage 31.1 K 2011-11-03 - 23:23 GraemeSmecher  
PNGpng drive_dacpath.png manage 31.3 K 2011-11-04 - 00:11 GraemeSmecher  
PNGpng drive_plain.png manage 30.7 K 2011-11-03 - 23:21 GraemeSmecher  
Cascading Style Sheet filecss prettify.css manage 0.7 K 2011-11-03 - 23:35 GraemeSmecher  
JavaScriptjs prettify.js manage 13.3 K 2011-11-03 - 23:35 GraemeSmecher  

This topic: CryoElectronics > WebHome > SensorChannelCalibration Topic revision: r2 - 2011-11-09 - GraemeSmecher
© 2020 Winterland Cosmology Lab, McGill University, Montréal, Québec, Canada