diff --git a/configs/digitiser/wd1_scope_a4818.conf b/configs/digitiser/wd1_scope_a4818.conf new file mode 100644 index 0000000..28d02f6 --- /dev/null +++ b/configs/digitiser/wd1_scope_a4818.conf @@ -0,0 +1,9 @@ +[required] + +dig_name = 'V1730_A4818' +dig_gen = 1 +con_type = 'usb_A4818' +link_num = 42898 +conet_node = 0 +vme_base_address = 0 +dig_authority = 'caen.internal' diff --git a/core/controller.py b/core/controller.py index 238a63f..d821fc7 100644 --- a/core/controller.py +++ b/core/controller.py @@ -27,8 +27,8 @@ from ui import oscilloscope class Controller: - def __init__(self, - dig_config: Optional[str] = None, + def __init__(self, + dig_config: Optional[str] = None, rec_config: Optional[str] = None): ''' Initialise controller for GUI and digitiser @@ -95,16 +95,25 @@ def data_handling(self): except Exception as e: logging.exception("Error in data_handling(): ") - + # save the data (PUT IT HERE) # update visuals - self.main_window.screen.update_ch(np.arange(0, wf_size, dtype=wf_size.dtype), ADCs) - + # SCOPE firmware and DPP-DSD hold the data differently: + # SCOPE returns an array, where each channels waveform is an element in the array + # DPP-DSD returns the channel number and the array together, although I havent tested multichannel output + # We deal with this stupidly atm as we don't include multichannel support. + # THIS WILL BE FIXED COME MULTI CHANNEL! IT HAS TO BE! + if ADCs.ndim == 2: #scope format + self.main_window.screen.update_ch(np.arange(0, wf_size[0], dtype=wf_size[0].dtype), ADCs[0]) + else: + self.main_window.screen.update_ch(np.arange(0, wf_size, dtype=wf_size.dtype), ADCs) + + # ping the tracker (make this optional) self.tracker.track(ADCs.nbytes) - + # prep the next thread if self.digitiser.isAcquiring: self.worker_wait_condition.notify_one() @@ -120,7 +129,7 @@ def update_fps(self): def run_app(self): self.main_window.show() return self.app.exec() - + def connect_digitiser(self): ''' Connect to the digitiser using the provided configuration file. @@ -131,7 +140,7 @@ def connect_digitiser(self): # Load in configs dig_dict = read_config_file(self.dig_config) rec_dict = read_config_file(self.rec_config) - + if dig_dict is None: logging.error("Digitiser configuration file not found or invalid.") #raise ValueError("Digitiser configuration file not found or invalid.") @@ -148,8 +157,8 @@ def connect_digitiser(self): else: if (digitiser is not None) and digitiser.isConnected: digitiser.configure(dig_dict, rec_dict) - return digitiser - + return digitiser + def start_acquisition(self): ''' @@ -166,7 +175,7 @@ def start_acquisition(self): logging.exception('Failed to start acquisition.') #self.digitiser.start_acquisition() #self.trigger_and_record() - + def stop_acquisition(self): ''' Simple stopping of acquisition, this will end the AcquisitionWorkers loop and terminate @@ -185,22 +194,22 @@ def __init__(self, wait_condition, digitiser, parent=None): self.mutex = QMutex() # ensure on initial startup that you're not acquiring. self.digitiser.isAcquiring = False - - + + def run(self): - - + + while True: self.mutex.lock() if not self.digitiser.isAcquiring: self.wait_condition.wait(self.mutex) self.mutex.unlock() - - + + self.data = self.digitiser.acquire() self.data_ready.emit() - + self.stop() def stop(self): @@ -234,4 +243,4 @@ def track(self, nbytes: int = 0): logging.info(f'|| {self.events_ps} events/sec || {MB:.2f} MB/sec ||') self.last_time = t_check self.bytes_ps = 0 - self.events_ps = 0 \ No newline at end of file + self.events_ps = 0 diff --git a/felib/digitiser.py b/felib/digitiser.py index dfed235..582f04b 100644 --- a/felib/digitiser.py +++ b/felib/digitiser.py @@ -41,12 +41,12 @@ def __init__(self, dig_dict : dict): else: logging.error("Invalid digitiser generation specified in the configuration.") #raise ValueError("Invalid digitiser generation specified in the configuration.") - + self.URI = self.generate_uri() self.isAcquiring = False self.isConnected = False self.isRecording = False - + self.data_format = [] self.endpoint = None @@ -73,15 +73,15 @@ def generate_uri(self): else: logging.error("Invalid digitiser generation specified in the configuration.") #raise ValueError("Invalid digitiser generation specified in the configuration.") - + def connect(self): ''' Connect to the digitiser using the generated URI. ''' - + logging.info(f'Attemping connection to digitiser {self.dig_name} at {self.URI}.') - + # fake connection for debugging if self.dig_name == 'debug': self.dig = None @@ -113,7 +113,7 @@ def connect(self): return None - def configure(self, + def configure(self, dig_dict : dict, rec_dict : dict): #record_length: Optional[int] = 0, @@ -121,7 +121,7 @@ def configure(self, #trigger_level: Optional[str] = 'SWTRG'): ''' Configure the digitiser with the provided settings and calibrate it. - ''' + ''' self.record_length = rec_dict.get('record_length') self.pre_trigger = rec_dict.get('pre_trigger') @@ -142,21 +142,28 @@ def configure(self, # extract channel config of interest ch_dict = rec_dict.get(f'ch{i}') - + + # disable channels if not explicitly called if ch_dict is None: + ch.par.CH_ENABLED.value = 'FALSE' continue - + ch.par.CH_ENABLED.value = 'TRUE' if ch_dict['enabled'] else 'FALSE' - ch.par.CH_PRETRG.value = f'{self.pre_trigger}' + + # set post trigger for SCOPE, pre trigger for DPP-DSD + # in principle, the SCOPE one is digitiser-wide, and so doesnt have to be set per channel + # but I like both here + if self.dig.par.FWTYPE.value == 'DPP-DSD' : ch.par.CH_PRETRIG.value = f'{self.pre_trigger}' + elif self.dig.par.FWTYPE.value == 'SCOPE' : self.dig.par.POSTTRG.value = f'{self.record_length - self.pre_trigger}' # ensure self trigger only enabled when you don't have SWTRIG enabled if ch_dict['self_trigger'] and self.trigger_mode != 'SWTRIG': - ch.par.CH_SELF_TRG_ENABLE.value = 'TRUE' + if self.dig.par.FWTYPE.value == 'DPP-DSD' : ch.par.CH_SELF_TRG_ENABLE.value = 'TRUE' ch.par.CH_THRESHOLD.value = str(ch_dict['threshold']) else: # doesn't reset by default! so forcing this here - ch.par.CH_SELF_TRG_ENABLE.value = 'FALSE' - + if self.dig.par.FWTYPE.value == 'DPP-DSD' : ch.par.CH_SELF_TRG_ENABLE.value = 'FALSE' + if ch_dict['polarity'] == 'positive': ch.par.CH_POLARITY.value = 'POLARITY_POSITIVE' elif ch_dict['polarity'] == 'negative': @@ -165,25 +172,45 @@ def configure(self, else: ch.par.CH_SELF_TRG_ENABLE.value = 'FALSE' # technically customisable - + # calculate the true reclen value for outputting reclen_ns = int(self.dig.par.RECLEN.value) self.reclen = int(reclen_ns / int(1e3 / self.dig_info['sample_rate'])) - # if DPP, need to specify that you're looking at waveforms specifically. - if self.dig.par.FWTYPE.value == 'DPP-PSD': - self.dig.par.WAVEFORMS.value = 'TRUE' - self.data_format = formats.DPP(int(self.dig.par.NUMCH.value), int(self.reclen)) - # setting up probe types (READ UP ON THIS) - self.dig.vtrace[0].par.VTRACE_PROBE.value = 'VPROBE_INPUT' - + # set up data formats + match self.dig.par.FWTYPE.value: + case 'DPP-PSD': + self.dig.par.WAVEFORMS.value = 'TRUE' + self.data_format = formats.DPP(int(self.dig.par.NUMCH.value), int(self.reclen)) + # setting up probe types (READ UP ON THIS) + self.dig.vtrace[0].par.VTRACE_PROBE.value = 'VPROBE_INPUT' + case 'SCOPE': + self.data_format = formats.SCOPE(int(self.dig.par.NUMCH.value), int(self.reclen)) + case _: + logging.exception(f"Firmware type {self.dig.par.FWTYPE.value} not recognised.\nCurrent FWs available are DPP-DSD and SCOPE") + endpoint_path = (self.dig.par.FWTYPE.value).replace('-', '') self.endpoint = self.dig.endpoint[endpoint_path] self.data = self.endpoint.set_read_data_format(self.data_format) - + # generalised extraction of data parameters + match self.dig.par.FWTYPE.value: + case 'DPP-PSD': + self.channel = self.data[0].value + self.timestamp = self.data[1].value + self.waveform_size = self.data[7].value + self.waveform = self.data[3].value + case 'SCOPE': + # no channel parameter as channels are treated differently + # all channels are within the dataset, check the data format to understand the shape + self.timestamp = self.data[1].value + self.waveform_size = self.data[3].value + self.waveform = self.data[2].value + case _: + logging.exception(f"Firmware type {self.dig.par.FWTYPE.value} not recognised.\nCurrent FWs available are DPP-DSD and SCOPE") + logging.info(f"Digitiser configured:\nrecord length {self.record_length}, pre-trigger {self.pre_trigger}, trigger mode {self.trigger_mode}.") except Exception as e: logging.exception(f"Failed to configure recording parameters.\n{e}") @@ -206,18 +233,18 @@ def start_acquisition(self): self.dig.cmd.ARMACQUISITION() except Exception as e: logging.exception("Starting acquisition failed:") - + # start recording function #self.trigger_and_record() #try: #self.dig.cmd.START() - #self.collect = True + #self.collect = True #print("Digitiser acquisition started.") #except Exception as e: # raise RuntimeError(f"Failed to start digitiser acquisition.\n{e}") #self.collect = True - - + + def stop_acquisition(self): ''' Stop the digitiser acquisition. @@ -239,7 +266,7 @@ def acquire(self): return self.SELFTRIG_record() case _: logging.info(f'Trigger mode {self.trigger_mode} not currently implemented.') - self.stop_acquisition() + self.stop_acquisition() def SW_record(self): @@ -252,7 +279,7 @@ def SW_record(self): try: self.endpoint.has_data(check_timeout) self.endpoint.read_data(read_timeout, self.data) # timeout first number in ms - return (self.data[7].value, self.data[3].value) + return (self.waveform_size, self.waveform) except error.Error as ex: #logging.exception("Error in readout:") if ex.code is error.ErrorCode.TIMEOUT: @@ -260,16 +287,16 @@ def SW_record(self): if ex.code is error.ErrorCode.STOP: logging.exception("STOP") raise ex - + # ensure the input and trigger are acceptable (I think?) #assert self.data[3].value == 1 # VPROBE INPUT? I need to understand this #assert self.data[6].value == 1 # VPROBE TRIGGER? - + #waveform_size = self.data[7].value #valid_sample_range = np.arange(0, waveform_size, dtype = waveform_size.dtype) - - + + def SELFTRIG_record(self): ''' Trigger on channels @@ -297,4 +324,4 @@ def __del__(self): self.stop_acquisition() if hasattr(self, 'dig') and self.dig is not None: logging.info("Closing digitiser connection.") - self.dig.close() \ No newline at end of file + self.dig.close() diff --git a/felib/formats.py b/felib/formats.py index add2675..d5b184e 100644 --- a/felib/formats.py +++ b/felib/formats.py @@ -1,3 +1,4 @@ +from caen_felib import device # location for defining all the data formats of the differing firmwares def DPP(nch, record_length): @@ -5,51 +6,85 @@ def DPP(nch, record_length): DPP-DSD format nch - number of channels ''' - + # Configure endpoint data_format = [ { - 'name': 'CHANNEL', - 'type': 'U8', - 'dim' : 0, + 'name' : 'CHANNEL', + 'type' : 'U8', + 'dim' : 0, }, { - 'name': 'TIMESTAMP', - 'type': 'U64', - 'dim': 0, + 'name' : 'TIMESTAMP', + 'type' : 'U64', + 'dim' : 0, }, { - 'name': 'ENERGY', - 'type': 'U16', - 'dim': 0, + 'name' : 'ENERGY', + 'type' : 'U16', + 'dim' : 0, }, { - 'name': 'ANALOG_PROBE_1', - 'type': 'I16', - 'dim': 1, - 'shape': [record_length], + 'name' : 'ANALOG_PROBE_1', + 'type' : 'I16', + 'dim' : 1, + 'shape' : [record_length], }, { - 'name': 'ANALOG_PROBE_1_TYPE', - 'type': 'I32', - 'dim': 0, + 'name' : 'ANALOG_PROBE_1_TYPE', + 'type' : 'I32', + 'dim' : 0, }, { - 'name': 'DIGITAL_PROBE_1', - 'type': 'U8', - 'dim': 1, - 'shape': [record_length], + 'name' : 'DIGITAL_PROBE_1', + 'type' : 'U8', + 'dim' : 1, + 'shape' : [record_length], }, { - 'name': 'DIGITAL_PROBE_1_TYPE', - 'type': 'I32', - 'dim': 0, + 'name' : 'DIGITAL_PROBE_1_TYPE', + 'type' : 'I32', + 'dim' : 0, + }, + { + 'name' : 'WAVEFORM_SIZE', + 'type' : 'SIZE_T', + 'dim' : 0, + } + ] + + return data_format + + +def SCOPE(nch, record_length): + ''' + SCOPE format + nch - number of channels + ''' + + # Configure endpoint + # Configure endpoint + data_format = [ + { + 'name': 'EVENT_SIZE', + 'type': 'SIZE_T', + }, + { + 'name': 'TIMESTAMP', + 'type': 'U64', + }, + { + 'name': 'WAVEFORM', + 'type': 'U16', + 'dim': 2, + 'shape': [nch, record_length], }, { 'name': 'WAVEFORM_SIZE', 'type': 'SIZE_T', - 'dim': 0, + 'dim': 1, + 'shape': [nch], } ] - return data_format \ No newline at end of file + return data_format