diff --git a/configs/current_adcs.yaml b/configs/current_adcs.yaml new file mode 100644 index 0000000000000000000000000000000000000000..e773384db056193193f50fad8d32640ecaf11395 --- /dev/null +++ b/configs/current_adcs.yaml @@ -0,0 +1,3 @@ +lpGBT: + - 0 + - 7 diff --git a/tamalero/LPGBT.py b/tamalero/LPGBT.py index 0fab62a4a5f963cf0403cc496de3c3f0156d42c0..ff8f4f3d718f6721bcd76f331e4ebf619de70d78 100644 --- a/tamalero/LPGBT.py +++ b/tamalero/LPGBT.py @@ -127,6 +127,10 @@ class LPGBT(RegParser): self.cal_gain = 1.85 self.cal_offset = 512 + self.current_adcs = load_yaml(os.path.expandvars('$TAMALERO_BASE/configs/current_adcs.yaml'))['lpGBT'] + for adc in self.current_adcs: + self.set_current_adc(adc) + def read_base_config(self): # print("{:80}{:10}{:10}".format("Register", "value", "default")) @@ -546,7 +550,25 @@ class LPGBT(RegParser): else: print(tabulate(table, headers=["Register","Pin", "Reading", "Voltage", "Comment"], tablefmt="simple_outline")) - def read_adc(self, channel, convert=False): + def set_current_dac(self, units): + self.wr_reg("LPGBT.RWF.CUR_DAC.CURDACSELECT", units) + + def get_current_dac(self): + return self.rd_reg("LPGBT.RWF.CUR_DAC.CURDACSELECT") + + def set_current_dac_uA(self, uA): + # CURDACSELECT is in units of 900/256 uA per bit, with max of 255 + conv = 256.0/900 + val = min(round(uA*conv), 255) + self.set_current_dac(val) + return val/conv + + def get_current_dac_uA(self): + # CURDACSELECT is in units of 900/256 uA per bit, with max of 255 + return self.rd_reg("LPGBT.RWF.CUR_DAC.CURDACSELECT") * 900/256.0 + + + def read_adc(self, channel, calibrate=False, convert=False): # ADCInPSelect[3:0] | Input # ------------------ |---------------------------------------- # 4'd0 | ADC0 (external pin) @@ -583,13 +605,19 @@ class LPGBT(RegParser): self.wr_reg("LPGBT.RW.ADC.ADCCONVERT", 0x0) self.wr_reg("LPGBT.RW.ADC.ADCENABLE", 0x1) + if calibrate: + val = val*self.cal_gain/1.85 + (512 - self.cal_offset) # calibrate + if convert: + conversion = None for k in self.adc_mapping.keys(): if int(self.adc_mapping[k]['pin']) == channel: conversion = self.adc_mapping[k]['conv'] break - val = val*self.cal_gain/1.85 + (512 - self.cal_offset) # calibrate - val = val / (2**10 - 1) * conversion # convert + if conversion is not None: + val = val * conversion / (2**10 - 1) + else: + raise Exception(f"ADC conversion not found when reading ADC {channel}") return val def calibrate_adc(self, recalibrate=False): @@ -626,7 +654,49 @@ class LPGBT(RegParser): self.cal_gain = gain self.cal_offset = offset + + def set_current_adc(self, channel, verbose=False): + assert channel in range(8), f"Can only choose from ADC0 to ADC7; ADC{channel} was given instead" + + self.wr_reg("LPGBT.RWF.VOLTAGE_DAC.CURDACENABLE", 0x1) + if verbose: + print("Set current DAC...", self.rd_reg("LPGBT.RWF.VOLTAGE_DAC.CURDACENABLE")) + if channel == 0: + adc_chn = self.LPGBT_CONST.CURDAC_CHN0_bm + elif channel == 1: + adc_chn = self.LPGBT_CONST.CURDAC_CHN1_bm + elif channel == 2: + adc_chn = self.LPGBT_CONST.CURDAC_CHN2_bm + elif channel == 3: + adc_chn = self.LPGBT_CONST.CURDAC_CHN3_bm + elif channel == 4: + adc_chn = self.LPGBT_CONST.CURDAC_CHN4_bm + elif channel == 5: + adc_chn = self.LPGBT_CONST.CURDAC_CHN5_bm + elif channel == 6: + adc_chn = self.LPGBT_CONST.CURDAC_CHN6_bm + elif channel == 7: + adc_chn = self.LPGBT_CONST.CURDAC_CHN7_bm + else: + raise Exception("Invalid lpGBT ADC channel selected") + + currently_set = self.rd_reg("LPGBT.RWF.CUR_DAC.CURDACCHNENABLE") + + if verbose: + print(f"LPGBT.RWF.CUR_DAC.CURDACCHNENABLE currently set: {bin(currently_set)}") + print(f"Want to set {bin(adc_chn)}") + print(f"LPGBT.RWF.CUR_DAC.CURDACCHNENABLE new set: {bin(adc_chn | currently_set)}") + + self.wr_reg("LPGBT.RWF.CUR_DAC.CURDACCHNENABLE", adc_chn | currently_set) # Set pin ADC channel to current source + if verbose: + print(f"Set current source to pin ADC{channel}...", bin(self.rd_reg("LPGBT.RWF.CUR_DAC.CURDACCHNENABLE"))) + + current = 100 # Desired current of 100 uA + self.set_current_dac_uA(current) + if verbose: + print(f"Set current source value to {current} uA") + def set_dac(self, v_out): if v_out > 1.00: print ("Can't set the DAC to a value larger than 1.0 V!") @@ -653,6 +723,8 @@ class LPGBT(RegParser): elif self.ver == 1: lo_bits = self.rd_reg("LPGBT.RWF.VOLTAGE_DAC.VOLDACVALUE_0TO7") hi_bits = self.rd_reg("LPGBT.RWF.VOLTAGE_DAC.VOLDACVALUE_8TO11") + else: + raise Exception("Invalid lpgbt version detected.") value = lo_bits | (hi_bits << 8) return value/4096*v_ref @@ -918,13 +990,21 @@ class LPGBT(RegParser): print ("Write not successfull!") break - def I2C_read(self, reg=0x0, master=2, slave_addr=0x70, nbytes=1, adr_nbytes=2, freq=2, verbose=False): + def I2C_read(self, reg=0x0, master=2, slave_addr=0x70, nbytes=1, adr_nbytes=2, freq=2, verbose=True): #https://gitlab.cern.ch/lpgbt/pigbt/-/blob/master/backend/apiapp/lpgbtLib/lowLevelDrivers/MASTERI2C.py#L83 + + # debugging + #print("### LPGBT.I2C_read ###") + #print(f"reg: {reg}, \tmaster: {master}, \tslave_addr: {slave_addr}, \tnbytes: {nbytes}, \tadr_nbytes: {adr_nbytes}, \tfreq: {freq}, \tver: {self.ver}") + i2cm = master i2cm1cmd = self.get_node('LPGBT.RW.I2C.I2CM1CMD').real_address i2cm0cmd = self.get_node('LPGBT.RW.I2C.I2CM0CMD').real_address + # debugging + #print(f"i2cm1cmd: {i2cm1cmd}, \ti2cm0cmd: {i2cm0cmd}") + if self.ver == 0: i2cm1status = self.LPGBT_CONST.I2CM1STATUS i2cm0status = self.LPGBT_CONST.I2CM0STATUS @@ -939,28 +1019,49 @@ class LPGBT(RegParser): OFFSET_WR = i2cm*(i2cm1cmd - i2cm0cmd) #using the offset trick to switch between masters easily OFFSET_RD = i2cm*(i2cm1status - i2cm0status) + # debugging + #print(f"i2cm1status: {i2cm1status}, \ti2cm0status: {i2cm0status}, \ti2cm0data0: {i2cm0data0}, \ti2cm0cmd: {i2cm0cmd}, \ti2cm0address: {i2cm0address}, \tOFFSET_WR: {OFFSET_WR}, \tOFFSET_RD: {OFFSET_RD}") + ################################################################################ # Write the register address ################################################################################ # https://lpgbt.web.cern.ch/lpgbt/v0/i2cMasters.html#i2c-write-cr-0x0 self.wr_adr(i2cm0data0+OFFSET_WR, adr_nbytes<<self.LPGBT_CONST.I2CM_CR_NBYTES_of | (freq<<self.LPGBT_CONST.I2CM_CR_FREQ_of)) + # debugging + #print(f"Address: {i2cm0data0+OFFSET_WR}, \tValue: {adr_nbytes<<self.LPGBT_CONST.I2CM_CR_NBYTES_of | (freq<<self.LPGBT_CONST.I2CM_CR_FREQ_of)}") self.wr_adr(i2cm0cmd+OFFSET_WR, self.LPGBT_CONST.I2CM_WRITE_CRA) #write to config register + # debugging + #print(f"Address: {i2cm0cmd+OFFSET_WR}, \tValue: {self.LPGBT_CONST.I2CM_WRITE_CRA}") # https://lpgbt.web.cern.ch/lpgbt/v0/i2cMasters.html#i2c-w-multi-4byte0-0x8 for i in range (adr_nbytes): self.wr_adr(self.get_node("LPGBT.RW.I2C.I2CM0DATA%d"%i).real_address + OFFSET_WR, (reg >> (8*i)) & 0xff ) + # debugging + #print(f"Address: {self.get_node('LPGBT.RW.I2C.I2CM0DATA%d'%i).real_address + OFFSET_WR}, \tValue: {(reg >> (8*i)) & 0xff}, \ti: {i}") # self.wr_adr(self.LPGBT_CONST.I2CM0DATA1 + OFFSET_WR , regh) self.wr_adr(i2cm0cmd+OFFSET_WR, self.LPGBT_CONST.I2CM_W_MULTI_4BYTE0) # prepare a multi-write + # debugging + #print(f"Address: {i2cm0cmd+OFFSET_WR}, \tValue: {self.LPGBT_CONST.I2CM_W_MULTI_4BYTE0}") # https://lpgbt.web.cern.ch/lpgbt/v0/i2cMasters.html#i2c-write-multi-0xc self.wr_adr(i2cm0address+OFFSET_WR, slave_addr) + # debugging + #print(f"Address: {i2cm0address+OFFSET_WR}, \tValue: {slave_addr}") self.wr_adr(i2cm0cmd+OFFSET_WR, self.LPGBT_CONST.I2CM_WRITE_MULTI)# execute multi-write + # debugging + #print(f"Address: {i2cm0cmd+OFFSET_WR}, \tValue: {self.LPGBT_CONST.I2CM_WRITE_MULTI}") status = self.rd_adr(i2cm0status+OFFSET_RD) + + # debugging + #print(f"status: {status}, LPGBT_CONST.I2CM_SR_SUCC_bm: {self.LPGBT_CONST.I2CM_SR_SUCC_bm}, Address: {i2cm0status+OFFSET_RD}") + retries = 0 while (status != self.LPGBT_CONST.I2CM_SR_SUCC_bm): status = self.rd_adr(i2cm0status+OFFSET_RD) + # debugging + #print(f"Updating status: {status}, retries: {retries}") retries += 1 if retries > 50: if verbose: @@ -980,12 +1081,16 @@ class LPGBT(RegParser): self.wr_adr(i2cm0cmd+OFFSET_WR, self.LPGBT_CONST.I2CM_READ_MULTI)# execute read status = self.rd_adr(i2cm0status+OFFSET_RD) + + # debugging + #print(f"status: {status}") + retries = 0 while (status != self.LPGBT_CONST.I2CM_SR_SUCC_bm): status = self.rd_adr(i2cm0status+OFFSET_RD) retries += 1 if retries > 50: - if not quiet: + if verbose: print ("Read not successfull!") return None @@ -996,8 +1101,13 @@ class LPGBT(RegParser): else: i2cm0read15 = self.get_node("LPGBT.RO.I2CREAD.I2CM0READ.I2CM0READ15").real_address + # debugging + #print(f"i2cm0read15: {i2cm0read15}") + for i in range(0, nbytes): tmp_adr = abs(i-i2cm0read15)+OFFSET_RD + # debugging + #print(f"tmp_adr: {tmp_adr}, \ttmp_adr_val: {self.rd_adr(tmp_adr).value()}") read_values.append(self.rd_adr(tmp_adr).value()) #read_value = self.rd_adr(self.LPGBT_CONST.I2CM0READ15+OFFSET_RD) # get the read value. this is just the first byte diff --git a/tamalero/ReadoutBoard.py b/tamalero/ReadoutBoard.py index cd2e37021d03ce289b08236bef57b70c1bd4b952..d1a1334b448b96de9699b5f6dd95f481d6becf15 100644 --- a/tamalero/ReadoutBoard.py +++ b/tamalero/ReadoutBoard.py @@ -1,7 +1,7 @@ import os from tamalero.LPGBT import LPGBT from tamalero.SCA import SCA -from tamalero.utils import get_temp, chunk +from tamalero.utils import get_temp, chunk, get_temp_direct from tamalero.VTRX import VTRX from time import sleep @@ -349,32 +349,73 @@ class ReadoutBoard: else: raise RuntimeError(f"{link} link does not have a stable connection after {max_retries} retries") - def read_temp(self, verbose=0): - # high level function to read all the temperature sensors - - adc_7 = self.DAQ_LPGBT.read_adc(7)/(2**10-1) - adc_in29 = self.SCA.read_adc(29)/(2**12-1) - v_ref = self.DAQ_LPGBT.read_dac() - t_SCA = self.SCA.read_temp() # internal temp from SCA + def read_vtrx_temp(self): + + # vtrx thermistors + current_vtrx = self.DAQ_LPGBT.set_current_dac_uA(600) + rt_vtrx_voltage = self.DAQ_LPGBT.read_adc(0)/(2**10-1) # FIXME: 0 should not be hardcoded + + if self.ver == 1: + return -1.0 + elif self.ver == 2: + return get_temp_direct(rt_vtrx_voltage, current_vtrx, thermistor="NCP03XM102E05RL") # this comes from the lpGBT ADC (VTRX TH) + else: + raise Exception("Unknown lpgbt version") + + def read_rb_thermistor(self, rt): + + # read voltage reference; some temperature readings depend on it + v_ref = self.DAQ_LPGBT.read_dac() + if v_ref==0 and ((rt==1 and self.ver==1) or rt==2): + raise Exception("Read temperature called with VREF configured as 0V. VREF must be configured to read the temperatures.") + + if rt==1: + + if self.ver == 1: + # This uses the DAC output for current so just read the voltage + rt1_voltage = self.DAQ_LPGBT.read_adc(7)/(2**10-1) # FIXME: 7 should not be hardcoded + return get_temp(rt1_voltage, v_ref, 10000, 25, 10000, 3900) # this comes from the lpGBT ADC + elif self.ver == 2: + # Set the DAC current then read the voltage + current_rt1 = self.DAQ_LPGBT.set_current_dac_uA(50) + rt1_voltage = self.DAQ_LPGBT.read_adc(7)/(2**10-1) # FIXME: 7 should not be hardcoded + return get_temp_direct(rt1_voltage, current_rt1, thermistor="NTCG063JF103FTB") # this comes from the lpGBT ADC + else: + raise Exception("Unknown lpgbt version") + + elif rt==2: + + rt2_voltage = self.SCA.read_adc(29)/(2**12-1) # FIXME: 29 should not be hardcoded - if v_ref>0: if self.ver == 1: # https://www.digikey.com/en/products/detail/tdk-corporation/NTCG063UH103HTBX/8565486 - t1 = get_temp(adc_7, v_ref, 10000, 25, 10000, 3900) # this comes from the lpGBT ADC - t2 = get_temp(adc_in29, v_ref, 10000, 25, 10000, 3900) # this comes from the SCA ADC + return get_temp(rt2_voltage, v_ref, 10000, 25, 10000, 3900) # this comes from the SCA ADC elif self.ver == 2: # https://www.digikey.com/en/products/detail/tdk-corporation/NTCG063JF103FTB/5872743 - # Parameters need updating? - t1 = get_temp(adc_7, v_ref, 10000, 25, 10000, 3900) # this comes from the lpGBT ADC - t2 = get_temp(adc_in29, v_ref, 10000, 25, 10000, 3380) # this comes from the SCA ADC - - if verbose>0: - print ("\nV_ref is set to: %.3f V"%v_ref) - print ("\nTemperature on RB RT1 is: %.3f C"%t1) - print ("Temperature on RB RT2 is: %.3f C"%t2) - print ("Temperature on RB SCA is: %.3f C"%t_SCA) + return get_temp(rt2_voltage, v_ref, 10000, 25, 10000, 3380) # this comes from the SCA ADC + else: + raise Exception("Unknown lpgbt version") + else: - print ("V_ref found to be 0. Exiting.") - return {'t_SCA': t_SCA} - return {'t1': t1, 't2': t2, 't_SCA': t_SCA} + raise Exception(f"Attempt to read unknown thermistor {rt=}") + + def read_temp(self, verbose=False): + + """ + read all the temperature sensors + """ + + # internal temp from SCA + t_sca = self.SCA.read_temp() + t_vtrx = self.read_vtrx_temp() + t_rt1 = self.read_rb_thermistor(1) + t_rt2 = self.read_rb_thermistor(2) + + if verbose: + print ("\nTemperature on RB RT1 is: %.1f C" % t_rt1) + print ("Temperature on RB RT2 is: %.1f C" % t_rt2) + print ("Temperature on RB SCA is: %.1f C" % t_sca) + print ("Temperature on RB VTRX is: %.1f C" % t_vtrx) + + return {'t1': t_rt1, 't2': t_rt2, 't_SCA': t_sca, 't_VTRX': t_vtrx} diff --git a/tamalero/utils.py b/tamalero/utils.py index 64a91694fabb104beaa2495121baaa26cf419776..e781d71060cd69d8ff35122c1b347a2a9f20f8f5 100644 --- a/tamalero/utils.py +++ b/tamalero/utils.py @@ -37,6 +37,69 @@ def get_temp(v_out, v_ref, r_ref, t_1, r_1, b, celcius=True): return -999 return t_2-delta_t +def get_temp_direct(v_out, curr_dac, thermistor="NTCG063JF103FTB", celcius=True, verbose=False): + """ + Calculate the temperature of a thermistor, given the voltage measured on it. + + Arguments: + v_out (float) -- voltage measured on the thermistor + curr_dac (float) -- current source value set on the DAC (in uA) + thermistor (str="NTCG063JF103FTB") -- thermistor used for temperature calculation + + Keyword arguments: + celcius (bool) -- give and return the temperature in degree celcius. Kelvin scale used otherwise. + """ + + find_temp = temp_res_fit(thermistor=thermistor) + + r_t = v_out / (curr_dac / 10**6) + t = find_temp(np.log10(r_t)) + + delta_t = 0 if celcius else 273.15 + + if verbose: + print(f"Thermistor: {thermistor}") + print(f"Voltage: {v_out} \t Current: {curr_dac} uA") + print(f"Resistance: {r_t}") + print(f"Temperature: {t}") + + return t+delta_t + +def temp_res_fit(thermistor="NTCG063JF103FTB", power=2): + + T_ref = 25 + if thermistor=="NTCG063JF103FTB": + B_list = [3194, 3270, 3382, 3422] + T_list = [-25, 0, 50, 75] + R_ref = 10e3 + elif thermistor=="NTCG063UH103HTBX": + B_list = [3770, 3822, 3900, 3926] + T_list = [-25, 0, 50, 75] + R_ref = 10e3 + elif thermistor=="NCP03XM102E05RL": + B_list = [3500, 3539, 3545, 3560] + T_list = [50, 80, 85, 100] + R_ref = 1e3 + else: + raise ValueError(f"Only thermistors NTCG063JF103FTB, NTCG063UH103HTBX or NCP03XM102E05RL are currently allowed, but {thermistor} was passed.") + + R_list = [] + + for B, T in zip(B_list, T_list): + R = R_ref * math.exp(-B * ((1/298.15) - (1/(T+273.15)))) + R_list.append(R) + + if thermistor=="NTCG063JF103FTB" or thermistor=="NTCG063UH103HTBX": + T_list.insert(2, T_ref) # Reference temperature of thermistor + R_list.insert(2, R_ref) # Reference resistance of NTC at reference temperature + elif thermistor=="NCP03XM102E05RL": + T_list = [T_ref] + T_list + R_list = [R_ref] + R_list + + poly_coeffs = np.polyfit(np.log10(R_list), T_list, power) + fit = np.poly1d(poly_coeffs) + + return fit def read_mapping(f_in, selection='adc', flavor='small'): flavors = {'small':0, 'medium':1, 'large': 2} diff --git a/test_tamalero.py b/test_tamalero.py index 0c34aefa57ae825665fa42ef901ed056f68bd6f6..85d9bc8a3b22014aa498bedc0772694b98b16d67 100644 --- a/test_tamalero.py +++ b/test_tamalero.py @@ -189,7 +189,7 @@ if __name__ == '__main__': rb_0.DAQ_LPGBT.read_adcs(check=True) # High level reading of temperatures - temp = rb_0.read_temp(verbose=1) + temp = rb_0.read_temp(verbose=True) #------------------------------------------------------------------------------- # I2C Test