---+ Sense Channel Calibration
There are two dominant types of inaccuracy at DC:
- Errors in (voltage and current) measurements, and
- 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.
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.
This topic: CryoElectronics
> WebHome > SensorChannelCalibration
Topic revision: r2 - 2011-11-09 - GraemeSmecher