Completed
Push — master ( 40b1cc...546dfb )
by
unknown
15s
created

FastComtec.set_cycles()   A

Complexity

Conditions 1

Size

Total Lines 10

Duplication

Lines 7
Ratio 70 %

Importance

Changes 0
Metric Value
cc 1
dl 7
loc 10
rs 9.4285
c 0
b 0
f 0
1
# -*- coding: utf-8 -*-
2
3
"""
4
This file contains the Qudi hardware file implementation for FastComtec p7887 .
5
6
Qudi is free software: you can redistribute it and/or modify
7
it under the terms of the GNU General Public License as published by
8
the Free Software Foundation, either version 3 of the License, or
9
(at your option) any later version.
10
11
Qudi is distributed in the hope that it will be useful,
12
but WITHOUT ANY WARRANTY; without even the implied warranty of
13
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
GNU General Public License for more details.
15
16
You should have received a copy of the GNU General Public License
17
along with Qudi. If not, see <http://www.gnu.org/licenses/>.
18
19
Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the
20
top-level directory of this distribution and at <https://github.com/Ulm-IQO/qudi/>
21
"""
22
23
#TODO: start stop works but pause does not work, i guess gui/logic problem
24
#TODO: Check if there are more modules which are missing, and more settings for FastComtec which need to be put, should we include voltage threshold?
25
26
27
28
from core.module import Base, ConfigOption
29
from core.util.modules import get_main_dir
30
from interface.fast_counter_interface import FastCounterInterface
31
import time
32
import os
33
import numpy as np
34
import ctypes
35
36
37
"""
38
Remark to the usage of ctypes:
39
All Python types except integers (int), strings (str), and bytes (byte) objects
40
have to be wrapped in their corresponding ctypes type, so that they can be
41
converted to the required C data type.
42
43
ctypes type     C type                  Python type
44
----------------------------------------------------------------
45
c_bool          _Bool                   bool (1)
46
c_char          char                    1-character bytes object
47
c_wchar         wchar_t                 1-character string
48
c_byte          char                    int
49
c_ubyte         unsigned char           int
50
c_short         short                   int
51
c_ushort        unsigned short          int
52
c_int           int                     int
53
c_uint          unsigned int            int
54
c_long          long                    int
55
c_ulong         unsigned long           int
56
c_longlong      __int64 or
57
                long long               int
58
c_ulonglong     unsigned __int64 or
59
                unsigned long long      int
60
c_size_t        size_t                  int
61
c_ssize_t       ssize_t or
62
                Py_ssize_t              int
63
c_float         float                   float
64
c_double        double                  float
65
c_longdouble    long double             float
66
c_char_p        char *
67
                (NUL terminated)        bytes object or None
68
c_wchar_p       wchar_t *
69
                (NUL terminated)        string or None
70
c_void_p        void *                  int or None
71
72
"""
73
# Reconstruct the proper structure of the variables, which can be extracted
74
# from the header file 'struct.h'.
75
76
class AcqStatus(ctypes.Structure):
77
    """ Create a structured Data type with ctypes where the dll can write into.
78
79
    This object handles and retrieves the acquisition status data from the
80
    Fastcomtec.
81
82
    int started;                // acquisition status: 1 if running, 0 else
83
    double runtime;             // running time in seconds
84
    double totalsum;            // total events
85
    double roisum;              // events within ROI
86
    double roirate;             // acquired ROI-events per second
87
    double nettosum;            // ROI sum with background subtracted
88
    double sweeps;              // Number of sweeps
89
    double stevents;            // Start Events
90
    unsigned long maxval;       // Maximum value in spectrum
91
    """
92
    _fields_ = [('started', ctypes.c_int),
93
                ('runtime', ctypes.c_double),
94
                ('totalsum', ctypes.c_double),
95
                ('roisum', ctypes.c_double),
96
                ('roirate', ctypes.c_double),
97
                ('ofls', ctypes.c_double),
98
                ('sweeps', ctypes.c_double),
99
                ('stevents', ctypes.c_double),
100
                ('maxval', ctypes.c_ulong), ]
101
102
103
class AcqSettings(ctypes.Structure):
104
    _fields_ = [('range',       ctypes.c_long),
105
                ('cftfak',      ctypes.c_long),
106
                ('roimin',      ctypes.c_long),
107
                ('roimax',      ctypes.c_long),
108
                ('nregions',    ctypes.c_long),
109
                ('caluse',      ctypes.c_long),
110
                ('calpoints',   ctypes.c_long),
111
                ('param',       ctypes.c_long),
112
                ('offset',      ctypes.c_long),
113
                ('xdim',        ctypes.c_long),
114
                ('bitshift',    ctypes.c_ulong),
115
                ('active',      ctypes.c_long),
116
                ('eventpreset', ctypes.c_double),
117
                ('dummy1',      ctypes.c_double),
118
                ('dummy2',      ctypes.c_double),
119
                ('dummy3',      ctypes.c_double), ]
120
121
class ACQDATA(ctypes.Structure):
122
    """ Create a structured Data type with ctypes where the dll can write into.
123
124
    This object handles and retrieves the acquisition data of the Fastcomtec.
125
    """
126
    _fields_ = [('s0', ctypes.POINTER(ctypes.c_ulong)),
127
                ('region', ctypes.POINTER(ctypes.c_ulong)),
128
                ('comment', ctypes.c_char_p),
129
                ('cnt', ctypes.POINTER(ctypes.c_double)),
130
                ('hs0', ctypes.c_int),
131
                ('hrg', ctypes.c_int),
132
                ('hcm', ctypes.c_int),
133
                ('hct', ctypes.c_int), ]
134
135 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
136
class BOARDSETTING(ctypes.Structure):
137
    _fields_ = [('sweepmode',   ctypes.c_long),
138
                ('prena',       ctypes.c_long),
139
                ('cycles',      ctypes.c_long),
140
                ('sequences',   ctypes.c_long),
141
                ('syncout',     ctypes.c_long),
142
                ('digio',       ctypes.c_long),
143
                ('digval',      ctypes.c_long),
144
                ('dac0',        ctypes.c_long),
145
                ('dac1',        ctypes.c_long),
146
                ('dac2',        ctypes.c_long),
147
                ('dac3',        ctypes.c_long),
148
                ('dac4',        ctypes.c_long),
149
                ('dac5',        ctypes.c_long),
150
                ('fdac',        ctypes.c_int),
151
                ('tagbits',     ctypes.c_int),
152
                ('extclk',      ctypes.c_int),
153
                ('maxchan',     ctypes.c_long),
154
                ('serno',       ctypes.c_long),
155
                ('ddruse',      ctypes.c_long),
156
                ('active',      ctypes.c_long),
157
                ('holdafter',   ctypes.c_double),
158
                ('swpreset',    ctypes.c_double),
159
                ('fstchan',     ctypes.c_double),
160
                ('timepreset',  ctypes.c_double), ]
161
162
class FastComtec(Base, FastCounterInterface):
163
    """
164
    stable: Jochen Scheuer, Simon Schmitt
165
166
    Hardware Class for the FastComtec Card.
167
    """
168
    _modclass = 'FastComtec'
169
    _modtype = 'hardware'
170
    gated = ConfigOption('gated', False, missing='warn')
171
    trigger_safety = ConfigOption('trigger_safety', 200e-9, missing='warn')
172
    aom_delay = ConfigOption('aom_delay', 400e-9, missing='warn')
173
    minimal_binwidth = ConfigOption('minimal_binwidth', 0.2e-9, missing='warn')
174
175
    def __init__(self, config, **kwargs):
176
        super().__init__(config=config, **kwargs)
177
178
        self.log.debug('The following configuration was found.')
179
180
        # checking for the right configuration
181
        for key in config.keys():
182
            self.log.info('{0}: {1}'.format(key,config[key]))
183
184
        #this variable has to be added because there is no difference
185
        #in the fastcomtec it can be on "stopped" or "halt"
186
        self.stopped_or_halt = "stopped"
187
        self.timetrace_tmp = []
188
189
    def on_activate(self):
190
        """ Initialisation performed during activation of the module.
191
        """
192
193
        self.dll = ctypes.windll.LoadLibrary('C:\Windows\System32\DMCS6.dll')
194
        if self.gated:
195
            self.change_sweep_mode(gated=True)
196
        else:
197
            self.change_sweep_mode(gated=False)
198
        return
199
200
    def on_deactivate(self):
201
        """ Deinitialisation performed during deactivation of the module.
202
        """
203
        return
204
205
    def get_constraints(self):
206
        """ Retrieve the hardware constrains from the Fast counting device.
207
208
        @return dict: dict with keys being the constraint names as string and
209
                      items are the definition for the constaints.
210
211
         The keys of the returned dictionary are the str name for the constraints
212
        (which are set in this method).
213
214
                    NO OTHER KEYS SHOULD BE INVENTED!
215
216
        If you are not sure about the meaning, look in other hardware files to
217
        get an impression. If still additional constraints are needed, then they
218
        have to be added to all files containing this interface.
219
220
        The items of the keys are again dictionaries which have the generic
221
        dictionary form:
222
            {'min': <value>,
223
             'max': <value>,
224
             'step': <value>,
225
             'unit': '<value>'}
226
227
        Only the key 'hardware_binwidth_list' differs, since they
228
        contain the list of possible binwidths.
229
230
        If the constraints cannot be set in the fast counting hardware then
231
        write just zero to each key of the generic dicts.
232
        Note that there is a difference between float input (0.0) and
233
        integer input (0), because some logic modules might rely on that
234
        distinction.
235
236
        ALL THE PRESENT KEYS OF THE CONSTRAINTS DICT MUST BE ASSIGNED!
237
        """
238
239
        constraints = dict()
240
241
        # the unit of those entries are seconds per bin. In order to get the
242
        # current binwidth in seonds use the get_binwidth method.
243
        constraints['hardware_binwidth_list'] = list(self.minimal_binwidth * (2 ** np.array(
244
                                                     np.linspace(0,24,25))))
245
        constraints['max_sweep_len'] = 6.8
246
        return constraints
247
248
    def configure(self, bin_width_s, record_length_s, number_of_gates=0, filename=None):
249
        """ Configuration of the fast counter.
250
251
        @param float bin_width_s: Length of a single time bin in the time trace
252
                                  histogram in seconds.
253
        @param float record_length_s: Total length of the timetrace/each single
254
                                      gate in seconds.
255
        @param int number_of_gates: optional, number of gates in the pulse
256
                                    sequence. Ignore for not gated counter.
257
258
        @return tuple(binwidth_s, record_length_s, number_of_gates):
259
                    binwidth_s: float the actual set binwidth in seconds
260
                    gate_length_s: the actual record length in seconds
261
                    number_of_gates: the number of gated, which are accepted,
262
                    None if not-gated
263
        """
264
265
        # when not gated, record length = total sequence length, when gated, record length = laser length.
266
        # subtract 200 ns to make sure no sequence trigger is missed
267
        record_length_FastComTech_s = record_length_s
268
        if self.gated:
269
            # add time to account for AOM delay
270
            no_of_bins = int((record_length_FastComTech_s + self.aom_delay) / self.set_binwidth(bin_width_s))
271
        else:
272
            # subtract time to make sure no sequence trigger is missed
273
            no_of_bins = int((record_length_FastComTech_s - self.trigger_safety) / self.set_binwidth(bin_width_s))
274
275
        self.set_length(no_of_bins, preset=1, cycles=number_of_gates)
276
277
        if filename is not None:
278
            self._change_filename(filename)
279
280
        return (self.get_binwidth(), record_length_FastComTech_s, number_of_gates)
281
282
    #card if running or halt or stopped ...
283
    def get_status(self):
284
        """
285
        Receives the current status of the Fast Counter and outputs it as return value.
286
        0 = unconfigured
287
        1 = idle
288
        2 = running
289
        3 = paused
290
        -1 = error state
291
        """
292
        status = AcqStatus()
293
        self.dll.GetStatusData(ctypes.byref(status), 0)
294
        # status.started = 3 measn that fct is about to stop
295
        while status.started == 3:
296
            time.sleep(0.1)
297
            self.dll.GetStatusData(ctypes.byref(status), 0)
298
        if status.started == 1:
299
            return 2
300
        elif status.started == 0:
301
            if self.stopped_or_halt == "stopped":
302
                return 1
303
            elif self.stopped_or_halt == "halt":
304
                return 3
305
            else:
306
                self.log.error('There is an unknown status from FastComtec. The status message was %s' % (str(status.started)))
307
308
                return -1
309
        else:
310
            self.log.error(
311
                'There is an unknown status from FastComtec. The status message was %s' % (str(status.started)))
312
            return -1
313
314
315
    def start_measure(self):
316
        """Start the measurement. """
317
        status = self.dll.Start(0)
318
        while self.get_status() != 2:
319
            time.sleep(0.05)
320
        return status
321
322
    def pause_measure(self):
323
        """Make a pause in the measurement, which can be continued. """
324
        self.stopped_or_halt = "halt"
325
        status = self.dll.Halt(0)
326
        while self.get_status() != 3:
327
            time.sleep(0.05)
328
329
        if self.gated:
330
            self.timetrace_tmp = self.get_data_trace()
331
        return status
332
333
    def stop_measure(self):
334
        """Stop the measurement. """
335
        self.stopped_or_halt = "stopped"
336
        status = self.dll.Halt(0)
337
        while self.get_status() != 1:
338
            time.sleep(0.05)
339
340
        if self.gated:
341
            self.timetrace_tmp = []
342
        return status
343
344
    def continue_measure(self):
345
        """Continue a paused measurement. """
346
        if self.gated:
347
            status = self.start_measure()
348
        else:
349
            status = self.dll.Continue(0)
350
            while self.get_status() != 2:
351
                time.sleep(0.05)
352
        return status
353
354
    def get_binwidth(self):
355
        """ Returns the width of a single timebin in the timetrace in seconds.
356
357
        @return float: current length of a single bin in seconds (seconds/bin)
358
359
        The red out bitshift will be converted to binwidth. The binwidth is
360
        defined as 2**bitshift*minimal_binwidth.
361
        """
362
        return self.minimal_binwidth*(2**int(self.get_bitshift()))
363
364
    def is_gated(self):
365
        """ Check the gated counting possibility.
366
367
        @return bool: Boolean value indicates if the fast counter is a gated
368
                      counter (TRUE) or not (FALSE).
369
        """
370
        return self.gated
371
372
    def set_gated(self, gated):
373
        """ Check the gated counting possibility.
374
375
        @return bool: Boolean value indicates if the fast counter is a gated
376
                      counter (TRUE) or not (FALSE).
377
        """
378
        self.gated = gated
379
        self.change_sweep_mode(gated)
380
        return self.gated
381
382
    def get_data_trace(self):
383
        """
384
        Polls the current timetrace data from the fast counter and returns it as a numpy array (dtype = int64).
385
        The binning specified by calling configure() must be taken care of in this hardware class.
386
        A possible overflow of the histogram bins must be caught here and taken care of.
387
        If the counter is UNgated it will return a 1D-numpy-array with returnarray[timebin_index]
388
        If the counter is gated it will return a 2D-numpy-array with returnarray[gate_index, timebin_index]
389
390
          @return arrray: Time trace.
391
        """
392
        setting = AcqSettings()
393
        self.dll.GetSettingData(ctypes.byref(setting), 0)
394
        N = setting.range
395
396
        if self.gated:
397
            bsetting=BOARDSETTING()
398
            self.dll.GetMCSSetting(ctypes.byref(bsetting), 0)
399
            H = bsetting.cycles
400
            data = np.empty((H, int(N / H)), dtype=np.uint32)
401
402
        else:
403
            data = np.empty((N,), dtype=np.uint32)
404
405
        p_type_ulong = ctypes.POINTER(ctypes.c_uint32)
406
        ptr = data.ctypes.data_as(p_type_ulong)
407
        self.dll.LVGetDat(ptr, 0)
408
        time_trace = np.int64(data)
409
410
        if self.gated and self.timetrace_tmp != []:
411
            time_trace = time_trace + self.timetrace_tmp
412
413
        return time_trace
414
415
416
417
    # =========================================================================
418
    #                           Non Interface methods
419
    # =========================================================================
420
421
    def get_data_testfile(self):
422
        ''' Load data test file '''
423
        data = np.loadtxt(os.path.join(get_main_dir(), 'tools', 'FastComTec_demo_timetrace.asc'))
424
        time.sleep(0.5)
425
        return data
426
427
    def get_bitshift(self):
428
        """Get bitshift from Fastcomtec.
429
430
        @return int settings.bitshift: the red out bitshift
431
        """
432
        settings = AcqSettings()
433
        self.dll.GetSettingData(ctypes.byref(settings), 0)
434
        return int(settings.bitshift)
435
436
    def set_bitshift(self, bitshift):
437
        """ Sets the bitshift properly for this card.
438
439
        @param int bitshift:
440
441
        @return int: asks the actual bitshift and returns the red out value
442
        """
443
        cmd = 'BITSHIFT={0}'.format(bitshift)
444
        self.dll.RunCmd(0, bytes(cmd, 'ascii'))
445
        return self.get_bitshift()
446
447
    def set_binwidth(self, binwidth):
448
        """ Set defined binwidth in Card.
449
450
        @param float binwidth: the current binwidth in seconds
451
452
        @return float: Red out bitshift converted to binwidth
453
454
        The binwidth is converted into to an appropiate bitshift defined as
455
        2**bitshift*minimal_binwidth.
456
        """
457
        bitshift = int(np.log2(binwidth/self.minimal_binwidth))
458
        new_bitshift=self.set_bitshift(bitshift)
459
460
        return self.minimal_binwidth*(2**new_bitshift)
461
462
    #TODO: Check such that only possible lengths are set.
463
    def set_length(self, length_bins, preset=None, cycles=None):
464
        """ Sets the length of the length of the actual measurement.
465
466
        @param int length_bins: Length of the measurement in bins
467
468
        @return float: Red out length of measurement
469
        """
470
        constraints = self.get_constraints()
471
        if length_bins * self.get_binwidth() < constraints['max_sweep_len']:
472
            cmd = 'RANGE={0}'.format(int(length_bins))
473
            self.dll.RunCmd(0, bytes(cmd, 'ascii'))
474
            cmd = 'roimax={0}'.format(int(length_bins))
475
            self.dll.RunCmd(0, bytes(cmd, 'ascii'))
476
            if preset != None:
477
                cmd = 'swpreset={0}'.format(preset)
478
                self.dll.RunCmd(0, bytes(cmd, 'ascii'))
479
            if cycles != None and cycles != 0:
480
                cmd = 'cycles={0}'.format(cycles)
481
                self.dll.RunCmd(0, bytes(cmd, 'ascii'))
482
            return self.get_length()
483
        else:
484
            self.log.error(
485
                'Length of sequence is too high: %s' % (str(length_bins * self.get_binwidth())))
486
            return -1
487
488
    def get_length(self):
489
        """ Get the length of the current measurement.
490
491
          @return int: length of the current measurement in bins
492
        """
493
        setting = AcqSettings()
494
        self.dll.GetSettingData(ctypes.byref(setting), 0)
495
        return int(setting.range)
496
497
    def set_preset(self, preset):
498
        """ Sets the preset/
499
500
        @param int preset: Preset in sweeps of starts
501
502
        @return int mode: specified save mode
503
        """
504
        cmd = 'swpreset={0}'.format(preset)
505
        self.dll.RunCmd(0, bytes(cmd, 'ascii'))
506
        return preset
507
508
    def get_preset(self):
509
        """ Gets the preset
510
       @return int mode: current preset
511
        """
512
        bsetting = BOARDSETTING()
513
        self.dll.GetMCSSetting(ctypes.byref(bsetting), 0)
514 View Code Duplication
        preset = bsetting.swpreset
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
515
        return int(preset)
516
517
    def set_cycles(self, cycles):
518
        """ Sets the cycles
519
520
        @param int cycles: Total amount of cycles
521
522
        @return int mode: current cycles
523
        """
524
        cmd = 'cycles={0}'.format(cycles)
525
        self.dll.RunCmd(0, bytes(cmd, 'ascii'))
526
        return cycles
527
528
    def get_cycles(self):
529
        """ Gets the cycles
530
        @return int mode: current cycles
531
        """
532
        bsetting = BOARDSETTING()
533
        self.dll.GetMCSSetting(ctypes.byref(bsetting), 0)
534
        cycles = bsetting.cycles
535
        return cycles
536
537
    def _change_filename(self,name):
538
        """ Changed the name in FCT"""
539
        cmd = 'mpaname=%s'%name
540
        self.dll.RunCmd(0, bytes(cmd, 'ascii'))
541
        return name
542
543
    def change_sweep_mode(self, gated):
544
        if gated:
545
            cmd = 'sweepmode={0}'.format(hex(1978500))
546
            self.dll.RunCmd(0, bytes(cmd, 'ascii'))
547
            cmd = 'prena={0}'.format(hex(16)) #To select starts preset
548
            # cmd = 'prena={0}'.format(hex(4)) #To select sweeps preset
549
            self.dll.RunCmd(0, bytes(cmd, 'ascii'))
550
            self.gated = True
551
        else:
552
            # fastcomtch standard settings for ungated acquisition (check manual)
553
            cmd = 'sweepmode={0}'.format(hex(1978496))
554
            self.dll.RunCmd(0, bytes(cmd, 'ascii'))
555
            cmd = 'prena={0}'.format(hex(0))
556
            self.dll.RunCmd(0, bytes(cmd, 'ascii'))
557
            self.gated = False
558
        return gated
559
560
    def change_save_mode(self, mode):
561
        """ Changes the save mode of Mcs6
562
563
        @param int mode: Specifies the save mode (0: No Save at Halt, 1: Save at Halt,
564
                        2: Write list file, No Save at Halt, 3: Write list file, Save at Halt
565
566
        @return int mode: specified save mode
567
        """
568
        cmd = 'savedata={0}'.format(mode)
569
        self.dll.RunCmd(0, bytes(cmd, 'ascii'))
570
        return mode
571
572
    def set_delay_start(self, delay_s):
573
        """ Sets the record delay length
574
575
        @param int delay_s: Record delay after receiving a start trigger
576
577
        @return int mode: specified save mode
578
        """
579
580
        # A delay can only be adjusted in steps of 6.4ns
581
        delay_bins = np.rint(delay_s / 6.4e-9)
582
        cmd = 'fstchan={0}'.format(int(delay_bins))
583
        self.dll.RunCmd(0, bytes(cmd, 'ascii'))
584
        return delay_bins
585
586
    def get_delay_start(self):
587
        """ Returns the current record delay length
588
589
        @return float delay_s: current record delay length in seconds
590
        """
591
        bsetting = BOARDSETTING()
592
        self.dll.GetMCSSetting(ctypes.byref(bsetting), 0)
593
        delay_s = bsetting.fstchan * 6.4e-9
594
        return delay_s
595
596
    # =========================================================================
597
    #   The following methods have to be carefully reviewed and integrated as
598
    #   internal methods/function, because they might be important one day.
599
    # =========================================================================
600
601
602
603
    def SetLevel(self, start, stop):
604
        setting = AcqSettings()
605
        self.dll.GetSettingData(ctypes.byref(setting), 0)
606
        def FloatToWord(r):
607
            return int((r+2.048)/4.096*int('ffff',16))
608
        setting.dac0 = ( setting.dac0 & int('ffff0000',16) ) | FloatToWord(start)
609
        setting.dac1 = ( setting.dac1 & int('ffff0000',16) ) | FloatToWord(stop)
610
        self.dll.StoreSettingData(ctypes.byref(setting), 0)
611
        self.dll.NewSetting(0)
612
        return self.GetLevel()
613
614
    def GetLevel(self):
615
        setting = AcqSettings()
616
        self.dll.GetSettingData(ctypes.byref(setting), 0)
617
        def WordToFloat(word):
618
            return (word & int('ffff',16)) * 4.096 / int('ffff',16) - 2.048
619
        return WordToFloat(setting.dac0), WordToFloat(setting.dac1)
620
621
622
623
624
625
626
627
628
629
630