Skip to content
9 changes: 9 additions & 0 deletions configs/digitiser/wd1_scope_a4818.conf
Original file line number Diff line number Diff line change
@@ -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'
47 changes: 28 additions & 19 deletions core/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -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.
Expand All @@ -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.")
Expand All @@ -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):
'''
Expand All @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -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
self.events_ps = 0
93 changes: 60 additions & 33 deletions felib/digitiser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -113,15 +113,15 @@ def connect(self):
return None


def configure(self,
def configure(self,
dig_dict : dict,
rec_dict : dict):
#record_length: Optional[int] = 0,
#pre_trigger: Optional[int] = 0,
#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')
Expand All @@ -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':
Expand All @@ -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}")
Expand All @@ -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.
Expand All @@ -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):
Expand All @@ -252,24 +279,24 @@ 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:
logging.warning("Trigger timed out before receiving data. Increase timeout to avoid this warning") # Resolved by increasing timeout
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
Expand Down Expand Up @@ -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()
self.dig.close()
Loading
Loading