Skip to content
Snippets Groups Projects
Commit 414dd796 authored by Marko Mecina's avatar Marko Mecina
Browse files

add module providing calibration functions for SMILE

parent 81ca28e2
No related branches found
No related tags found
No related merge requests found
"""
Calibration functions and utilities for raw/engineering conversions in SMILE
Data from SMILE-IWF-PL-UM-147-d0-3_SXI_EBox_User_Manual (ID 5233)
"""
import os
import numpy as np
import scipy as sp
import matplotlib.pyplot as plt
# constants
T_ZERO = 273.15
# common ADC coefficients
ADC_INPRNG = 7.34783 # V
ADC_OFFSET = -1.69565 # V
class Dpu:
ADC_P3V9 = "HK_ADC_P3V9"
ADC_P3V3 = "HK_ADC_P3V3"
ADC_P3V3_LVDS = "HK_ADC_P3V3_LVDS"
ADC_P2V5 = "HK_ADC_P2V5"
ADC_P1V8 = "HK_ADC_P1V8"
ADC_P1V2 = "HK_ADC_P1V2"
ADC_REF = "HK_ADC_REF"
K_DPU = {
Dpu.ADC_P3V9: 2,
Dpu.ADC_P3V3: 1,
Dpu.ADC_P3V3_LVDS: 1,
Dpu.ADC_P2V5: 1,
Dpu.ADC_P1V8: 1,
Dpu.ADC_P1V2: 1,
Dpu.ADC_REF: 1
}
class Temp:
ADC_TEMP1 = "HK_ADC_TEMP1"
ADC_TEMP_FEE = "HK_ADC_TEMP_FEE"
ADC_TEMP_CCD = "HK_ADC_TEMP_CCD"
ADC_PSU_TEMP = "HK_ADC_PSU_TEMP"
# Signal specific coefficients
class V_T0:
CCD = 2.5650
TEMP1 = 2.5770
FEE = 1.2800
class K_T:
CCD = 0.00385
TEMP1 = 0.00385
FEE = 0.00385
# interpolation table for nominal operation CCD temperature
# (degC, ADC_V, ADU_dec, ADU_hex)
CCD_TEMP_TABLE = [
(-140.0, 1.125, 6288, 0x1890),
(-135.0, 1.178, 6407, 0x1906),
(-130.0, 1.231, 6524, 0x197C),
(-125.0, 1.283, 6642, 0x19F1),
(-120.0, 1.336, 6759, 0x1A66),
(-115.0, 1.388, 6876, 0x1ADB),
(-110.0, 1.440, 6992, 0x1B50),
(-105.0, 1.493, 7109, 0x1BC4),
(-100.0, 1.545, 7225, 0x1C38),
(-95.0, 1.596, 7340, 0x1CAC),
(-90.0, 1.648, 7456, 0x1D1F),
(-85.0, 1.700, 7571, 0x1D92),
(-80.0, 1.751, 7686, 0x1E05),
(-75.0, 1.803, 7800, 0x1E78),
(-70.0, 1.854, 7915, 0x1EEA),
(-65.0, 1.905, 8029, 0x1F5D),
(-60.0, 1.957, 8143, 0x1FCF),
(-55.0, 2.008, 8257, 0x2040),
(-50.0, 2.059, 8371, 0x20B2),
(-45.0, 2.109, 8484, 0x2123),
(-40.0, 2.160, 8597, 0x2195),
(-35.0, 2.211, 8710, 0x2206),
(-30.0, 2.261, 8823, 0x2276),
(-25.0, 2.312, 8936, 0x22E7),
(-20.0, 2.362, 9048, 0x2358)
]
# interpolation table for PSU temperature
# (degC, ADC_V, ADU_dec, ADU_hex)
PSU_TEMP = [
(-50.0, 3.237, 10998, 0x2AF6),
(-40.0, 3.187, 10887, 0x2A86),
(-20.0, 2.960, 10380, 0x288C),
(0.0, 2.487, 9326, 0x246D),
(20.0, 1.816, 7830, 0x1E95),
(25.0, 1.643, 7444, 0x1D13),
(40.0, 1.169, 6387, 0x18F3),
(60.0, 0.703, 5348, 0x14E4),
(80.0, 0.417, 4710, 0x1266),
(90.0, 0.323, 4501, 0x1194),
(100.0, 0.252, 4343, 0x10F6)
]
class Psu:
ADC_I_FEE_ANA = "HK_ADC_I_FEE_ANA"
ADC_I_FEE_DIG = "HK_ADC_I_FEE_DIG"
ADC_I_DPU = "HK_ADC_I_DPU"
ADC_I_RSE = "HK_ADC_I_RSE"
ADC_I_HEATER = "HK_ADC_I_HEATER"
K_PSU = {
Psu.ADC_I_FEE_ANA: 0.3058,
Psu.ADC_I_FEE_DIG: 0.1528,
Psu.ADC_I_DPU: 0.4913,
Psu.ADC_I_RSE: 0.844,
Psu.ADC_I_HEATER: 0.4349
}
PSU_OFFSET = {
Psu.ADC_I_FEE_ANA: 0,
Psu.ADC_I_FEE_DIG: 0,
Psu.ADC_I_DPU: 0,
Psu.ADC_I_RSE: 0,
Psu.ADC_I_HEATER: -0.3701
}
class Rse:
RSE_MOTOR_TEMP = "HK_RSE_MOTOR_TEMP"
RSE_ELEC_TEMP = "HK_RSE_ELEC_TEMP"
# fit polynomial of degree POLY_DEG through CCD ADU-degC relation (operational range)
_ccd_temp_adu_array = np.array(CCD_TEMP_TABLE).T # (degC, ADC_V, ADU_dec, ADU_hex)
POLY_DEG = 4
_ccd_temp_fit_adu = np.polynomial.polynomial.Polynomial.fit(_ccd_temp_adu_array[2], _ccd_temp_adu_array[0],
POLY_DEG).convert()
_ccd_temp_fit_adu_inv = np.polynomial.polynomial.Polynomial.fit(_ccd_temp_adu_array[0], _ccd_temp_adu_array[2],
POLY_DEG).convert()
# cubic-spline interpolation of PSU ADU-degC relation (nominal values)
_psu_temp_adu_array = np.array(PSU_TEMP).T # (degC, ADC_V, ADU_dec, ADU_hex)
_psu_temp_interp = sp.interpolate.interp1d(_psu_temp_adu_array[2], _psu_temp_adu_array[0],
kind='cubic', fill_value='extrapolate')
_psu_temp_interp_inv = sp.interpolate.interp1d(_psu_temp_adu_array[0], _psu_temp_adu_array[2], kind='cubic',
fill_value='extrapolate')
def t_ccd_adu_to_deg_oper(adu, warn=True):
if not ((_ccd_temp_adu_array[2].min() <= adu) & (adu <= _ccd_temp_adu_array[2].max())).all() and warn:
print('WARNING! Value(s) outside operational range ({:.0f}-{:.0f})!'.format(_ccd_temp_adu_array[2].min(),
_ccd_temp_adu_array[2].max()))
return _ccd_temp_fit_adu(adu)
def t_ccd_deg_to_adu_oper(t, warn=True):
if not ((_ccd_temp_adu_array[0].min() <= t) & (t <= _ccd_temp_adu_array[0].max())).all() and warn:
print('WARNING! Value(s) outside operational range ({} - {})!'.format(_ccd_temp_adu_array[0].min(),
_ccd_temp_adu_array[0].max()))
return round(_ccd_temp_fit_adu_inv(t))
def t_ccd_adu_to_deg_nonoper(adu):
return (adu * ADC_INPRNG / (2 ** 14 - 1) + ADC_OFFSET - V_T0.CCD) / (V_T0.CCD * K_T.CCD)
def t_ccd_deg_to_adu_nonoper(t):
return round(((t * V_T0.CCD * K_T.CCD - ADC_OFFSET + V_T0.CCD) * (2 ** 14 - 1)) / ADC_INPRNG)
def t_ccd_adu_to_deg(adu):
return np.where(adu <= _ccd_temp_adu_array[2].max(), t_ccd_adu_to_deg_oper(adu, warn=False), t_ccd_adu_to_deg_nonoper(adu))
def t_ccd_deg_to_adu(t):
return np.where(t <= _ccd_temp_adu_array[0].max(), t_ccd_deg_to_adu_oper(t, warn=False), t_ccd_deg_to_adu_nonoper(t))
def t_ccd_fee_adu_to_deg(adu):
"""
For CCD temperature reported in FEE HK
:param adu:
:return:
"""
return adu / 65535 * 4.096 * 338.581 - T_ZERO
def t_ccd_fee_deg_to_adu(t):
"""
For CCD temperature reported in FEE HK
:param t:
:return:
"""
return round((t + T_ZERO) / (4.096 * 338.581) * 65535)
def t_temp1_adu_to_deg(adu):
return (adu * ADC_INPRNG / (2 ** 14 - 1) + ADC_OFFSET - V_T0.TEMP1) / (V_T0.TEMP1 * K_T.TEMP1)
def t_temp1_deg_to_adu(t):
return round(((t * V_T0.TEMP1 * K_T.TEMP1 - ADC_OFFSET + V_T0.TEMP1) * (2 ** 14 - 1)) / ADC_INPRNG)
def t_fee_adu_to_deg(adu):
return (adu * ADC_INPRNG / (2 ** 14 - 1) + ADC_OFFSET - V_T0.FEE) / (V_T0.FEE * K_T.FEE)
def t_fee_deg_to_adu(t):
return round(((t * V_T0.FEE * K_T.FEE - ADC_OFFSET + V_T0.FEE) * (2 ** 14 - 1)) / ADC_INPRNG)
def t_rse_adu_to_deg(adu):
return (3.908 - np.sqrt(17.59246 - (76.56 / (4096 / adu - 1)))) / 0.00116
def t_rse_deg_to_adu(t):
return round(4096 / (76.56 / (17.59246 - (3.908 - 0.00116 * t) ** 2) + 1))
def t_psu_adu_to_deg(adu):
return _psu_temp_interp(adu)
def t_psu_deg_to_adu(t):
return _psu_temp_interp_inv(t)
def t_adu_to_deg(adu, signal):
if signal == Temp.ADC_TEMP_CCD:
t = t_ccd_adu_to_deg(adu)
elif signal == Temp.ADC_TEMP1:
t = t_temp1_adu_to_deg(adu)
elif signal == Temp.ADC_TEMP_FEE:
t = t_fee_adu_to_deg(adu)
elif signal == Temp.ADC_PSU_TEMP:
t = t_psu_adu_to_deg(adu)
elif signal in (Rse.RSE_MOTOR_TEMP, Rse.RSE_ELEC_TEMP):
t = t_rse_adu_to_deg(adu)
else:
raise ValueError("Unknown signal '{}'".format(signal))
return t
def t_deg_to_adu(t, signal):
if signal == Temp.ADC_TEMP_CCD:
adu = t_ccd_deg_to_adu(t)
elif signal == Temp.ADC_TEMP1:
adu = t_temp1_deg_to_adu(t)
elif signal == Temp.ADC_TEMP_FEE:
adu = t_fee_deg_to_adu(t)
elif signal == Temp.ADC_PSU_TEMP:
adu = t_psu_deg_to_adu(t)
elif signal in (Rse.RSE_MOTOR_TEMP, Rse.RSE_ELEC_TEMP):
adu = t_rse_deg_to_adu(t)
else:
raise ValueError("Unknown signal '{}'".format(signal))
return adu
def u_dpu_adu_to_volt(adu, signal):
return ((adu * ADC_INPRNG) / (2 ** 14 - 1) + ADC_OFFSET) * K_DPU[signal]
def u_dpu_volt_to_adu(u, signal):
return round(((u / K_DPU[signal] - ADC_OFFSET) * (2 ** 14 - 1)) / ADC_INPRNG)
def i_psu_adu_to_amp(adu, signal):
return ((adu * ADC_INPRNG) / (2 ** 14 - 1) + ADC_OFFSET) * K_PSU[signal] + PSU_OFFSET[signal]
def i_psu_amp_to_adu(i, signal):
return round((((i - PSU_OFFSET[signal]) / K_PSU[signal] - ADC_OFFSET) * (2 ** 14 - 1)) / ADC_INPRNG)
def calibrate(adu, signal):
if signal in Dpu.__dict__.values():
x = u_dpu_adu_to_volt(adu, signal)
elif signal in Temp.__dict__.values() or signal in Rse.__dict__.values():
x = t_adu_to_deg(adu, signal)
elif signal in Psu.__dict__.values():
x = i_psu_adu_to_amp(adu, signal)
else:
raise ValueError("Unknown signal '{}'".format(signal))
return x
def decalibrate(x, signal):
if signal in Dpu.__dict__.values():
adu = u_dpu_volt_to_adu(x, signal)
elif signal in Temp.__dict__.values() or signal in Rse.__dict__.values():
adu = t_deg_to_adu(x, signal)
elif signal in Psu.__dict__.values():
adu = i_psu_amp_to_adu(x, signal)
else:
raise ValueError("Unknown signal '{}'".format(signal))
return adu
class CalibrationTables:
# default ADC limits
BOUND_L = 0
BOUND_U = 0x3FFE
BOUND_RSE = 0xCD
def __init__(self):
# temperatures
x = np.linspace(self.BOUND_L, self.BOUND_U, 60, dtype=int)
self.temperature = {}
for sig in vars(Temp):
if sig.startswith('ADC'):
label = getattr(Temp, sig)
self.temperature[label] = np.array([x, t_adu_to_deg(x, label)])
x = np.linspace(self.BOUND_L, self.BOUND_RSE, 60, dtype=int)
for sig in vars(Rse):
if sig.startswith('RSE'):
label = getattr(Rse, sig)
self.temperature[label] = np.array([x, t_adu_to_deg(x, label)])
x = np.linspace(self.BOUND_L, self.BOUND_U, 2, dtype=int) # two points suffice for linear voltage and current calibrations
# voltages
self.voltage = {}
for sig in vars(Dpu):
if sig.startswith('ADC'):
label = getattr(Dpu, sig)
self.voltage[label] = np.array([x, u_dpu_adu_to_volt(x, label)])
# currents
self.current = {}
for sig in vars(Psu):
if sig.startswith('ADC'):
label = getattr(Psu, sig)
self.current[label] = np.array([x, i_psu_adu_to_amp(x, label)])
def write_to_files(self, path):
for k in self.temperature:
np.savetxt(os.path.join(path, k + '.dat'), self.temperature[k].T, header=k, fmt=('%5d', '%6.1f'))
for k in self.voltage:
np.savetxt(os.path.join(path, k + '.dat'), self.voltage[k].T, header=k, fmt=('%5d', '%6.3f'))
for k in self.current:
np.savetxt(os.path.join(path, k + '.dat'), self.current[k].T, header=k, fmt=('%5d', '%6.3f'))
print("Calibration tables written to {}".format(path))
def plot(self, signal, xmin=BOUND_L, xmax=BOUND_U):
sig = signal[3:]
if sig in vars(Dpu):
xy = self.voltage[signal]
ylabel = 'Voltage [V]'
elif sig in vars(Temp):
xy = self.temperature[signal]
ylabel = 'Temperature [°C]'
elif sig in vars(Psu):
xy = self.current[signal]
ylabel = 'Current [A]'
else:
raise ValueError("Unknown signal '{}'".format(sig))
xref = np.linspace(xmin, xmax, 1000)
yref = calibrate(xref, signal)
limits = np.array((np.array(getattr(Limits, sig)), calibrate(np.array(getattr(Limits, sig)), signal))).T
print(limits)
fl, wl, wu, fu = limits
plt.axvspan(xmin, fl[0], alpha=0.25, color='red')
plt.axvspan(fl[0], wl[0], alpha=0.5, color='orange')
plt.axvspan(wu[0], fu[0], alpha=0.5, color='orange')
plt.axvspan(fu[0], xmax, alpha=0.25, color='red')
for i in limits:
plt.axhline(i[1], ls=':', color='grey')
plt.plot(xref, yref, color='grey', lw=0.5)
plt.plot(*xy, 'k.', label=signal, ms=4)
# plt.legend()
plt.xlabel('ADU')
plt.ylabel(ylabel)
plt.title(signal)
plt.grid(True)
plt.show()
class Limits:
# raw operational limits (FAIL_L, WARN_L, WARN_U, FAIL_U)
ADC_P3V9 = (0x1D8D, 0x1E67, 0x2119, 0x21F3)
ADC_P3V3 = (0x27A2, 0x2912, 0x2DF2, 0x2F62)
ADC_P3V3_LVDS = (0x27A2, 0x2912, 0x2DF2, 0x2F62)
ADC_P2V5 = (0x215D, 0x2274, 0x26A1, 0x27B8)
ADC_P1V8 = (0x1BE0, 0x1CA9, 0x203A, 0x2103)
ADC_P1V2 = (0x172C, 0x17B2, 0x1ABE, 0x1B43)
ADC_REF = (0x215D, 0x2274, 0x26A1, 0x27B8)
ADC_TEMP1 = (0x210F, 0x2259, 0x2B37, 0x2C12)
ADC_TEMP_FEE = (0x17EA, 0x188E, 0x1CFD, 0x1D6B)
ADC_TEMP_CCD = (0x1968, 0x19DD, 0x1D20, 0x1D93)
ADC_I_FEE_ANA = (0xDC4, 0xDC4, 0x1D70, 0x1ECE)
ADC_I_FEE_DIG = (0xDC4, 0xDC4, 0x20FB, 0x22B4)
ADC_I_DPU = (0xDC4, 0xDC4, 0x20B7, 0x2269)
ADC_I_RSE = (0xDC4, 0xDC4, 0x1EA8, 0x2025)
ADC_I_HEATER = (0x152E, 0x152E, 0x23B2, 0x24F2)
ADC_PSU_TEMP = (0x12A5, 0x13E4, 0x298C, 0x2B08)
# raw upper RSE limits
RSE_MOTOR_TEMP = 0x96
RSE_ELEC_TEMP = 0x96
# raw ambient CCD limits
ADC_TEMP_CCD_AMB = (0x1968, 0x19DD, 0x29DB, 0x2A49)
class LimitTables:
def __init__(self):
# temperatures
self.temperature = {}
for sig in vars(Temp):
if sig.startswith('ADC'):
label = getattr(Temp, sig)
adu_limits = np.array(getattr(Limits, sig))
self.temperature[label] = np.array([adu_limits, t_adu_to_deg(adu_limits, label)])
for sig in vars(Rse):
if sig.startswith('RSE'):
label = getattr(Rse, sig)
adu_limits = np.array(getattr(Limits, sig))
self.temperature[label] = np.array([adu_limits, t_adu_to_deg(adu_limits, label)])
# voltages
self.voltage = {}
for sig in vars(Dpu):
if sig.startswith('ADC'):
label = getattr(Dpu, sig)
adu_limits = np.array(getattr(Limits, sig))
self.voltage[label] = np.array([adu_limits, u_dpu_adu_to_volt(adu_limits, label)])
# currents
self.current = {}
for sig in vars(Psu):
if sig.startswith('ADC'):
label = getattr(Psu, sig)
adu_limits = np.array(getattr(Limits, sig))
self.current[label] = np.array([adu_limits, i_psu_adu_to_amp(adu_limits, label)])
if __name__ == '__main__':
ct = CalibrationTables()
# ct.plot(Temp.ADC_PSU_TEMP)
lmt = LimitTables()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment