|
1
|
|
|
# -*- coding: utf-8 -*- |
|
2
|
|
|
""" |
|
3
|
|
|
This file contains the Qudi hardware module for the PicoHarp300. |
|
4
|
|
|
|
|
5
|
|
|
Qudi is free software: you can redistribute it and/or modify |
|
6
|
|
|
it under the terms of the GNU General Public License as published by |
|
7
|
|
|
the Free Software Foundation, either version 3 of the License, or |
|
8
|
|
|
(at your option) any later version. |
|
9
|
|
|
|
|
10
|
|
|
Qudi is distributed in the hope that it will be useful, |
|
11
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13
|
|
|
GNU General Public License for more details. |
|
14
|
|
|
|
|
15
|
|
|
You should have received a copy of the GNU General Public License |
|
16
|
|
|
along with Qudi. If not, see <http://www.gnu.org/licenses/>. |
|
17
|
|
|
|
|
18
|
|
|
Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the |
|
19
|
|
|
top-level directory of this distribution and at <https://github.com/Ulm-IQO/qudi/> |
|
20
|
|
|
""" |
|
21
|
|
|
|
|
22
|
|
|
import ctypes |
|
23
|
|
|
import numpy as np |
|
24
|
|
|
import time |
|
25
|
|
|
from qtpy import QtCore |
|
26
|
|
|
|
|
27
|
|
|
from core.base import Base |
|
28
|
|
|
from core.util.mutex import Mutex |
|
29
|
|
|
from interface.slow_counter_interface import SlowCounterInterface |
|
30
|
|
|
from interface.fast_counter_interface import FastCounterInterface |
|
31
|
|
|
|
|
32
|
|
|
# ============================================================================= |
|
33
|
|
|
# Wrapper around the PHLib.DLL. The current file is based on the header files |
|
34
|
|
|
# 'phdefin.h', 'phlib.h' and 'errorcodes.h'. The 'phdefin.h' contains all the |
|
35
|
|
|
# constants and 'phlib.h' contains all the functions exported within the dll |
|
36
|
|
|
# file. 'errorcodes.h' contains the possible error messages of the device. |
|
37
|
|
|
# |
|
38
|
|
|
# The wrappered commands are based on the PHLib Version 3.0. For further |
|
39
|
|
|
# information read the manual |
|
40
|
|
|
# 'PHLib - Programming Library for Custom Software Development' |
|
41
|
|
|
# which can be downloaded from the PicoQuant homepage. |
|
42
|
|
|
# ============================================================================= |
|
43
|
|
|
""" |
|
44
|
|
|
The PicoHarp programming library PHLib.DLL is written in C and its data types |
|
45
|
|
|
correspond to standard C/C++ data types as follows: |
|
46
|
|
|
|
|
47
|
|
|
char 8 bit, byte (or characters in ASCII) |
|
48
|
|
|
short int 16 bit signed integer |
|
49
|
|
|
unsigned short int 16 bit unsigned integer |
|
50
|
|
|
int 32 bit signed integer |
|
51
|
|
|
long int 32 bit signed integer |
|
52
|
|
|
unsigned int 32 bit unsigned integer |
|
53
|
|
|
unsigned long int 32 bit unsigned integer |
|
54
|
|
|
float 32 bit floating point number |
|
55
|
|
|
double 64 bit floating point number |
|
56
|
|
|
""" |
|
57
|
|
|
|
|
58
|
|
|
# Bitmask in hex. |
|
59
|
|
|
# the comments behind each bitmask contain the integer value for the bitmask. |
|
60
|
|
|
# You can check that by typing 'int(0x0001)' into the console to get the int. |
|
61
|
|
|
|
|
62
|
|
|
#FEATURE_DLL = 0x0001 # |
|
63
|
|
|
#FEATURE_TTTR = 0x0002 # 2 |
|
64
|
|
|
#FEATURE_MARKERS = 0x0004 # 4 |
|
65
|
|
|
#FEATURE_LOWRES = 0x0008 # 8 |
|
66
|
|
|
#FEATURE_TRIGOUT = 0x0010 # 16 |
|
67
|
|
|
# |
|
68
|
|
|
#FLAG_FIFOFULL = 0x0003 # T-modes # 3 |
|
69
|
|
|
#FLAG_OVERFLOW = 0x0040 # Histomode # 64 |
|
70
|
|
|
#FLAG_SYSERROR = 0x0100 # Hardware problem # 256 |
|
71
|
|
|
|
|
72
|
|
|
# The following are bitmasks for return values from GetWarnings() |
|
73
|
|
|
#WARNING_INP0_RATE_ZERO = 0x0001 # 1 |
|
74
|
|
|
#WARNING_INP0_RATE_TOO_LOW = 0x0002 # 2 |
|
75
|
|
|
#WARNING_INP0_RATE_TOO_HIGH = 0x0004 # 4 |
|
76
|
|
|
# |
|
77
|
|
|
#WARNING_INP1_RATE_ZERO = 0x0010 # 16 |
|
78
|
|
|
#WARNING_INP1_RATE_TOO_HIGH = 0x0040 # 64 |
|
79
|
|
|
# |
|
80
|
|
|
#WARNING_INP_RATE_RATIO = 0x0100 # 256 |
|
81
|
|
|
#WARNING_DIVIDER_GREATER_ONE = 0x0200 # 512 |
|
82
|
|
|
#WARNING_TIME_SPAN_TOO_SMALL = 0x0400 # 1024 |
|
83
|
|
|
#WARNING_OFFSET_UNNECESSARY = 0x0800 # 2048 |
|
84
|
|
|
|
|
85
|
|
|
|
|
86
|
|
|
class PicoHarp300(Base, SlowCounterInterface, FastCounterInterface): |
|
87
|
|
|
"""Hardware class to control the Picoharp 300 from PicoQuant. |
|
88
|
|
|
|
|
89
|
|
|
This class is written according to the Programming Library Version 3.0 |
|
90
|
|
|
STABLE AND TESTED VERSION: Alex S. |
|
91
|
|
|
""" |
|
92
|
|
|
_modclass = 'PicoHarp300' |
|
93
|
|
|
_modtype = 'hardware' |
|
94
|
|
|
|
|
95
|
|
|
# declare connectors |
|
96
|
|
|
_out = {'picocounter': 'PicoHarp300', |
|
97
|
|
|
'counter': 'SlowCounterInterface' |
|
98
|
|
|
} |
|
99
|
|
|
|
|
100
|
|
|
sigReadoutPicoharp = QtCore.Signal() |
|
101
|
|
|
sigAnalyzeData = QtCore.Signal(object, object) |
|
102
|
|
|
sigStart = QtCore.Signal() |
|
103
|
|
|
|
|
104
|
|
|
def __init__(self, config, **kwargs): |
|
105
|
|
|
super().__init__(config=config, **kwargs) |
|
106
|
|
|
|
|
107
|
|
|
if 'deviceID' in config.keys(): |
|
108
|
|
|
self._deviceID = config['deviceID'] |
|
109
|
|
|
else: |
|
110
|
|
|
self.log.warning('Picoharp: No deviceID specified in the ' |
|
111
|
|
|
'config!\n' |
|
112
|
|
|
'Devide ID = 0 will be taken, but without any ' |
|
113
|
|
|
'warranty to be able to connect now correctly to the ' |
|
114
|
|
|
'device.') |
|
115
|
|
|
|
|
116
|
|
|
self._deviceID = 0 |
|
117
|
|
|
|
|
118
|
|
|
if 'mode' in config.keys(): |
|
119
|
|
|
self._mode = config['mode'] |
|
120
|
|
|
else: |
|
121
|
|
|
self.log.warning('Picoharp: No mode specified in the config!\n' |
|
122
|
|
|
'Mode will be set to 0 (= Histogram Mode) as a ' |
|
123
|
|
|
'default.') |
|
124
|
|
|
self._mode = 0 |
|
125
|
|
|
|
|
126
|
|
|
self.errorcode = self._create_errorcode() |
|
127
|
|
|
self._set_constants() |
|
128
|
|
|
|
|
129
|
|
|
# the library can communicate with 8 devices: |
|
130
|
|
|
self.connected_to_device = False |
|
131
|
|
|
|
|
132
|
|
|
#FIXME: Check which architecture the host PC is and choose the dll |
|
133
|
|
|
# according to that! |
|
134
|
|
|
|
|
135
|
|
|
# Load the picoharp library file phlib64.dll from the folder |
|
136
|
|
|
# <Windows>/System32/ |
|
137
|
|
|
self._dll = ctypes.cdll.LoadLibrary('phlib64') |
|
138
|
|
|
|
|
139
|
|
|
# Just some default values: |
|
140
|
|
|
self._bin_width_ns = 3000 |
|
141
|
|
|
self._record_length_ns = 100 *1e9 |
|
142
|
|
|
|
|
143
|
|
|
self._photon_source2 = None #for compatibility reasons with second APD |
|
144
|
|
|
self._count_channel = 1 |
|
145
|
|
|
|
|
146
|
|
|
#locking for thread safety |
|
147
|
|
|
self.threadlock = Mutex() |
|
148
|
|
|
|
|
149
|
|
|
|
|
150
|
|
|
def on_activate(self, fysom_e=None): |
|
151
|
|
|
""" Activate and establish the connection to Picohard and initialize. |
|
152
|
|
|
|
|
153
|
|
|
@param object e: Event class object from Fysom. |
|
154
|
|
|
An object created by the state machine module Fysom, |
|
155
|
|
|
which is connected to a specific event (have a look in |
|
156
|
|
|
the Base Class). This object contains the passed event |
|
157
|
|
|
the state before the event happens and the destination |
|
158
|
|
|
of the state which should be reached after the event |
|
159
|
|
|
has happen. |
|
160
|
|
|
""" |
|
161
|
|
|
self.open_connection() |
|
162
|
|
|
self.initialize(self._mode) |
|
163
|
|
|
self.calibrate() |
|
164
|
|
|
|
|
165
|
|
|
#FIXME: These are default values determined from the measurement |
|
166
|
|
|
# One need still to include this in the config. |
|
167
|
|
|
self.set_input_CFD(1,10,7) |
|
168
|
|
|
|
|
169
|
|
|
# the signal has one argument of type object, which should allow |
|
170
|
|
|
# anything to pass through: |
|
171
|
|
|
|
|
172
|
|
|
self.sigStart.connect(self.start_measure) |
|
173
|
|
|
self.sigReadoutPicoharp.connect(self.get_fresh_data_loop, QtCore.Qt.QueuedConnection) # ,QtCore.Qt.QueuedConnection |
|
174
|
|
|
self.sigAnalyzeData.connect(self.analyze_received_data, QtCore.Qt.QueuedConnection) |
|
175
|
|
|
self.result = [] |
|
176
|
|
|
|
|
177
|
|
|
|
|
178
|
|
|
def on_deactivate(self, fysom_e=None): |
|
179
|
|
|
""" Deactivates and disconnects the device. |
|
180
|
|
|
|
|
181
|
|
|
@param object e: Event class object from Fysom. Detailed explanation |
|
182
|
|
|
see in method 'activation'. |
|
183
|
|
|
""" |
|
184
|
|
|
|
|
185
|
|
|
self.close_connection() |
|
186
|
|
|
self.sigReadoutPicoharp.disconnect() |
|
187
|
|
|
self.sigAnalyzeData.disconnect() |
|
188
|
|
|
|
|
189
|
|
|
def _create_errorcode(self): |
|
190
|
|
|
""" Create a dictionary with the errorcode for the device. |
|
191
|
|
|
|
|
192
|
|
|
@return dict: errorcode in a dictionary |
|
193
|
|
|
|
|
194
|
|
|
The errorcode is extracted of PHLib Ver. 3.0, December 2013. The |
|
195
|
|
|
errorcode can be also extracted by calling the get_error_string method |
|
196
|
|
|
with the appropriate integer value. |
|
197
|
|
|
""" |
|
198
|
|
|
|
|
199
|
|
|
maindir = self.get_main_dir() |
|
200
|
|
|
|
|
201
|
|
|
filename = os.path.join(maindir, 'hardware', 'PicoQuant', 'errorcodes.h') |
|
202
|
|
|
try: |
|
203
|
|
|
with open(filename) as f: |
|
204
|
|
|
content = f.readlines() |
|
205
|
|
|
except: |
|
206
|
|
|
self.log.error('No file "errorcodes.h" could be found in the ' |
|
207
|
|
|
'PicoHarp hardware directory!') |
|
208
|
|
|
|
|
209
|
|
|
errorcode = {} |
|
210
|
|
|
for line in content: |
|
211
|
|
|
if '#define ERROR' in line: |
|
212
|
|
|
errorstring, errorvalue = line.split()[-2:] |
|
213
|
|
|
errorcode[int(errorvalue)] = errorstring |
|
214
|
|
|
|
|
215
|
|
|
return errorcode |
|
216
|
|
|
|
|
217
|
|
|
def _set_constants(self): |
|
218
|
|
|
"""Set the constants (max and min values) for the Picoharp300 device. |
|
219
|
|
|
These setting are taken from phdefin.h""" |
|
220
|
|
|
|
|
221
|
|
|
self.MODE_HIST = 0 |
|
222
|
|
|
self.MODE_T2 = 2 |
|
223
|
|
|
self.MODE_T3 = 3 |
|
224
|
|
|
|
|
225
|
|
|
# in mV: |
|
226
|
|
|
self.ZCMIN = 0 |
|
227
|
|
|
self.ZCMAX = 20 |
|
228
|
|
|
self.DISCRMIN = 0 |
|
229
|
|
|
self.DISCRMAX = 800 |
|
230
|
|
|
self.PHR800LVMIN = -1600 |
|
231
|
|
|
self.PHR800LVMAX = 2400 |
|
232
|
|
|
|
|
233
|
|
|
# in ps: |
|
234
|
|
|
self.OFFSETMIN = 0 |
|
235
|
|
|
self.OFFSETMAX = 1000000000 |
|
236
|
|
|
self.SYNCOFFSMIN = -99999 |
|
237
|
|
|
self.SYNCOFFSMAX = 99999 |
|
238
|
|
|
|
|
239
|
|
|
# in ms: |
|
240
|
|
|
self.ACQTMIN = 1 |
|
241
|
|
|
self.ACQTMAX = 10*60*60*1000 |
|
242
|
|
|
self.TIMEOUT = 80 # the maximal device timeout for a readout request |
|
243
|
|
|
|
|
244
|
|
|
# in ns: |
|
245
|
|
|
self.HOLDOFFMAX = 210480 |
|
246
|
|
|
|
|
247
|
|
|
self.BINSTEPSMAX = 8 |
|
248
|
|
|
self.HISTCHAN = 65536 # number of histogram channels 2^16 |
|
249
|
|
|
self.TTREADMAX = 131072 # 128K event records (2^17) |
|
250
|
|
|
|
|
251
|
|
|
# in Hz: |
|
252
|
|
|
self.COUNTFREQ = 10 |
|
253
|
|
|
|
|
254
|
|
|
def check(self, func_val): |
|
255
|
|
|
""" Check routine for the received error codes. |
|
256
|
|
|
|
|
257
|
|
|
@param func_val int: return error code of the called function. |
|
258
|
|
|
|
|
259
|
|
|
@return int: pass the error code further so that other functions have |
|
260
|
|
|
the possibility to use it. |
|
261
|
|
|
|
|
262
|
|
|
Each called function in the dll has an 32-bit return integer, which |
|
263
|
|
|
indicates, whether the function was called and finished successfully |
|
264
|
|
|
(then func_val = 0) or if any error has occured (func_val < 0). The |
|
265
|
|
|
errorcode, which corresponds to the return value can be looked up in |
|
266
|
|
|
the file 'errorcodes.h'. |
|
267
|
|
|
""" |
|
268
|
|
|
|
|
269
|
|
|
if not func_val == 0: |
|
270
|
|
|
self.log.error('Error in PicoHarp300 with errorcode {0}:\n' |
|
271
|
|
|
'{1}'.format(func_val, self.errorcode[func_val])) |
|
272
|
|
|
return func_val |
|
273
|
|
|
|
|
274
|
|
|
# ========================================================================= |
|
275
|
|
|
# These two function below can be accessed without connection to device. |
|
276
|
|
|
# ========================================================================= |
|
277
|
|
|
|
|
278
|
|
|
def get_version(self): |
|
279
|
|
|
""" Get the software/library version of the device. |
|
280
|
|
|
|
|
281
|
|
|
@return string: string representation of the |
|
282
|
|
|
Version number of the current library.""" |
|
283
|
|
|
buf = ctypes.create_string_buffer(16) # at least 8 byte |
|
284
|
|
|
self.check(self._dll.PH_GetLibraryVersion(ctypes.byref(buf))) |
|
285
|
|
|
return buf.value # .decode() converts byte to string |
|
286
|
|
|
|
|
287
|
|
|
def get_error_string(self, errcode): |
|
288
|
|
|
""" Get the string error code from the Picoharp Device. |
|
289
|
|
|
|
|
290
|
|
|
@param int errcode: errorcode from 0 and below. |
|
291
|
|
|
|
|
292
|
|
|
@return byte: byte representation of the string error code. |
|
293
|
|
|
|
|
294
|
|
|
The stringcode for the error is the same as it is extracted from the |
|
295
|
|
|
errorcodes.h header file. Note that errcode should have the value 0 |
|
296
|
|
|
or lower, since interger bigger 0 are not defined as error. |
|
297
|
|
|
""" |
|
298
|
|
|
|
|
299
|
|
|
buf = ctypes.create_string_buffer(80) # at least 40 byte |
|
300
|
|
|
self.check(self._dll.PH_GetErrorString(ctypes.byref(buf), errcode)) |
|
301
|
|
|
return buf.value.decode() # .decode() converts byte to string |
|
302
|
|
|
|
|
303
|
|
|
# ========================================================================= |
|
304
|
|
|
# Establish the connection and initialize the device or disconnect it. |
|
305
|
|
|
# ========================================================================= |
|
306
|
|
|
|
|
307
|
|
|
def open_connection(self): |
|
308
|
|
|
""" Open a connection to this device. """ |
|
309
|
|
|
|
|
310
|
|
|
|
|
311
|
|
|
buf = ctypes.create_string_buffer(16) # at least 8 byte |
|
312
|
|
|
ret = self.check(self._dll.PH_OpenDevice(self._deviceID, ctypes.byref(buf))) |
|
313
|
|
|
self._serial = buf.value.decode() # .decode() converts byte to string |
|
314
|
|
|
if ret >= 0: |
|
315
|
|
|
self.connected_to_device = True |
|
316
|
|
|
self.log.info('Connection to the Picoharp 300 established') |
|
317
|
|
|
|
|
318
|
|
|
def initialize(self, mode): |
|
319
|
|
|
""" Initialize the device with one of the three possible modes. |
|
320
|
|
|
|
|
321
|
|
|
@param int mode: 0: histogramming |
|
322
|
|
|
2: T2 |
|
323
|
|
|
3: T3 |
|
324
|
|
|
""" |
|
325
|
|
|
mode = int(mode) # for safety reasons, convert to integer |
|
326
|
|
|
self._mode = mode |
|
327
|
|
|
|
|
328
|
|
|
if not ((mode != self.MODE_HIST) or (mode != self.MODE_T2) or \ |
|
329
|
|
|
(mode != self.MODE_T3)): |
|
330
|
|
|
self.log.error('Picoharp: Mode for the device could not be set. ' |
|
331
|
|
|
'It must be {0}=Histogram-Mode, {1}=T2-Mode or ' |
|
332
|
|
|
'{2}=T3-Mode, but a parameter {3} was ' |
|
333
|
|
|
'passed.'.format( |
|
334
|
|
|
self.MODE_HIST, |
|
335
|
|
|
self.MODE_T2, |
|
336
|
|
|
self.MODE_T3, |
|
337
|
|
|
mode)) |
|
338
|
|
|
else: |
|
339
|
|
|
self.check(self._dll.PH_Initialize(self._deviceID, mode)) |
|
340
|
|
|
|
|
341
|
|
|
def close_connection(self): |
|
342
|
|
|
"""Close the connection to the device. |
|
343
|
|
|
|
|
344
|
|
|
@param int deviceID: a divice index from 0 to 7. |
|
345
|
|
|
""" |
|
346
|
|
|
self.connected_to_device = False |
|
347
|
|
|
self.check(self._dll.PH_CloseDevice(self._deviceID)) |
|
348
|
|
|
self.log.info('Connection to the Picoharp 300 closed.') |
|
349
|
|
|
|
|
350
|
|
|
# def __del__(self): |
|
351
|
|
|
# """ Delete the object PicoHarp300.""" |
|
352
|
|
|
# self.close() |
|
353
|
|
|
|
|
354
|
|
|
# ========================================================================= |
|
355
|
|
|
# All functions below can be used if the device was successfully called. |
|
356
|
|
|
# ========================================================================= |
|
357
|
|
|
|
|
358
|
|
|
def get_hardware_info(self): |
|
359
|
|
|
""" Retrieve the device hardware information. |
|
360
|
|
|
|
|
361
|
|
|
@return string tuple(3): (Model, Partnum, Version) |
|
362
|
|
|
""" |
|
363
|
|
|
|
|
364
|
|
|
model = ctypes.create_string_buffer(32) # at least 16 byte |
|
365
|
|
|
version = ctypes.create_string_buffer(16) # at least 8 byte |
|
366
|
|
|
partnum = ctypes.create_string_buffer(16) # at least 8 byte |
|
367
|
|
|
self.check(self._dll.PH_GetHardwareInfo(self._deviceID, ctypes.byref(model), |
|
368
|
|
|
ctypes.byref(partnum), ctypes.byref(version))) |
|
369
|
|
|
|
|
370
|
|
|
# the .decode() function converts byte objects to string objects |
|
371
|
|
|
return (model.value.decode(), partnum.value.decode(), version.value.decode()) |
|
372
|
|
|
|
|
373
|
|
|
def get_serial_number(self): |
|
374
|
|
|
""" Retrieve the serial number of the device. |
|
375
|
|
|
|
|
376
|
|
|
@return string: serial number of the device |
|
377
|
|
|
""" |
|
378
|
|
|
|
|
379
|
|
|
serialnum = ctypes.create_string_buffer(16) # at least 8 byte |
|
380
|
|
|
self.check(self._dll.PH_GetSerialNumber(self._deviceID, ctypes.byref(serialnum))) |
|
381
|
|
|
return serialnum.value.decode() # .decode() converts byte to string |
|
382
|
|
|
|
|
383
|
|
|
def get_base_resolution(self): |
|
384
|
|
|
""" Retrieve the base resolution of the device. |
|
385
|
|
|
|
|
386
|
|
|
@return double: the base resolution of the device |
|
387
|
|
|
""" |
|
388
|
|
|
|
|
389
|
|
|
res = ctypes.c_double() |
|
390
|
|
|
self.check(self._dll.PH_GetBaseResolution(self._deviceID, ctypes.byref(res))) |
|
391
|
|
|
return res.value |
|
392
|
|
|
|
|
393
|
|
|
def calibrate(self): |
|
394
|
|
|
""" Calibrate the device.""" |
|
395
|
|
|
self.check(self._dll.PH_Calibrate(self._deviceID)) |
|
396
|
|
|
|
|
397
|
|
|
def get_features(self): |
|
398
|
|
|
""" Retrieve the possible features of the device. |
|
399
|
|
|
|
|
400
|
|
|
@return int: a bit pattern indicating the feature. |
|
401
|
|
|
""" |
|
402
|
|
|
features = ctypes.c_int32() |
|
403
|
|
|
self.check(self._dll.PH_GetFeatures(self._deviceID, ctypes.byref(features))) |
|
404
|
|
|
return features.value |
|
405
|
|
|
|
|
406
|
|
View Code Duplication |
def set_input_CFD(self, channel, level, zerocross): |
|
|
|
|
|
|
407
|
|
|
""" Set the Constant Fraction Discriminators for the Picoharp300. |
|
408
|
|
|
|
|
409
|
|
|
@param int channel: number (0 or 1) of the input channel |
|
410
|
|
|
@param int level: CFD discriminator level in millivolts |
|
411
|
|
|
@param int zerocross: CFD zero cross in millivolts |
|
412
|
|
|
""" |
|
413
|
|
|
channel = int(channel) |
|
414
|
|
|
level = int(level) |
|
415
|
|
|
zerocross = int(zerocross) |
|
416
|
|
|
if channel not in (0, 1): |
|
417
|
|
|
self.log.error('PicoHarp: Channal does not exist.\nChannel has ' |
|
418
|
|
|
'to be 0 or 1 but {0} was passed.'.format(channel)) |
|
419
|
|
|
return |
|
420
|
|
|
if not(self.DISCRMIN <= level <= self.DISCRMAX): |
|
421
|
|
|
self.log.error('PicoHarp: Invalid CFD level.\nValue must be ' |
|
422
|
|
|
'within the range [{0},{1}] millivolts but a value of ' |
|
423
|
|
|
'{2} has been ' |
|
424
|
|
|
'passed.'.format(self.DISCRMIN, self.DISCRMAX, level)) |
|
425
|
|
|
return |
|
426
|
|
|
if not(self.ZCMIN <= zerocross <= self.ZCMAX): |
|
427
|
|
|
self.log.error('PicoHarp: Invalid CFD zero cross.\nValue must be ' |
|
428
|
|
|
'within the range [{0},{1}] millivolts but a value of ' |
|
429
|
|
|
'{2} has been ' |
|
430
|
|
|
'passed.'.format(self.ZCMIN, self.ZCMAX, zerocross)) |
|
431
|
|
|
return |
|
432
|
|
|
|
|
433
|
|
|
self.check(self._dll.PH_SetInputCFD(self._deviceID, channel, level, zerocross)) |
|
434
|
|
|
|
|
435
|
|
|
|
|
436
|
|
|
def set_sync_div(self, div): |
|
437
|
|
|
""" Synchronize the devider of the device. |
|
438
|
|
|
|
|
439
|
|
|
@param int div: input rate devider applied at channel 0 (1,2,4, or 8) |
|
440
|
|
|
|
|
441
|
|
|
The sync devider must be used to keep the effective sync rate at |
|
442
|
|
|
values <= 10MHz. It should only be used with sync sources of stable |
|
443
|
|
|
period. The readins obtained with PH_GetCountRate are corrected for the |
|
444
|
|
|
devider settin and deliver the external (undivided) rate. |
|
445
|
|
|
""" |
|
446
|
|
|
if not ( (div !=1) or (div !=2) or (div !=4) or (div !=8) ): |
|
447
|
|
|
self.log.error('PicoHarp: Invalid sync devider.\n' |
|
448
|
|
|
'Value must be 1, 2, 4 or 8 but a value of {0} was ' |
|
449
|
|
|
'passed.'.format(div)) |
|
450
|
|
|
return |
|
451
|
|
|
else: |
|
452
|
|
|
self.check(self._dll.PH_SetSyncDiv(self._deviceID, div)) |
|
453
|
|
|
|
|
454
|
|
|
def set_sync_offset(self, offset): |
|
455
|
|
|
""" Set the offset of the synchronization. |
|
456
|
|
|
|
|
457
|
|
|
@param int offset: offset (time shift) in ps for that channel. That |
|
458
|
|
|
value must lie within the range of SYNCOFFSMIN and |
|
459
|
|
|
SYNCOFFSMAX. |
|
460
|
|
|
""" |
|
461
|
|
|
offset = int(offset) |
|
462
|
|
|
if not(self.SYNCOFFSMIN <= offset <= self.SYNCOFFSMAX): |
|
463
|
|
|
self.log.error('PicoHarp: Invalid Synchronization offset.\nValue ' |
|
464
|
|
|
'must be within the range [{0},{1}] ps but a value of ' |
|
465
|
|
|
'{2} has been passed.'.format( |
|
466
|
|
|
self.SYNCOFFSMIN, self.SYNCOFFSMAX, offset)) |
|
467
|
|
|
else: |
|
468
|
|
|
self.check(self._dll.PH_SetSyncOffset(self._deviceID, offset)) |
|
469
|
|
|
|
|
470
|
|
|
|
|
471
|
|
|
def set_stop_overflow(self, stop_ovfl, stopcount): |
|
472
|
|
|
""" Stop the measurement if maximal amount of counts is reached. |
|
473
|
|
|
|
|
474
|
|
|
@param int stop_ovfl: 0 = do not stop, |
|
475
|
|
|
1 = do stop on overflow |
|
476
|
|
|
@param int stopcount: count level at which should be stopped |
|
477
|
|
|
(maximal 65535). |
|
478
|
|
|
|
|
479
|
|
|
This setting determines if a measurement run will stop if any channel |
|
480
|
|
|
reaches the maximum set by stopcount. If stop_ofl is 0 the measurement |
|
481
|
|
|
will continue but counts above 65535 in any bin will be clipped. |
|
482
|
|
|
""" |
|
483
|
|
|
if stop_ovfl not in (0, 1): |
|
484
|
|
|
self.log.error('PicoHarp: Invalid overflow parameter.\n' |
|
485
|
|
|
'The overflow parameter must be either 0 or 1 but a ' |
|
486
|
|
|
'value of {0} was passed.'.format(stop_ovfl)) |
|
487
|
|
|
return |
|
488
|
|
|
|
|
489
|
|
|
if not(0 <= stopcount <= self.HISTCHAN): |
|
490
|
|
|
self.log.error('PicoHarp: Invalid stopcount parameter.\n' |
|
491
|
|
|
'stopcount must be within the range [0,{0}] but a ' |
|
492
|
|
|
'value of {1} was passed.'.format(self.HISTCHAN, stopcount)) |
|
493
|
|
|
return |
|
494
|
|
|
|
|
495
|
|
|
return self.check(self._dll.PH_SetStopOverflow(self._deviceID, stop_ovfl, stopcount)) |
|
496
|
|
|
|
|
497
|
|
|
def set_binning(self, binning): |
|
498
|
|
|
""" Set the base resolution of the measurement. |
|
499
|
|
|
|
|
500
|
|
|
@param int binning: binning code |
|
501
|
|
|
minimum = 0 (smallest, i.e. base resolution) |
|
502
|
|
|
maximum = (BINSTEPSMAX-1) (largest) |
|
503
|
|
|
|
|
504
|
|
|
The binning code corresponds to a power of 2, i.e. |
|
505
|
|
|
0 = base resolution, => 4*2^0 = 4ps |
|
506
|
|
|
1 = 2x base resolution, => 4*2^1 = 8ps |
|
507
|
|
|
2 = 4x base resolution, => 4*2^2 = 16ps |
|
508
|
|
|
3 = 8x base resolution => 4*2^3 = 32ps |
|
509
|
|
|
4 = 16x base resolution => 4*2^4 = 64ps |
|
510
|
|
|
5 = 32x base resolution => 4*2^5 = 128ps |
|
511
|
|
|
6 = 64x base resolution => 4*2^6 = 256ps |
|
512
|
|
|
7 = 128x base resolution => 4*2^7 = 512ps |
|
513
|
|
|
|
|
514
|
|
|
These are all the possible values. In histogram mode the internal |
|
515
|
|
|
buffer can store 65535 points (each a 32bit word). For largest |
|
516
|
|
|
resolution you can count 33.55392 ms in total |
|
517
|
|
|
|
|
518
|
|
|
""" |
|
519
|
|
|
if not(0 <= binning < self.BINSTEPSMAX): |
|
520
|
|
|
self.log.error('PicoHarp: Invalid binning.\nValue must be within ' |
|
521
|
|
|
'the range [{0},{1}] bins, but a value of {2} has been ' |
|
522
|
|
|
'passed.'.format(0, self.BINSTEPSMAX, binning)) |
|
523
|
|
|
else: |
|
524
|
|
|
self.check(self._dll.PH_SetBinning(self._deviceID, binning)) |
|
525
|
|
|
|
|
526
|
|
|
def set_multistop_enable(self, enable=True): |
|
527
|
|
|
""" Set whether multistops are possible within a measurement. |
|
528
|
|
|
|
|
529
|
|
|
@param bool enable: optional, Enable or disable the mutlistops. |
|
530
|
|
|
|
|
531
|
|
|
This is only for special applications where the multistop feature of |
|
532
|
|
|
the Picoharp is causing complications in statistical analysis. Usually |
|
533
|
|
|
it is not required to call this function. By default, multistop is |
|
534
|
|
|
enabled after PH_Initialize. |
|
535
|
|
|
""" |
|
536
|
|
|
if enable: |
|
537
|
|
|
self.check(self._dll.PH_SetMultistopEnable(self._deviceID, 1)) |
|
538
|
|
|
else: |
|
539
|
|
|
self.check(self._dll.PH_SetMultistopEnable(self._deviceID, 0)) |
|
540
|
|
|
|
|
541
|
|
|
def set_offset(self, offset): |
|
542
|
|
|
""" Set an offset time. |
|
543
|
|
|
|
|
544
|
|
|
@param int offset: offset in ps (only possible for histogramming and T3 |
|
545
|
|
|
mode!). Value must be within [OFFSETMIN,OFFSETMAX]. |
|
546
|
|
|
|
|
547
|
|
|
The true offset is an approximation fo the desired offset by the |
|
548
|
|
|
nearest multiple of the base resolution. This offset only acts on the |
|
549
|
|
|
difference between ch1 and ch0 in hitogramming and T3 mode. Do not |
|
550
|
|
|
confuse it with the input offsets! |
|
551
|
|
|
""" |
|
552
|
|
|
if not(self.OFFSETMIN <= offset <= self.OFFSETMAX): |
|
553
|
|
|
self.log.error('PicoHarp: Invalid offset.\nValue must be within ' |
|
554
|
|
|
'the range [{0},{1}] ps, but a value of {2} has been ' |
|
555
|
|
|
'passed.'.format(self.OFFSETMIN, self.OFFSETMAX, offset)) |
|
556
|
|
|
else: |
|
557
|
|
|
self.check(self._dll.PH_SetOffset(self._deviceID, offset)) |
|
558
|
|
|
|
|
559
|
|
|
def clear_hist_memory(self, block=0): |
|
560
|
|
|
""" Clear the histogram memory. |
|
561
|
|
|
|
|
562
|
|
|
@param int block: set which block number to clear. |
|
563
|
|
|
""" |
|
564
|
|
|
self.check(self._dll.PH_ClearHistMem(self._deviceID, block)) |
|
565
|
|
|
|
|
566
|
|
|
def start(self, acq_time): |
|
567
|
|
|
""" Start acquisition for 'acq_time' ms. |
|
568
|
|
|
|
|
569
|
|
|
@param int acq_time: acquisition time in miliseconds. The value must be |
|
570
|
|
|
be within the range [ACQTMIN,ACQTMAX]. |
|
571
|
|
|
""" |
|
572
|
|
|
if not(self.ACQTMIN <= acq_time <= self.ACQTMAX): |
|
573
|
|
|
self.log.error('PicoHarp: No measurement could be started.\n' |
|
574
|
|
|
'The acquisition time must be within the range [{0},{1}] ' |
|
575
|
|
|
'ms, but a value of {2} has been passed.' |
|
576
|
|
|
''.format(self.ACQTMIN, self.ACQTMAX, acq_time)) |
|
577
|
|
|
else: |
|
578
|
|
|
self.check(self._dll.PH_StartMeas(self._deviceID, int(acq_time))) |
|
579
|
|
|
|
|
580
|
|
|
def stop_device(self): |
|
581
|
|
|
""" Stop the measurement.""" |
|
582
|
|
|
self.check(self._dll.PH_StopMeas(self._deviceID)) |
|
583
|
|
|
self.meas_run = False |
|
584
|
|
|
|
|
585
|
|
|
def _get_status(self): |
|
586
|
|
|
""" Check the status of the device. |
|
587
|
|
|
|
|
588
|
|
|
@return int: = 0: acquisition time still running |
|
589
|
|
|
> 0: acquisition time has ended, measurement finished. |
|
590
|
|
|
""" |
|
591
|
|
|
ctcstatus = ctypes.c_int32() |
|
592
|
|
|
self.check(self._dll.PH_CTCStatus(self._deviceID, ctypes.byref(ctcstatus))) |
|
593
|
|
|
return ctcstatus.value |
|
594
|
|
|
|
|
595
|
|
|
def get_histogram(self, block=0, xdata=True): |
|
596
|
|
|
""" Retrieve the measured histogram. |
|
597
|
|
|
|
|
598
|
|
|
@param int block: the block number to fetch (block >0 is only |
|
599
|
|
|
meaningful with routing) |
|
600
|
|
|
@param bool xdata: if true, the x values in ns corresponding to the |
|
601
|
|
|
read array will be returned. |
|
602
|
|
|
|
|
603
|
|
|
@return numpy.array[65536] or numpy.array[65536], numpy.array[65536]: |
|
604
|
|
|
depending if xdata = True, also the xdata are passed in |
|
605
|
|
|
ns. |
|
606
|
|
|
|
|
607
|
|
|
""" |
|
608
|
|
|
chcount = np.zeros((self.HISTCHAN,), dtype=np.uint32) |
|
609
|
|
|
# buf.ctypes.data is the reference to the array in the memory. |
|
610
|
|
|
self.check(self._dll.PH_GetHistogram(self._deviceID, chcount.ctypes.data, block)) |
|
611
|
|
|
if xdata: |
|
612
|
|
|
xbuf = np.arange(self.HISTCHAN) * self.get_resolution() / 1000 |
|
613
|
|
|
return xbuf, chcount |
|
614
|
|
|
return chcount |
|
615
|
|
|
|
|
616
|
|
|
def get_resolution(self): |
|
617
|
|
|
""" Retrieve the current resolution of the picohard. |
|
618
|
|
|
|
|
619
|
|
|
@return double: resolution at current binning. |
|
620
|
|
|
""" |
|
621
|
|
|
|
|
622
|
|
|
resolution = ctypes.c_double() |
|
623
|
|
|
self.check(self._dll.PH_GetResolution(self._deviceID, ctypes.byref(resolution))) |
|
624
|
|
|
return resolution.value |
|
625
|
|
|
|
|
626
|
|
|
def get_count_rate(self, channel): |
|
627
|
|
|
""" Get the current count rate for the |
|
628
|
|
|
|
|
629
|
|
|
@param int channel: which input channel to read (0 or 1): |
|
630
|
|
|
|
|
631
|
|
|
@return int: count rate in ps. |
|
632
|
|
|
|
|
633
|
|
|
The hardware rate meters emply a gate time of 100ms. You must allow at |
|
634
|
|
|
least 100ms after PH_Initialize or PH_SetDyncDivider to get a valid |
|
635
|
|
|
rate meter reading. Similarly, wait at least 100ms to get a new |
|
636
|
|
|
reading. The readings are corrected for the snyc devider setting and |
|
637
|
|
|
deliver the external (undivided) rate. The gate time cannot be changed. |
|
638
|
|
|
The readings may therefore be inaccurate of fluctuating when the rate |
|
639
|
|
|
are very low. If accurate rates are needed you must perform a full |
|
640
|
|
|
blown measurement and sum up the recorded events. |
|
641
|
|
|
""" |
|
642
|
|
|
if not ((channel !=0) or (channel != 1)): |
|
643
|
|
|
self.log.error('PicoHarp: Count Rate could not be read out, ' |
|
644
|
|
|
'Channel does not exist.\nChannel has to be 0 or 1 ' |
|
645
|
|
|
'but {0} was passed.'.format(channel)) |
|
646
|
|
|
return -1 |
|
647
|
|
|
else: |
|
648
|
|
|
rate = ctypes.c_int32() |
|
649
|
|
|
self.check(self._dll.PH_GetCountRate(self._deviceID, channel, ctypes.byref(rate))) |
|
650
|
|
|
return rate.value |
|
651
|
|
|
|
|
652
|
|
|
def get_flags(self): |
|
653
|
|
|
""" Get the current status flag as a bit pattern. |
|
654
|
|
|
|
|
655
|
|
|
@return int: the current status flags (a bit pattern) |
|
656
|
|
|
|
|
657
|
|
|
Use the predefined bit mask values in phdefin.h (e.g. FLAG_OVERFLOW) to |
|
658
|
|
|
extract indiviual bits though a bitwise AND. It is also recommended to |
|
659
|
|
|
check for FLAG_SYSERROR to detect possible hardware failures. In that |
|
660
|
|
|
case you may want to call PH_GetHardwareDebugInfo and submit the |
|
661
|
|
|
results to support. |
|
662
|
|
|
""" |
|
663
|
|
|
|
|
664
|
|
|
flags = ctypes.c_int32() |
|
665
|
|
|
self.check(self._dll.PH_GetFlags(self._deviceID, ctypes.byref(flags))) |
|
666
|
|
|
return flags.value |
|
667
|
|
|
|
|
668
|
|
|
def get_elepased_meas_time(self): |
|
669
|
|
|
""" Retrieve the elapsed measurement time in ms. |
|
670
|
|
|
|
|
671
|
|
|
@return double: the elapsed measurement time in ms. |
|
672
|
|
|
""" |
|
673
|
|
|
elapsed = ctypes.c_double() |
|
674
|
|
|
self.check(self._dll.PH_GetElapsedMeasTime(self._deviceID, ctypes.byref(elapsed))) |
|
675
|
|
|
return elapsed.value |
|
676
|
|
|
|
|
677
|
|
|
def get_warnings(self): |
|
678
|
|
|
"""Retrieve any warnings about the device or the current measurement. |
|
679
|
|
|
|
|
680
|
|
|
@return int: a bitmask for the warnings, as defined in phdefin.h |
|
681
|
|
|
|
|
682
|
|
|
NOTE: you have to call PH_GetCountRates for all channels prior to this |
|
683
|
|
|
call! |
|
684
|
|
|
""" |
|
685
|
|
|
warnings = ctypes.c_int32() |
|
686
|
|
|
self.check(self._dll.PH_GetWarnings(self._deviceID, ctypes.byref(warnings))) |
|
687
|
|
|
return warnings.value |
|
688
|
|
|
|
|
689
|
|
|
def get_warnings_text(self, warning_num): |
|
690
|
|
|
"""Retrieve the warningtext for the corresponding warning bitmask. |
|
691
|
|
|
|
|
692
|
|
|
@param int warning_num: the number for which you want to have the |
|
693
|
|
|
warning text. |
|
694
|
|
|
@return char[32568]: the actual text of the warning. |
|
695
|
|
|
|
|
696
|
|
|
""" |
|
697
|
|
|
text = ctypes.create_string_buffer(32568) # buffer at least 16284 byte |
|
698
|
|
|
self.check(self._dll.PH_GetWarningsText(self._deviceID, warning_num, text)) |
|
699
|
|
|
return text.value |
|
700
|
|
|
|
|
701
|
|
|
def get_hardware_debug_info(self): |
|
702
|
|
|
""" Retrieve the debug information for the current hardware. |
|
703
|
|
|
|
|
704
|
|
|
@return char[32568]: the information for debugging. |
|
705
|
|
|
""" |
|
706
|
|
|
debuginfo = ctypes.create_string_buffer(32568) # buffer at least 16284 byte |
|
707
|
|
|
self.check(self._dll.PH_GetHardwareDebugInfo(self._deviceID, debuginfo)) |
|
708
|
|
|
return debuginfo.value |
|
709
|
|
|
|
|
710
|
|
|
# ========================================================================= |
|
711
|
|
|
# Special functions for Time-Tagged Time Resolved mode |
|
712
|
|
|
# ========================================================================= |
|
713
|
|
|
# To check whether you can use the TTTR mode (must be purchased in |
|
714
|
|
|
# addition) you can call PH_GetFeatures to check. |
|
715
|
|
|
|
|
716
|
|
|
def tttr_read_fifo(self):#, num_counts): |
|
717
|
|
|
""" Read out the buffer of the FIFO. |
|
718
|
|
|
|
|
719
|
|
|
@param int num_counts: number of TTTR records to be fetched. Maximal |
|
720
|
|
|
TTREADMAX |
|
721
|
|
|
|
|
722
|
|
|
@return tuple (buffer, actual_num_counts): |
|
723
|
|
|
buffer = data array where the TTTR data are stored. |
|
724
|
|
|
actual_num_counts = how many numbers of TTTR could be |
|
725
|
|
|
actually be read out. THIS NUMBER IS |
|
726
|
|
|
NOT CHECKED FOR PERFORMANCE REASONS, SO |
|
727
|
|
|
BE CAREFUL! Maximum is TTREADMAX. |
|
728
|
|
|
|
|
729
|
|
|
THIS FUNCTION SHOULD BE CALLED IN A SEPARATE THREAD! |
|
730
|
|
|
|
|
731
|
|
|
Must not be called with count larger than buffer size permits. CPU time |
|
732
|
|
|
during wait for completion will be yielded to other processes/threads. |
|
733
|
|
|
Function will return after a timeout period of 80 ms even if not all |
|
734
|
|
|
data could be fetched. Return value indicates how many records were |
|
735
|
|
|
fetched. Buffer must not be accessed until the function returns! |
|
736
|
|
|
""" |
|
737
|
|
|
|
|
738
|
|
|
# if type(num_counts) is not int: |
|
739
|
|
|
# num_counts = self.TTREADMAX |
|
740
|
|
|
# elif (num_counts<0) or (num_counts>self.TTREADMAX): |
|
741
|
|
|
# self.log.error('PicoHarp: num_counts were expected to within the ' |
|
742
|
|
|
# 'interval [0,{0}], but a value of {1} was ' |
|
743
|
|
|
# 'passed'.format(self.TTREADMAX, num_counts)) |
|
744
|
|
|
# num_counts = self.TTREADMAX |
|
745
|
|
|
|
|
746
|
|
|
# PicoHarp T3 Format (for analysis and interpretation): |
|
747
|
|
|
# The bit allocation in the record for the 32bit event is, starting |
|
748
|
|
|
# from the MSB: |
|
749
|
|
|
# channel: 4 bit |
|
750
|
|
|
# dtime: 12 bit |
|
751
|
|
|
# nsync: 16 bit |
|
752
|
|
|
# The channel code 15 (all bits ones) marks a special record. |
|
753
|
|
|
# Special records can be overflows or external markers. To |
|
754
|
|
|
# differentiate this, dtime must be checked: |
|
755
|
|
|
# |
|
756
|
|
|
# If it is zero, the record marks an overflow. |
|
757
|
|
|
# If it is >=1 the individual bits are external markers. |
|
758
|
|
|
|
|
759
|
|
|
num_counts = self.TTREADMAX |
|
760
|
|
|
|
|
761
|
|
|
buffer = np.zeros((num_counts,), dtype=np.uint32) |
|
762
|
|
|
|
|
763
|
|
|
actual_num_counts = ctypes.c_int32() |
|
764
|
|
|
|
|
765
|
|
|
self.check(self._dll.PH_ReadFiFo(self._deviceID, buffer.ctypes.data, |
|
766
|
|
|
num_counts, ctypes.byref(actual_num_counts))) |
|
767
|
|
|
|
|
768
|
|
|
|
|
769
|
|
|
return (buffer, actual_num_counts.value) |
|
770
|
|
|
|
|
771
|
|
|
def tttr_set_marker_edges(self, me0, me1, me2, me3): |
|
772
|
|
|
""" Set the marker edges |
|
773
|
|
|
|
|
774
|
|
|
@param int me<n>: active edge of marker signal <n>, |
|
775
|
|
|
0 = falling |
|
776
|
|
|
1 = rising |
|
777
|
|
|
|
|
778
|
|
|
PicoHarp devices prior to hardware version 2.0 support only the first |
|
779
|
|
|
three markers. Default after Initialize is all rising, i.e. set to 1. |
|
780
|
|
|
""" |
|
781
|
|
|
|
|
782
|
|
|
if (me0 != 0) or (me0 != 1) or (me1 != 0) or (me1 != 1) or \ |
|
783
|
|
|
(me2 != 0) or (me2 != 1) or (me3 != 0) or (me3 != 1): |
|
784
|
|
|
|
|
785
|
|
|
self.log.error('PicoHarp: All the marker edges must be either 0 ' |
|
786
|
|
|
'or 1, but the current marker settings were passed:\n' |
|
787
|
|
|
'me0={0}, me1={1}, ' |
|
788
|
|
|
'me2={2}, me3={3},'.format(me0, me1, me2, me3)) |
|
789
|
|
|
return |
|
790
|
|
|
else: |
|
791
|
|
|
self.check(self._dll.PH_TTSetMarkerEdges(self._deviceID, me0, me1, |
|
792
|
|
|
me2, me3)) |
|
793
|
|
|
|
|
794
|
|
|
def tttr_set_marker_enable(self, me0, me1, me2, me3): |
|
795
|
|
|
""" Set the marker enable or not. |
|
796
|
|
|
|
|
797
|
|
|
@param int me<n>: enabling of marker signal <n>, |
|
798
|
|
|
0 = disabled |
|
799
|
|
|
1 = enabled |
|
800
|
|
|
|
|
801
|
|
|
PicoHarp devices prior to hardware version 2.0 support only the first |
|
802
|
|
|
three markers. Default after Initialize is all rising, i.e. set to 1. |
|
803
|
|
|
""" |
|
804
|
|
|
|
|
805
|
|
|
# if ((me0 != 0) or (me0 != 1)) or ((me1 != 0) or (me1 != 1)) or \ |
|
806
|
|
|
# ((me2 != 0) or (me2 != 1)) or ((me3 != 0) or (me3 != 1)): |
|
807
|
|
|
# |
|
808
|
|
|
# self.log.error('PicoHarp: Could not set marker enable.\n' |
|
809
|
|
|
# 'All the marker options must be either 0 or 1, but ' |
|
810
|
|
|
# 'the current marker settings were passed:\n' |
|
811
|
|
|
# 'me0={0}, me1={1}, ' |
|
812
|
|
|
# 'me2={2}, me3={3},'.format(me0, me1, me2, me3)) |
|
813
|
|
|
# return |
|
814
|
|
|
# else: |
|
815
|
|
|
self.check(self._dll.PH_SetMarkerEnable(self._deviceID, me0, |
|
816
|
|
|
me1, me2, me3)) |
|
817
|
|
|
|
|
818
|
|
|
def tttr_set_marker_holdofftime(self, holfofftime): |
|
819
|
|
|
""" Set the holdofftime for the markers. |
|
820
|
|
|
|
|
821
|
|
|
@param int holdofftime: holdofftime in ns. Maximal value is HOLDOFFMAX. |
|
822
|
|
|
|
|
823
|
|
|
This setting can be used to clean up glitches on the marker signals. |
|
824
|
|
|
When set to X ns then after detecting a first marker edge the next |
|
825
|
|
|
marker will not be accepted before x ns. Observe that the internal |
|
826
|
|
|
granularity of this time is only about 50ns. The holdoff time is set |
|
827
|
|
|
equally for all marker inputs but the holdoff logic acts on each |
|
828
|
|
|
marker independently. |
|
829
|
|
|
""" |
|
830
|
|
|
|
|
831
|
|
|
if not(0 <= holdofftime <= self.HOLDOFFMAX): |
|
832
|
|
|
self.log.error('PicoHarp: Holdofftime could not be set.\n' |
|
833
|
|
|
'Value of holdofftime must be within the range ' |
|
834
|
|
|
'[0,{0}], but a value of {1} was passed.' |
|
835
|
|
|
''.format(self.HOLDOFFMAX, holfofftime)) |
|
836
|
|
|
else: |
|
837
|
|
|
self.check(self._dll.PH_SetMarkerHoldofftime(self._deviceID, holfofftime)) |
|
838
|
|
|
|
|
839
|
|
|
# ========================================================================= |
|
840
|
|
|
# Special functions for Routing Devices |
|
841
|
|
|
# ========================================================================= |
|
842
|
|
|
# If this functions wanted to be used, then you have to use the current |
|
843
|
|
|
# PicoHarp300 with a router device like PHR 402, PHR 403 or PHR 800. |
|
844
|
|
|
|
|
845
|
|
|
def get_routing_channels(self): |
|
846
|
|
|
""" Retrieve the number of routing channels. |
|
847
|
|
|
|
|
848
|
|
|
@param return int: The number of possible routing_channels. |
|
849
|
|
|
""" |
|
850
|
|
|
routing_channels = ctypes.c_int32() |
|
851
|
|
|
self.check(self._dll.PH_GetRoutingChannels( |
|
852
|
|
|
self._deviceID, ctypes.byref(routing_channels))) |
|
853
|
|
|
return routing_channels.value |
|
854
|
|
|
|
|
855
|
|
|
def set_enable_routing(self, use_router): |
|
856
|
|
|
""" Configure whether the connected router is used or not. |
|
857
|
|
|
|
|
858
|
|
|
@param int use_router: 0 = enable routing |
|
859
|
|
|
1 = disable routing |
|
860
|
|
|
|
|
861
|
|
|
Note: This function can also be used to detect the presence of a router! |
|
862
|
|
|
""" |
|
863
|
|
|
|
|
864
|
|
|
return self.check(self._dll.PH_EnableRouting(self._deviceID, use_router)) |
|
865
|
|
|
|
|
866
|
|
|
def get_router_version(self): |
|
867
|
|
|
""" Retrieve the model number and the router version. |
|
868
|
|
|
|
|
869
|
|
|
@return string list[2]: first entry will be the model number and second |
|
870
|
|
|
entry the router version. |
|
871
|
|
|
""" |
|
872
|
|
|
# pointer to a buffer for at least 8 characters: |
|
873
|
|
|
model_number = ctypes.create_string_buffer(16) |
|
874
|
|
|
version_number = ctypes.create_string_buffer(16) |
|
875
|
|
|
|
|
876
|
|
|
self.check(self._dll.PH_GetRouterVersion(self._deviceID, |
|
877
|
|
|
ctypes.byref(model_number), |
|
878
|
|
|
ctypes.byref(version_number))) |
|
879
|
|
|
|
|
880
|
|
|
return [model_number.value.decode(), version_number.value.decode()] |
|
881
|
|
|
|
|
882
|
|
|
def set_routing_channel_offset(self, offset_time): |
|
883
|
|
|
""" Set the offset for the routed channels to compensate cable delay. |
|
884
|
|
|
|
|
885
|
|
|
@param int offset_time: offset (time shift) in ps for that channel. |
|
886
|
|
|
Value must be within [OFFSETMIN,OFFSETMAX] |
|
887
|
|
|
|
|
888
|
|
|
Note: This function can be used to compensate small timing delays |
|
889
|
|
|
between the individual routing channels. It is similar to |
|
890
|
|
|
PH_SetSyncOffset and can replace cumbersome cable length |
|
891
|
|
|
adjustments but compared to PH_SetSyncOffset the adjustment range |
|
892
|
|
|
is relatively small. A positive number corresponds to inserting |
|
893
|
|
|
cable in that channel. |
|
894
|
|
|
""" |
|
895
|
|
|
|
|
896
|
|
|
if not(self.OFFSETMIN <= offset_time <= self.OFFSETMAX): |
|
897
|
|
|
self.log.error('PicoHarp: Invalid offset time for routing.\nThe ' |
|
898
|
|
|
'offset time was expected to be within the interval ' |
|
899
|
|
|
'[{0},{1}] ps, but a value of {2} was passed.' |
|
900
|
|
|
''.format(self.OFFSETMIN, self.OFFSETMAX, offset_time)) |
|
901
|
|
|
return |
|
902
|
|
|
else: |
|
903
|
|
|
self.check(self._dll.PH_SetRoutingChannelOffset(self._deviceID, offset_time)) |
|
904
|
|
|
|
|
905
|
|
|
def set_phr800_input(self, channel, level, edge): |
|
906
|
|
|
""" Configure the input channels of the PHR800 device. |
|
907
|
|
|
|
|
908
|
|
|
@param int channel: which router channel is going to be programmed. |
|
909
|
|
|
This number but be within the range [0,3]. |
|
910
|
|
|
@param int level: set the trigger voltage level in mV. The entered |
|
911
|
|
|
value must be within [PHR800LVMIN,PHR800LVMAX]. |
|
912
|
|
|
@param int edge: Specify whether the trigger should be detected on |
|
913
|
|
|
0 = falling edge or |
|
914
|
|
|
1 = rising edge. |
|
915
|
|
|
|
|
916
|
|
|
Note: Not all channels my be present! |
|
917
|
|
|
Note: INVALID COMBINATIONS OF LEVEL AND EDGES MAY LOOK UP ALL CHANNELS! |
|
918
|
|
|
""" |
|
919
|
|
|
|
|
920
|
|
|
channel = int(channel) |
|
921
|
|
|
level = int(level) |
|
922
|
|
|
edge = int(edge) |
|
923
|
|
|
|
|
924
|
|
|
if channel not in range(0, 4): |
|
925
|
|
|
self.log.error('PicoHarp: Invalid channel for routing.\n' |
|
926
|
|
|
'The channel must be within the interval [0,3], but a value ' |
|
927
|
|
|
'of {2} was passed.'.format(channel)) |
|
928
|
|
|
return |
|
929
|
|
|
if not(self.PHR800LVMIN <= level <= self.PHR800LVMAX): |
|
930
|
|
|
self.log.error('PicoHarp: Invalid level for routing.\n' |
|
931
|
|
|
'The level used for channel {0} must be within the interval ' |
|
932
|
|
|
'[{1},{2}] mV, but a value of {3} was passed.' |
|
933
|
|
|
''.format(channel, self.PHR800LVMIN, self.PHR800LVMAX, level)) |
|
934
|
|
|
return |
|
935
|
|
|
if (edge != 0) or (edge != 1): |
|
936
|
|
|
self.log.error('PicoHarp: Could not set edge.\n' |
|
937
|
|
|
'The edge setting must be either 0 or 1, but the ' |
|
938
|
|
|
'current edge value {0} was ' |
|
939
|
|
|
'passed'.format(edge)) |
|
940
|
|
|
return |
|
941
|
|
|
|
|
942
|
|
|
self.check(self._dll.PH_SetPHR800Input(self._deviceID, channel, level, edge)) |
|
943
|
|
|
|
|
944
|
|
View Code Duplication |
def set_phr800_cfd(self, channel, dscrlevel, zerocross): |
|
|
|
|
|
|
945
|
|
|
""" Set the Constant Fraction Discriminators (CFD) for the PHR800 device. |
|
946
|
|
|
|
|
947
|
|
|
@param int channel: which router channel is going to be programmed. |
|
948
|
|
|
This number but be within the range [0,3]. |
|
949
|
|
|
@param dscrlevel: the discriminator level in mV, which must be within a |
|
950
|
|
|
range of [DISCRMIN,DISCRMAX] |
|
951
|
|
|
""" |
|
952
|
|
|
|
|
953
|
|
|
channel = int(channel) |
|
954
|
|
|
dscrlevel = int(dscrlevel) |
|
955
|
|
|
zerocross = int(zerocross) |
|
956
|
|
|
|
|
957
|
|
|
if channel not in range(0, 4): |
|
958
|
|
|
self.log.error('PicoHarp: Invalid channel for routing.\nThe ' |
|
959
|
|
|
'channel must be within the interval [0,3], but a value ' |
|
960
|
|
|
'of {2} has been passed.'.format(channel)) |
|
961
|
|
|
return |
|
962
|
|
|
if not(self.DISCRMIN <= dscrlevel <= self.DISCRMAX): |
|
963
|
|
|
self.log.error('PicoHarp: Invalid Constant Fraction Discriminators ' |
|
964
|
|
|
'level.\nValue must be within the range [{0},{1}] ' |
|
965
|
|
|
' millivolts but a value of {2} has been ' |
|
966
|
|
|
'passed.'.format(self.DISCRMIN, self.DISCRMAX, dscrlevel)) |
|
967
|
|
|
return |
|
968
|
|
|
if not(self.ZCMIN <= zerocross <= self.ZCMAX): |
|
969
|
|
|
self.log.error('PicoHarp: Invalid CFD zero cross.\nValue must be ' |
|
970
|
|
|
'within the range [{0},{1}] millivolts but a value of ' |
|
971
|
|
|
'{2} has been ' |
|
972
|
|
|
'passed.'.format(self.ZCMIN, self.ZCMAX, zerocross)) |
|
973
|
|
|
return |
|
974
|
|
|
|
|
975
|
|
|
self.check(self._dll.PH_SetPHR800CFD(self._deviceID, channel, dscrlevel, zerocross)) |
|
976
|
|
|
|
|
977
|
|
|
# ========================================================================= |
|
978
|
|
|
# Higher Level function, which should be called directly from Logic |
|
979
|
|
|
# ========================================================================= |
|
980
|
|
|
|
|
981
|
|
|
|
|
982
|
|
|
# ========================================================================= |
|
983
|
|
|
# Functions for the SlowCounter Interface |
|
984
|
|
|
# ========================================================================= |
|
985
|
|
|
|
|
986
|
|
|
def set_up_clock(self, clock_frequency = None, clock_channel = None): |
|
987
|
|
|
""" Set here which channel you want to access of the Picoharp. |
|
988
|
|
|
|
|
989
|
|
|
@param float clock_frequency: Sets the frequency of the clock. That |
|
990
|
|
|
frequency will not be taken. It is not |
|
991
|
|
|
needed, and argument will be omitted. |
|
992
|
|
|
@param string clock_channel: This is the physical channel |
|
993
|
|
|
of the clock. It is not needed, and |
|
994
|
|
|
argument will be omitted. |
|
995
|
|
|
|
|
996
|
|
|
The Hardware clock for the Picoharp is not programmable. It is a gated |
|
997
|
|
|
counter every 100ms. That you cannot change. You can retrieve from both |
|
998
|
|
|
channels simultaneously the count rates. |
|
999
|
|
|
|
|
1000
|
|
|
@return int: error code (0:OK, -1:error) |
|
1001
|
|
|
""" |
|
1002
|
|
|
self.log.info('Picoharp: The Hardware clock for the Picoharp is not ' |
|
1003
|
|
|
'programmable!\n' |
|
1004
|
|
|
'It is a gated counter every 100ms. That you cannot change. ' |
|
1005
|
|
|
'You can retrieve from both channels simultaneously the ' |
|
1006
|
|
|
'count rates.') |
|
1007
|
|
|
|
|
1008
|
|
|
return 0 |
|
1009
|
|
|
|
|
1010
|
|
|
def set_up_counter(self, counter_channel = 1, photon_source = None, |
|
1011
|
|
|
clock_channel = None): |
|
1012
|
|
|
""" Ensure Interface compatibility. The counter allows no set up. |
|
1013
|
|
|
|
|
1014
|
|
|
@param string counter_channel: Set the actual channel which you want to |
|
1015
|
|
|
read out. Default it is 0. It can |
|
1016
|
|
|
also be 1. |
|
1017
|
|
|
@param string photon_source: is not needed, arg will be omitted. |
|
1018
|
|
|
@param string clock_channel: is not needed, arg will be omitted. |
|
1019
|
|
|
|
|
1020
|
|
|
@return int: error code (0:OK, -1:error) |
|
1021
|
|
|
""" |
|
1022
|
|
|
self._count_channel = counter_channel |
|
1023
|
|
|
self.log.info('Picoharp: The counter allows no set up!\n' |
|
1024
|
|
|
'The implementation of this command ensures Interface ' |
|
1025
|
|
|
'compatibility.') |
|
1026
|
|
|
|
|
1027
|
|
|
#FIXME: make the counter channel chooseable in config |
|
1028
|
|
|
#FIXME: add second photon source either to config or in a better way to file |
|
1029
|
|
|
return 0 |
|
1030
|
|
|
|
|
1031
|
|
|
def get_counter(self, samples=None): |
|
1032
|
|
|
""" Returns the current counts per second of the counter. |
|
1033
|
|
|
|
|
1034
|
|
|
@param int samples: if defined, number of samples to read in one go |
|
1035
|
|
|
|
|
1036
|
|
|
@return float: the photon counts per second |
|
1037
|
|
|
""" |
|
1038
|
|
|
time.sleep(0.05) |
|
1039
|
|
|
return [self.get_count_rate(self._count_channel)] |
|
1040
|
|
|
|
|
1041
|
|
|
def close_counter(self): |
|
1042
|
|
|
""" Closes the counter and cleans up afterwards. Actually, you do not |
|
1043
|
|
|
have to do anything with the picoharp. Therefore this command will do |
|
1044
|
|
|
nothing and is only here for SlowCounterInterface compatibility. |
|
1045
|
|
|
|
|
1046
|
|
|
@return int: error code (0:OK, -1:error) |
|
1047
|
|
|
""" |
|
1048
|
|
|
return 0 |
|
1049
|
|
|
|
|
1050
|
|
|
def close_clock(self): |
|
1051
|
|
|
"""Closes the clock and cleans up afterwards.. Actually, you do not |
|
1052
|
|
|
have to do anything with the picoharp. Therefore this command will do |
|
1053
|
|
|
nothing and is only here for SlowCounterInterface compatibility. |
|
1054
|
|
|
|
|
1055
|
|
|
@return int: error code (0:OK, -1:error) |
|
1056
|
|
|
""" |
|
1057
|
|
|
return 0 |
|
1058
|
|
|
|
|
1059
|
|
|
# ========================================================================= |
|
1060
|
|
|
# Functions for the FastCounter Interface |
|
1061
|
|
|
# ========================================================================= |
|
1062
|
|
|
|
|
1063
|
|
|
#FIXME: The interface connection to the fast counter must be established! |
|
1064
|
|
|
|
|
1065
|
|
|
def configure(self, bin_width_ns, record_length_ns, number_of_gates = 0): |
|
1066
|
|
|
""" |
|
1067
|
|
|
Configuration of the fast counter. |
|
1068
|
|
|
bin_width_ns: Length of a single time bin in the time trace histogram |
|
1069
|
|
|
in nanoseconds. |
|
1070
|
|
|
record_length_ns: Total length of the timetrace/each single gate in |
|
1071
|
|
|
nanoseconds. |
|
1072
|
|
|
number_of_gates: Number of gates in the pulse sequence. Ignore for |
|
1073
|
|
|
ungated counter. |
|
1074
|
|
|
""" |
|
1075
|
|
|
# self.initialize(mode=3) |
|
1076
|
|
|
self._bin_width_ns = bin_width_ns |
|
1077
|
|
|
self._record_length_ns = record_length_ns |
|
1078
|
|
|
self._number_of_gates = number_of_gates |
|
1079
|
|
|
|
|
1080
|
|
|
#FIXME: actualle only an unsigned array will be needed. Change that later. |
|
1081
|
|
|
# self.data_trace = np.zeros(number_of_gates, dtype=np.int64 ) |
|
1082
|
|
|
self.data_trace = [0]*number_of_gates |
|
1083
|
|
|
self.count = 0 |
|
1084
|
|
|
|
|
1085
|
|
|
self.result = [] |
|
1086
|
|
|
self.initialize(2) |
|
1087
|
|
|
return |
|
1088
|
|
|
|
|
1089
|
|
|
def get_status(self): |
|
1090
|
|
|
""" |
|
1091
|
|
|
Receives the current status of the Fast Counter and outputs it as |
|
1092
|
|
|
return value. |
|
1093
|
|
|
0 = unconfigured |
|
1094
|
|
|
1 = idle |
|
1095
|
|
|
2 = running |
|
1096
|
|
|
3 = paused |
|
1097
|
|
|
-1 = error state |
|
1098
|
|
|
""" |
|
1099
|
|
|
if not self.connected_to_device: |
|
1100
|
|
|
return -1 |
|
1101
|
|
|
else: |
|
1102
|
|
|
returnvalue = self._get_status() |
|
1103
|
|
|
if returnvalue == 0: |
|
1104
|
|
|
return 2 |
|
1105
|
|
|
else: |
|
1106
|
|
|
return 1 |
|
1107
|
|
|
|
|
1108
|
|
|
|
|
1109
|
|
|
def pause_measure(self): |
|
1110
|
|
|
""" |
|
1111
|
|
|
Pauses the current measurement if the fast counter is in running state. |
|
1112
|
|
|
""" |
|
1113
|
|
|
|
|
1114
|
|
|
self.stop_measure() |
|
1115
|
|
|
self.meas_run = False |
|
1116
|
|
|
|
|
1117
|
|
|
def continue_measure(self): |
|
1118
|
|
|
""" |
|
1119
|
|
|
Continues the current measurement if the fast counter is in pause state. |
|
1120
|
|
|
""" |
|
1121
|
|
|
self.meas_run = True |
|
1122
|
|
|
self.start(self._record_length_ns/1e6) |
|
1123
|
|
|
|
|
1124
|
|
|
def is_gated(self): |
|
1125
|
|
|
""" |
|
1126
|
|
|
Boolean return value indicates if the fast counter is a gated counter |
|
1127
|
|
|
(TRUE) or not (FALSE). |
|
1128
|
|
|
""" |
|
1129
|
|
|
return False |
|
1130
|
|
|
|
|
1131
|
|
|
def get_binwidth(self): |
|
1132
|
|
|
""" |
|
1133
|
|
|
returns the width of a single timebin in the timetrace in seconds |
|
1134
|
|
|
""" |
|
1135
|
|
|
#FIXME: Must be implemented |
|
1136
|
|
|
return 2e-9 |
|
1137
|
|
|
|
|
1138
|
|
|
def get_data_trace(self): |
|
1139
|
|
|
""" |
|
1140
|
|
|
Polls the current timetrace data from the fast counter and returns it |
|
1141
|
|
|
as a numpy array (dtype = int64). The binning specified by calling |
|
1142
|
|
|
configure() must be taken care of in this hardware class. A possible |
|
1143
|
|
|
overflow of the histogram bins must be caught here and taken care of. |
|
1144
|
|
|
- If the counter is NOT gated it will return a 1D-numpy-array with |
|
1145
|
|
|
returnarray[timebin_index]. |
|
1146
|
|
|
- If the counter is gated it will return a 2D-numpy-array with |
|
1147
|
|
|
returnarray[gate_index, timebin_index] |
|
1148
|
|
|
""" |
|
1149
|
|
|
|
|
1150
|
|
|
|
|
1151
|
|
|
return self.data_trace |
|
1152
|
|
|
|
|
1153
|
|
|
|
|
1154
|
|
|
|
|
1155
|
|
|
# ========================================================================= |
|
1156
|
|
|
# Test routine for continuous readout |
|
1157
|
|
|
# ========================================================================= |
|
1158
|
|
|
|
|
1159
|
|
|
|
|
1160
|
|
|
def start_measure(self): |
|
1161
|
|
|
""" |
|
1162
|
|
|
Starts the fast counter. |
|
1163
|
|
|
""" |
|
1164
|
|
|
self.lock() |
|
1165
|
|
|
|
|
1166
|
|
|
self.meas_run = True |
|
1167
|
|
|
|
|
1168
|
|
|
# start the device: |
|
1169
|
|
|
self.start(int(self._record_length_ns/1e6)) |
|
1170
|
|
|
|
|
1171
|
|
|
self.sigReadoutPicoharp.emit() |
|
1172
|
|
|
|
|
1173
|
|
|
def stop_measure(self): |
|
1174
|
|
|
""" By setting the Flag, the measurement should stop. """ |
|
1175
|
|
|
self.meas_run = False |
|
1176
|
|
|
|
|
1177
|
|
|
|
|
1178
|
|
|
def get_fresh_data_loop(self): |
|
1179
|
|
|
""" This method will be run infinitely until the measurement stops. """ |
|
1180
|
|
|
|
|
1181
|
|
|
# for testing one can also take another array: |
|
1182
|
|
|
buffer, actual_counts = self.tttr_read_fifo() |
|
1183
|
|
|
# buffer, actual_counts = [1,2,3,4,5,6,7,8,9], 9 |
|
1184
|
|
|
|
|
1185
|
|
|
# This analysis signel should be analyzed in a queued thread: |
|
1186
|
|
|
self.sigAnalyzeData.emit(buffer[0:actual_counts-1], actual_counts) |
|
1187
|
|
|
|
|
1188
|
|
|
if not self.meas_run: |
|
1189
|
|
|
with self.threadlock: |
|
1190
|
|
|
self.unlock() |
|
1191
|
|
|
self.stop_device |
|
1192
|
|
|
return |
|
1193
|
|
|
|
|
1194
|
|
|
print('get new data.') |
|
1195
|
|
|
# get the next data: |
|
1196
|
|
|
self.sigReadoutPicoharp.emit() |
|
1197
|
|
|
|
|
1198
|
|
|
|
|
1199
|
|
|
|
|
1200
|
|
|
def analyze_received_data(self, arr_data, actual_counts): |
|
1201
|
|
|
""" Analyze the actual data obtained from the TTTR mode of the device. |
|
1202
|
|
|
|
|
1203
|
|
|
@param arr_data: numpy uint32 array with length 'actual_counts'. |
|
1204
|
|
|
@param actual_counts: int, number of read out events from the buffer. |
|
1205
|
|
|
|
|
1206
|
|
|
Write the obtained arr_data to the predefined array data_trace, |
|
1207
|
|
|
initialized in the configure method. |
|
1208
|
|
|
|
|
1209
|
|
|
The received array contains 32bit words. The bit assignment starts from |
|
1210
|
|
|
the MSB (most significant bit), which is here displayed as the most |
|
1211
|
|
|
left bit. |
|
1212
|
|
|
|
|
1213
|
|
|
For T2 (initialized device with mode=2): |
|
1214
|
|
|
---------------------------------------- |
|
1215
|
|
|
|
|
1216
|
|
|
[ 4 bit for channel-number |28 bit for time-tag] = [32 bit word] |
|
1217
|
|
|
|
|
1218
|
|
|
channel-number: 4 marker, which serve for the different channels. |
|
1219
|
|
|
0001 = marker 1 |
|
1220
|
|
|
0010 = marker 2 |
|
1221
|
|
|
0011 = marker 3 |
|
1222
|
|
|
0100 = marker 4 |
|
1223
|
|
|
|
|
1224
|
|
|
The channel code 15 (all bits ones, 1111) marks a |
|
1225
|
|
|
special record. Special records can be overflows or |
|
1226
|
|
|
external markers. To differentiate this, the lower 4 |
|
1227
|
|
|
bits of timetag must be checked: |
|
1228
|
|
|
- If they are all zero, the record marks an |
|
1229
|
|
|
overflow. |
|
1230
|
|
|
- If they are >=1 the individual bits are external |
|
1231
|
|
|
markers. |
|
1232
|
|
|
|
|
1233
|
|
|
Overflow period: 210698240 |
|
1234
|
|
|
|
|
1235
|
|
|
the first bit is the overflow bit. It will be set if |
|
1236
|
|
|
the time-tag reached 2^28: |
|
1237
|
|
|
|
|
1238
|
|
|
0000 = overflow |
|
1239
|
|
|
|
|
1240
|
|
|
Afterwards both overflow marker and time-tag |
|
1241
|
|
|
will be reseted. This overflow should be detected and |
|
1242
|
|
|
the time axis should be adjusted accordingly. |
|
1243
|
|
|
|
|
1244
|
|
|
time-tag: The resolution is fixed to 4ps. Within the time of |
|
1245
|
|
|
4ps*2^28 = 1.073741824 ms |
|
1246
|
|
|
another photon event should occur so that the time axis can |
|
1247
|
|
|
be computed properly. |
|
1248
|
|
|
|
|
1249
|
|
|
For T3 (initialized device with mode=3): |
|
1250
|
|
|
---------------------------------------- |
|
1251
|
|
|
|
|
1252
|
|
|
[ 4 bit for channel-number | 12 bit for start-stop-time | 16 bit for sync counter] = [32 bit word] |
|
1253
|
|
|
|
|
1254
|
|
|
channel-number: 4 marker, which serve for the different channels. |
|
1255
|
|
|
0001 = marker 1 |
|
1256
|
|
|
0010 = marker 2 |
|
1257
|
|
|
0011 = marker 3 |
|
1258
|
|
|
0100 = marker 4 |
|
1259
|
|
|
|
|
1260
|
|
|
the first bit is the overflow bit. It will be set if |
|
1261
|
|
|
the sync-counter reached 65536 events: |
|
1262
|
|
|
|
|
1263
|
|
|
1000 = overflow |
|
1264
|
|
|
|
|
1265
|
|
|
Afterwards both, overflow marker and sync-counter |
|
1266
|
|
|
will be reseted. This overflow should be detected and |
|
1267
|
|
|
the time axis should be adjusted accordingly. |
|
1268
|
|
|
|
|
1269
|
|
|
start-stop-time: time between to consecutive sync pulses. Maximal time |
|
1270
|
|
|
between two sync pulses is therefore limited to |
|
1271
|
|
|
2^12 * Res |
|
1272
|
|
|
where Res is the Resolution |
|
1273
|
|
|
Res = {4,8,16,32,54,128,256,512} (in ps) |
|
1274
|
|
|
For largest Resolution of 512ps you have 2097.152 ns. |
|
1275
|
|
|
sync-counter: can hold up to 2^16 = 65536 events. It that number is |
|
1276
|
|
|
reached overflow will be set. That means all 4 bits in |
|
1277
|
|
|
the channel-number are set to high (i.e. 1). |
|
1278
|
|
|
""" |
|
1279
|
|
|
|
|
1280
|
|
|
# at first just a simple test |
|
1281
|
|
|
time.sleep(0.2) |
|
1282
|
|
|
|
|
1283
|
|
|
self.data_trace[self.count] = actual_counts |
|
1284
|
|
|
self.count += 1 |
|
1285
|
|
|
|
|
1286
|
|
|
if self.count > self._number_of_gates-1: |
|
1287
|
|
|
self.count = 0 |
|
1288
|
|
|
|
|
1289
|
|
|
if actual_counts == self.TTREADMAX: |
|
1290
|
|
|
self.log.warning('Overflow!') |
|
1291
|
|
|
|
|
1292
|
|
|
print('Data analyzed.') |
|
1293
|
|
|
|
|
1294
|
|
|
# self.result = [] |
|
1295
|
|
|
# for entry in arr_data[0:actual_counts-1]: |
|
1296
|
|
|
# |
|
1297
|
|
|
# # apply three bitmasks to extract the relavent numbers: |
|
1298
|
|
|
# overflow = entry & (2**(32-1) ) |
|
1299
|
|
|
# marker_ch = entry & (2**(32-2) + 2**(32-3) + 2**(32-4)) |
|
1300
|
|
|
# time_tag = entry & (2**32 -1 - 2**(32-1) + 2**(32-2) + 2**(32-3) + 2**(32-4)) |
|
1301
|
|
|
|
|
1302
|
|
|
|
|
1303
|
|
|
|
|
1304
|
|
|
|
|
1305
|
|
|
|
|
1306
|
|
|
|
|
1307
|
|
|
|
|
1308
|
|
|
|
|
1309
|
|
|
|
|
1310
|
|
|
|
|
1311
|
|
|
|
|
1312
|
|
|
|
|
1313
|
|
|
|