-
Notifications
You must be signed in to change notification settings - Fork 1
/
HF2LI.py
1382 lines (1191 loc) · 62.1 KB
/
HF2LI.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# Copyright []
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
### BEGIN NODE INFO
[info]
name = HF2LI Server
version = 1.0
description = Communicates with the Lock in, which has built in PLL / PID methods.
[startup]
cmdline = %PYTHON% %FILE%
timeout = 20
[shutdown]
message = 987654321
timeout = 20
### END NODE INFO
"""
#TODO: Filter BW, Filter Order, and Harmonic info for PID control
#TODO: reprogram as a proper device server to make it be able to host connections to several HF2LI at the same time
from labrad.server import LabradServer, setting
from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.internet import reactor, defer
import labrad.units as units
from labrad.types import Value
import time
import numpy as np
import zhinst
import zhinst.utils
import sys
class HF2LIServer(LabradServer):
name = "HF2LI Server" # Will be labrad name of server
#------------------------------------------------------------------------------------------------------------------------------------------#
"""
The following section of code has initializing and general lock in commands that can be useful in multiple contexts.
"""
def initServer(self): # Do initialization here
self.daq = None
self.dev_ID = 'No Device Selected'
self.device_list = []
self.props = None
self.sweeper = None
self.pidAdvisor = None
self.poll_data = None
print("Server initialization complete")
@inlineCallbacks
def initPIDAdvisor(self, c = None):
self.pidAdvisor = yield self.daq.pidAdvisor()
#Set device
yield self.pidAdvisor.set('pidAdvisor/device', self.dev_ID)
#Automatic response calculation triggered by parameter change.
yield self.pidAdvisor.set('pidAdvisor/auto', 1)
#Adjusts the demodulator bandwidth to fit best to the specified target bandwidth of the full system.
yield self.pidAdvisor.set('pidAdvisor/pid/autobw', 1)
# DUT model
# source = 4: Internal PLL
yield self.pidAdvisor.set('pidAdvisor/dut/source', 4)
# IO Delay of the feedback system describing the earliest response
# for a step change. This parameter does not affect the shape of
# the DUT transfer function
yield self.pidAdvisor.set('pidAdvisor/dut/delay', 0.0)
@inlineCallbacks
def initSweeper(self, c = None):
self.sweeper = yield self.daq.sweep()
yield self.sweeper.set('sweep/device', self.dev_ID)
@setting(100,returns = '')
def detect_devices(self,c):
""" Attempt to connect to the LabOne server (not a LadRAD server) and get a list of devices."""
try:
self.daq = yield zhinst.utils.autoConnect()
print('LabOne DAQ Server detected.')
self.device_list = yield zhinst.utils.devices(self.daq)
print('Devices connected to LabOne DAQ Server are the following:')
print(self.device_list)
except RuntimeError:
print ('Failed to detected LabOne DAQ Server and an associated Zurich Instruments device.'
' Check that everything is plugged into the computer.')
@setting(101, 'List Devices', returns=['*(ws)'])
def list_devices(self, c):
"""Returns the list of devices. If none have been detected (either because detect_devices has not yet
been run, or because of a bad connection), this will return an empty array. This is the format required for a DeviceServer
which this server has not yet transitioned to."""
names = self.device_list
length = len(self.device_list)
IDs = list(range(0,length))
return list(zip(IDs, names))
@setting(102, 'Select Device', key=[': Select first device', 's: Select device by name', 'w: Select device by ID'], returns=['s: Name of the selected device'])
def select_device(self, c, key = None):
"""Select a device for the current context. DOES NOT WORK. Instead, sets the active device ID to the provided dev_ID. If no dev_ID is provided, sets the active
device to the first device from the device list. Sets the API level to 1, which should provide all the functionality for the HF2LI. Right now, this is a
server setting, NOT a context setting, so you cannot have multiple connections with different contexts connected to different devices."""
if key is None:
self.dev_ID = self.device_list[0]
self.initPIDAdvisor()
self.initSweeper()
elif isinstance(key, str):
if key in self.device_list:
self.dev_ID = key
self.initPIDAdvisor()
self.initSweeper()
else:
print("Provided device key is not in the list of possible devices.")
else:
try:
self.dev_ID = self.device_list[key]
self.initPIDAdvisor()
self.initSweeper()
except:
print("Provided device key is not in the list of possible devices.")
return self.dev_ID
@setting(103,settings = '**s', returns = '')
def set_settings(self, c, settings):
"""Simultaneously set all the settings described in the settings input. Settings should be a
list of string and input tuples, where the string provides the node information and the
input is the required input. For example:
setting = [['/%s/demods/*/enable' % self.dev_ID, '0'],
['/%s/demods/*/trigger' % self.dev_ID, '0'],
['/%s/sigouts/*/enables/*' % self.dev_ID, '0'],
['/%s/scopes/*/enable' % self.dev_ID, '0']]
This function allows changing multiple settings quickly, however it requires knowledge
of the node names. Most settings that can be set through this function can also be
set through other functions."""
for el in settings:
el[1] = float(el[1])
yield self.daq.set(settings)
@setting(104,returns = '')
def sync(self,c):
"""Perform a global synchronisation between the device and the data server:
Ensure that the settings have taken effect on the device before setting
the next configuration."""
yield self.daq.sync()
@setting(155, returns = 's')
def version(self,c):
"""Returns the version of the software installed on this computer"""
ver = yield self.daq.version()
returnValue(ver)
#------------------------------------------------------------------------------------------------------------------------------------------#
"""
The following section of code has all of the commands relevant to basic controls of the lock in reading and output.
"""
@setting(105,returns = '')
def disable_outputs(self,c):
"""Create a base instrument configuration: disable all outputs, demods and scopes."""
general_setting = [['/%s/demods/*/enable' % self.dev_ID, 0],
['/%s/demods/*/trigger' % self.dev_ID, 0],
['/%s/sigouts/*/enables/*' % self.dev_ID, 0],
['/%s/scopes/*/enable' % self.dev_ID, 0]]
yield self.daq.set(general_setting)
# Perform a global synchronisation between the device and the data server:
# Ensure that the settings have taken effect on the device before setting
# the next configuration.
@setting(106,input_channel = 'i', on = 'b', returns = '')
def set_ac(self, c, input_channel, on):
"""Set the AC coupling of the provided input channel (1 indexed) to on, if on is True,
and to off, if on is False"""
setting = ['/%s/sigins/%d/ac' % (self.dev_ID, input_channel-1), on],
yield self.daq.set(setting)
@setting(107,input_channel = 'i', on = 'b', returns = '')
def set_imp50(self, c, input_channel, on):
"""Set the input impedance of the provided input channel (1 indexed) to 50 ohms, if on is True,
and to 1 mega ohm, if on is False"""
setting = ['/%s/sigins/%d/imp50' % (self.dev_ID, input_channel-1), on],
yield self.daq.set(setting)
@setting(108,input_channel = 'i', amplitude = 'v[]', returns = '')
def set_range(self, c, input_channel, amplitude):
"""Set the input voltage range of the provided input channel (1 indexed) to the provided amplitude in Volts."""
setting = ['/%s/sigins/%d/range' % (self.dev_ID, input_channel-1), amplitude],
yield self.daq.set(setting)
@setting(1080,input_channel = 'i', returns = 'v[]')
def get_range(self, c, input_channel):
"""Set the input voltage range of the provided input channel (1 indexed) to the provided amplitude in Volts."""
setting = '/%s/sigins/%d/range' % (self.dev_ID, input_channel-1)
range = yield self.daq.get(setting, True)
returnValue(float(range[setting]))
@setting(109,input_channel = 'i', on = 'b', returns = '')
def set_diff(self, c, input_channel, on):
"""Set the input mode of the provided input channel (1 indexed) to differential, if on is True,
and to single ended, if on is False"""
setting = ['/%s/sigins/%d/diff' % (self.dev_ID, input_channel-1), on],
yield self.daq.set(setting)
@setting(110,osc_index= 'i', freq = 'v[]', returns = '')
def set_oscillator_freq(self,c, osc_index, freq):
"""Set the frequency of the designated oscillator (1 indexed) to the provided frequency. The HF2LI Lock-in has
two oscillators. """
setting = ['/%s/oscs/%d/freq' % (self.dev_ID, osc_index-1), freq],
yield self.daq.set(setting)
@setting(1110,demod_index= 'i', on = 'b', returns = '')
def set_demod(self,c, demod_index, on):
"""Turns the specified demodulator on, if on is True, and off, if on is False"""
setting = ['/%s/demods/%d/enable' % (self.dev_ID, demod_index-1), on],
yield self.daq.set(setting)
@setting(111,demod_index= 'i', oscselect = 'i', returns = '')
def set_demod_osc(self,c, demod_index, oscselect):
"""Sets the provided demodulator to select the provided oscillator as its reference frequency. The HF2LI Lock-in has
six demodulators and two oscillators."""
setting = ['/%s/demods/%d/oscselect' % (self.dev_ID, demod_index-1), oscselect-1],
yield self.daq.set(setting)
@setting(112,demod_index= 'i', harm = 'i', returns = '')
def set_demod_harm(self,c, demod_index, harm):
"""Sets the provided demodulator harmonic. Demodulation frequency will be the reference oscillator times the provided
integer harmonic."""
setting = ['/%s/demods/%d/harmonic' % (self.dev_ID, demod_index-1), harm],
yield self.daq.set(setting)
@setting(113,demod_index= 'i', phase = 'v[]', returns = '')
def set_demod_phase(self,c, demod_index, phase):
"""Sets the provided demodulator phase."""
setting = ['/%s/demods/%d/phaseshift' % (self.dev_ID, demod_index-1), phaseshift],
yield self.daq.set(setting)
@setting(114,demod_index= 'i', input_channel = 'i', returns = '')
def set_demod_input(self,c, demod_index, input_channel):
"""Sets the provided demodulator phase."""
setting = ['/%s/demods/%d/adcselect' % (self.dev_ID, demod_index-1), input_channel-1],
yield self.daq.set(setting)
@setting(115,demod_index= 'i', time_constant = 'v[]', returns = '')
def set_demod_time_constant(self,c, demod_index, time_constant):
"""Sets the provided demodulator time constant in seconds."""
setting = ['/%s/demods/%d/timeconstant' % (self.dev_ID, demod_index-1), time_constant],
yield self.daq.set(setting)
@setting(1150,demod_index= 'i', returns = 'v[]')
def get_demod_time_constant(self,c, demod_index):
"""Sets the provided demodulator time constant in seconds."""
setting = '/%s/demods/%d/timeconstant' % (self.dev_ID, demod_index-1)
tc = yield self.daq.get(setting, True)
returnValue(float(tc[setting]))
@setting(117,output_channel = 'i', on = 'b', returns = '')
def set_output(self, c, output_channel, on):
"""Turns the output of the provided output channel (1 indexed) to on, if on is True,
and to off, if on is False"""
setting = ['/%s/sigouts/%d/on' % (self.dev_ID, output_channel-1), on],
yield self.daq.set(setting)
@setting(118,output_channel = 'i', amp = 'v[]', returns = '')
def set_output_amplitude(self, c, output_channel, amp):
"""Sets the output amplitude of the provided output channel (1 indexed) to the provided input amplitude
in units of the output range."""
if output_channel == 1:
setting = ['/%s/sigouts/%d/amplitudes/6' % (self.dev_ID, output_channel-1), amp],
elif output_channel == 2:
setting = ['/%s/sigouts/%d/amplitudes/7' % (self.dev_ID, output_channel-1), amp],
yield self.daq.set(setting)
@setting(1180,output_channel = 'i', returns = 'v[]')
def get_output_amplitude(self, c, output_channel):
"""Gets the output amplitude of the provided output channel (1 indexed) in units of the output range."""
if output_channel == 1:
setting = '/%s/sigouts/%d/amplitudes/6' % (self.dev_ID, output_channel-1)
elif output_channel == 2:
setting = '/%s/sigouts/%d/amplitudes/7' % (self.dev_ID, output_channel-1)
dic = yield self.daq.get(setting, True)
amp = float(dic[setting])
returnValue(amp)
@setting(119,output_channel = 'i', range = 'v[]', returns = '')
def set_output_range(self, c, output_channel, range):
"""Sets the output range of the provided output channel (1 indexed) to the provided input amplitude
in units of volts. Will automatically go to the value just above the desired provided range. Sets to
10 mV, 100 mV, 1 V or 10V."""
setting = ['/%s/sigouts/%d/range' % (self.dev_ID, output_channel-1), range],
yield self.daq.set(setting)
@setting(120,output_channel = 'i', returns = 'v[]')
def get_output_range(self, c, output_channel):
"""Gets the output amplitude of the provided output channel (1 indexed) to the provided input amplitude
in units of the output range."""
setting = '/%s/sigouts/%d/range' % (self.dev_ID, output_channel-1)
dic = yield self.daq.get(setting,True)
range = float(dic[setting])
returnValue(range)
#------------------------------------------------------------------------------------------------------------------------------------------#
"""
The following section of code has all of the commands relevant to using the HF2LI built in sweeper.
"""
@setting(121,start = 'v[]', stop = 'v[]', samplecount = 'i', sweep_param = 's', demod = 'i', log = 'b', bandwidthcontrol = 'i', bandwidth = 'v[]', bandwidthoverlap = 'b', loopcount = 'i', settle_time = 'v[]', settle_inaccuracy = 'v[]', averaging_tc = 'v[]', averaging_sample = 'v[]', returns = 'b')
def create_sweep_object(self,c,start,stop, samplecount, sweep_param, demod = 1, log = False, bandwidthcontrol = 2, bandwidth = 1000, bandwidthoverlap = False, loopcount = 1, settle_time = 0, settle_inaccuracy = 0.001, averaging_tc = 5, averaging_sample = 5):
"""Sweeps the provided sweep parameter from the provided start value to the provided stop value with
the desired number of points. The sweep records all data at each point in the sweep. The sweeper will
not turn on any outputs or configure anything else. It only sweeps the parameter and records data.
Available sweep_param inputs are (spaces included): \r\n
oscillator 1 \r\n
oscillator 2 \r\n
output 1 amplitude \r\n
output 2 amplitude \r\n
output 1 offset \r\n
output 2 offset \r\n
Returns the 4 by samplecount array with the first column corresponding to grid of the swept parameter,
the second corresponds to the demodulator R, the third to the phase, and the fourth to the frequency.
Loop count greater than 1 not yet implemented. """
self.sweeper_path = '/%s/demods/%d/sample' % (self.dev_ID, demod - 1)
#Set the parameter to be swept
sweep_param_set = False
if sweep_param == "oscillator 1":
yield self.sweeper.set('sweep/gridnode', 'oscs/0/freq')
sweep_param_set = True
elif sweep_param == "oscillator 2":
yield self.sweeper.set('sweep/gridnode', 'oscs/1/freq')
sweep_param_set = True
elif sweep_param == "output 1 amplitude":
yield self.sweeper.set('sweep/gridnode', 'sigouts/0/amplitudes/6')
sweep_param_set = True
elif sweep_param == "output 2 amplitude":
yield self.sweeper.set('sweep/gridnode', 'sigouts/1/amplitudes/7')
sweep_param_set = True
elif sweep_param == "output 1 offset":
yield self.sweeper.set('sweep/gridnode', 'sigouts/0/offset')
sweep_param_set = True
elif sweep_param == "output 2 offset":
yield self.sweeper.set('sweep/gridnode', 'sigouts/1/offset')
sweep_param_set = True
if sweep_param_set == True:
#Set the start and stop points
if start <= stop:
yield self.sweeper.set('sweep/start', start)
yield self.sweeper.set('sweep/stop', stop)
yield self.sweeper.set('sweep/scan', 0)
else:
yield self.sweeper.set('sweep/start', stop)
yield self.sweeper.set('sweep/stop', start)
yield self.sweeper.set('sweep/scan', 3)
yield self.sweeper.set('sweep/samplecount', samplecount)
#Specify linear or logarithmic grid spacing. Off by default
yield self.sweeper.set('sweep/xmapping', log)
# Automatically control the demodulator bandwidth/time constants used.
# 0=manual, 1=fixed, 2=auto
# Note: to use manual and fixed, sweep/bandwidth has to be set to a value > 0.
yield self.sweeper.set('sweep/bandwidthcontrol', bandwidthcontrol)
if bandwidthcontrol == 0 or bandwidthcontrol == 1:
yield self.sweeper.set('sweep/bandwidth',bandwidth)
# Sets the bandwidth overlap mode (default 0). If enabled, the bandwidth of
# a sweep point may overlap with the frequency of neighboring sweep
# points. The effective bandwidth is only limited by the maximal bandwidth
# setting and omega suppression. As a result, the bandwidth is independent
# of the number of sweep points. For frequency response analysis bandwidth
# overlap should be enabled to achieve maximal sweep speed (default: 0). 0 =
# Disable, 1 = Enable.
yield self.sweeper.set('sweep/bandwidthoverlap', bandwidthoverlap)
# Specify the number of sweeps to perform back-to-back.
yield self.sweeper.set('sweep/loopcount', loopcount)
#Specify the settling time between data points.
yield self.sweeper.set('sweep/settling/time', settle_time)
# The sweep/settling/inaccuracy' parameter defines the settling time the
# sweeper should wait before changing a sweep parameter and recording the next
# sweep data point. The settling time is calculated from the specified
# proportion of a step response function that should remain. The value
# provided here, 0.001, is appropriate for fast and reasonably accurate
# amplitude measurements. For precise noise measurements it should be set to
# ~100n.
# Note: The actual time the sweeper waits before recording data is the maximum
# time specified by sweep/settling/time and defined by
# sweep/settling/inaccuracy.
yield self.sweeper.set('sweep/settling/inaccuracy', settle_inaccuracy)
# Set the minimum time to record and average data. By default set to 10 demodulator
# filter time constants.
yield self.sweeper.set('sweep/averaging/tc', averaging_tc)
# Minimal number of samples that we want to record and average. Note,
# the number of samples used for averaging will be the maximum number of
# samples specified by either sweep/averaging/tc or sweep/averaging/sample.
# By default this is set to 5.
yield self.sweeper.set('sweep/averaging/sample', averaging_sample)
#Subscribe to path defined previously
yield self.sweeper.subscribe(self.sweeper_path)
returnValue(True)
else:
print('Desired sweep parameter does not exist')
returnValue(False)
@setting(122, returns = 'b')
def start_sweep(self,c):
success = False
if self.sweeper is not None:
yield self.sweeper.execute()
success = True
returnValue(success)
@setting(123, returns = '**v[]')
def read_latest_values(self,c):
return_flat_dict = True
data = yield self.sweeper.read(return_flat_dict)
demod_data = data[self.sweeper_path]
grid = demod_data[0][0]['grid']
R = np.abs(demod_data[0][0]['x'] + 1j*demod_data[0][0]['y'])
#phi = np.angle(demod_data[0][0]['x'] + 1j*demod_data[0][0]['y'], True)
phi = 180*np.arctan2(demod_data[0][0]['y'], demod_data[0][0]['x'])/np.pi
frequency = demod_data[0][0]['frequency']
formatted_data = [[],[],[],[]]
length = len(grid)
for i in range(0,length):
try:
formatted_data[0].append(float(grid[i]))
formatted_data[1].append(float(frequency[i]))
formatted_data[2].append(float(R[i]))
formatted_data[3].append(float(phi[i]))
except:
pass
returnValue(formatted_data)
@setting(124,returns = 'b')
def sweep_complete(self,c):
'''Checks to see if there's a sweep was completed. Returns True if the sweeper is not
currently sweeping. Returns False if the sweeper is mid sweep.'''
if self.sweeper is not None:
done = yield self.sweeper.finished()
else:
done = True
returnValue(done)
@setting(125,returns = 'v[]')
def sweep_time_remaining(self,c):
if self.sweeper is not None:
time = yield self.sweeper.get('sweep/remainingtime')
time = time['remainingtime'][0]
else:
time = float('nan')
returnValue(time)
@setting(126, returns = 'b')
def stop_sweep(self,c):
success = False
if self.sweeper is not None:
yield self.sweeper.finish()
success = True
returnValue(success)
@setting(127,returns = '')
def clear_sweep(self,c):
try:
# Stop the sweeper thread and clear the memory.
self.sweeper.unsubscribe(path)
self.sweeper.clear()
except:
pass
#------------------------------------------------------------------------------------------------------------------------------------------#
"""
The following section of code has all of the commands relevant to modifing the PLL module options.
"""
@setting(129,PLL = 'i', freq = 'v[]', returns = '')
def set_PLL_freqcenter(self, c, PLL, freq):
"""Sets the center frequency of the specified PLL (either 1 or 2)"""
setting = ['/%s/plls/%d/freqcenter' % (self.dev_ID, PLL-1), freq],
yield self.daq.set(setting)
@setting(130, PLL = 'i', returns = 'v[]')
def get_PLL_freqcenter(self, c, PLL):
"""Gets the PLL center frequency of the specified PLL (either 1 or 2)."""
setting = '/%s/plls/%d/freqcenter' % (self.dev_ID, PLL-1)
dic = yield self.daq.get(setting,True)
freq = float(dic[setting])
returnValue(freq)
@setting(131,PLL = 'i', freq = 'v[]', returns = '')
def set_PLL_freqrange(self, c, PLL, freq):
"""Sets the frequency range of the specified PLL (either 1 or 2)"""
setting = ['/%s/plls/%d/freqrange' % (self.dev_ID, PLL-1), freq],
yield self.daq.set(setting)
@setting(132, PLL = 'i', returns = 'v[]')
def get_PLL_freqrange(self, c, PLL):
"""Gets the PLL frequency range of the specified PLL (either 1 or 2)."""
setting = '/%s/plls/%d/freqrange' % (self.dev_ID, PLL-1)
dic = yield self.daq.get(setting,True)
freq = float(dic[setting])
returnValue(freq)
@setting(133,PLL = 'i', harm = 'i', returns = '')
def set_PLL_harmonic(self, c, PLL, harm):
"""Sets the phase detector harmonic (1 or 2) of the specified PLL (either 1 or 2)"""
setting = ['/%s/plls/%d/harmonic' % (self.dev_ID, PLL-1), harm],
yield self.daq.set(setting)
@setting(134, PLL = 'i', returns = 'i')
def get_PLL_harmonic(self, c, PLL):
"""Gets the phase detector harmonic of the specified PLL (either 1 or 2)."""
setting = '/%s/plls/%d/harmonic' % (self.dev_ID, PLL-1)
dic = yield self.daq.get(setting,True)
harm = int(dic[setting])
returnValue(harm)
@setting(135,PLL = 'i', tc = 'v[]', returns = '')
def set_PLL_TC(self, c, PLL, tc):
"""Sets the time constant of the specified PLL (either 1 or 2)"""
setting = ['/%s/plls/%d/timeconstant' % (self.dev_ID, PLL-1), tc],
yield self.daq.set(setting)
@setting(136, PLL = 'i', returns = 'v[]')
def get_PLL_TC(self, c, PLL):
"""Gets the PLL center frequency of the specified PLL (either 1 or 2)."""
setting = '/%s/plls/%d/timeconstant' % (self.dev_ID, PLL-1)
dic = yield self.daq.get(setting,True)
tc = float(dic[setting])
returnValue(tc)
@setting(137,PLL = 'i', order = 'i', returns = '')
def set_PLL_filterorder(self, c, PLL, order):
"""Sets the filter order (1 through 8) of the specified PLL (either 1 or 2)"""
setting = ['/%s/plls/%d/order' % (self.dev_ID, PLL-1), order],
yield self.daq.set(setting)
@setting(138, PLL = 'i', returns = 'i')
def get_PLL_filterorder(self, c, PLL):
"""Gets the filter order of the specified PLL (either 1 or 2)."""
setting = '/%s/plls/%d/order' % (self.dev_ID, PLL-1)
dic = yield self.daq.get(setting,True)
order = int(dic[setting])
returnValue(order)
@setting(139,PLL = 'i', setpoint = 'v[]', returns = '')
def set_PLL_setpoint(self, c, PLL, setpoint):
"""Sets the phase setpoint in degrees of the specified PLL (either 1 or 2)"""
setting = ['/%s/plls/%d/setpoint' % (self.dev_ID, PLL-1), setpoint],
yield self.daq.set(setting)
@setting(140, PLL = 'i', returns = 'v[]')
def get_PLL_setpoint(self, c, PLL):
"""Gets the phase setpoint of the specified PLL (either 1 or 2)."""
setting = '/%s/plls/%d/setpoint' % (self.dev_ID, PLL-1)
dic = yield self.daq.get(setting,True)
setpoint = float(dic[setting])
returnValue(setpoint)
@setting(141,PLL = 'i', P = 'v[]', returns = '')
def set_PLL_P(self, c, PLL, P):
"""Sets the proportional term of the specified PLL (either 1 or 2) PID loop"""
setting = ['/%s/plls/%d/p' % (self.dev_ID, PLL-1), P],
yield self.daq.set(setting)
@setting(142, PLL = 'i', returns = 'v[]')
def get_PLL_P(self, c, PLL):
"""Gets the proportional term of the specified PLL (either 1 or 2) PID loop"""
setting = '/%s/plls/%d/p' % (self.dev_ID, PLL-1)
dic = yield self.daq.get(setting,True)
P = float(dic[setting])
returnValue(P)
@setting(143,PLL = 'i', I = 'v[]', returns = '')
def set_PLL_I(self, c, PLL, I):
"""Sets the intergral term of the specified PLL (either 1 or 2) PID loop"""
setting = ['/%s/plls/%d/i' % (self.dev_ID, PLL-1), I],
yield self.daq.set(setting)
@setting(144, PLL = 'i', returns = 'v[]')
def get_PLL_I(self, c, PLL):
"""Gets the integral term of the specified PLL (either 1 or 2) PID loop"""
setting = '/%s/plls/%d/i' % (self.dev_ID, PLL-1)
dic = yield self.daq.get(setting,True)
I = float(dic[setting])
returnValue(I)
@setting(145,PLL = 'i', D = 'v[]', returns = '')
def set_PLL_D(self, c, PLL, D):
"""Sets the derivative term of the specified PLL (either 1 or 2) PID loop"""
setting = ['/%s/plls/%d/d' % (self.dev_ID, PLL-1), D],
yield self.daq.set(setting)
@setting(146, PLL = 'i', returns = 'v[]')
def get_PLL_D(self, c, PLL):
"""Gets the derivative term of the specified PLL (either 1 or 2) PID loop"""
setting = '/%s/plls/%d/d' % (self.dev_ID, PLL-1)
dic = yield self.daq.get(setting,True)
D = float(dic[setting])
returnValue(D)
@setting(147,PLL = 'i', returns = '')
def set_PLL_on(self, c, PLL):
"""Enables the PLL"""
setting = ['/%s/plls/%d/enable' % (self.dev_ID, PLL-1), 1],
yield self.daq.set(setting)
@setting(148,PLL = 'i', returns = '')
def set_PLL_off(self, c, PLL):
"""Turns off the PLL"""
setting = ['/%s/plls/%d/enable' % (self.dev_ID, PLL-1), 0],
yield self.daq.set(setting)
@setting(1480,PLL = 'i', returns = 'b')
def get_PLL_on(self, c, PLL):
"""Turns off the PLL"""
setting = '/%s/plls/%d/enable' % (self.dev_ID, PLL-1)
dic = yield self.daq.get(setting, True)
on = bool(dic[setting])
returnValue(on)
@setting(149,PLL = 'i', sigin = 'i', returns = '')
def set_PLL_input(self, c, PLL, sigin):
"""Sets the PLL input signal (1/2 correspond to sig in 1/2, 3/4 correspond to Aux In 1/2, and 5/6 correspond to
DIO D0/D1"""
setting = ['/%s/plls/%d/adcselect' % (self.dev_ID, PLL-1), sigin-1],
yield self.daq.set(setting)
@setting(150, PLL = 'i', returns = 'v[]')
def get_PLL_input(self, c, PLL):
"""Gets the PID input signal channel"""
setting = '/%s/plls/%d/adcselect' % (self.dev_ID, PLL-1)
dic = yield self.daq.get(setting,True)
sigin = int(dic[setting])
returnValue(sigin+1)
'''
Following two methods currently do not work, also don't really ever need to be used. Kept commented out in case someone
needs them in the future and wants a starting point. Farily certain that PLL rate cannot be set, through reading it
should be possible.
@setting(151,PLL = 'i', rate = 'v[]', returns = '')
def set_PLL_rate(self, c, PLL, rate):
"""Sets the PLL PID sampling rate"""
setting = ['/%s/plls/%d/rate' % (self.dev_ID, PLL-1), rate],
yield self.daq.set(setting)
@setting(152, PLL = 'i', returns = 'v[]')
def get_PLL_rate(self, c, PLL):
"""Gets the PLL PID sampling rate"""
setting = '/%s/plls/%d/rate' % (self.dev_ID, PLL-1)
dic = yield self.daq.get(setting,True)
rate = float(dic[setting])
returnValue(rate)
'''
@setting(174,PLL_index = 'i', on = 'b', returns = '')
def set_PLL_autocenter(self, c, PLL_index, on):
"""Set the PLL auto center frequency of the provided PLL (1 indexed) to on, if on is True,
and to off, if on is False"""
setting = ['/%s/plls/%d/autocenter' % (self.dev_ID, PLL_index-1), on],
yield self.daq.set(setting)
@setting(175,PLL_index = 'i', on = 'b', returns = '')
def set_PLL_autotc(self, c, PLL_index, on):
"""Set the PLL auto time constant of the provided PLL (1 indexed) to on, if on is True,
and to off, if on is False"""
setting = ['/%s/plls/%d/autotimeconstant' % (self.dev_ID, PLL_index-1), on],
yield self.daq.set(setting)
@setting(176,PLL_index = 'i', on = 'b', returns = '')
def set_PLL_autopid(self, c, PLL_index, on):
"""Set the PLL auto pid of the provided PLL (1 indexed) to on, if on is True,
and to off, if on is False"""
setting = ['/%s/plls/%d/autopid' % (self.dev_ID, PLL_index-1), on],
yield self.daq.set(setting)
@setting(1520, PLL = 'i', returns = 'i')
def get_PLL_lock(self, c, PLL):
"""Returns 0 if the PLL is NOT locked, and returns 1 if the PLL is locked."""
setting = '/%s/plls/%d/locked' % (self.dev_ID, PLL-1)
lock = yield self.daq.getInt(setting)
returnValue(lock)
@setting(1521,PLL_index = 'i', returns = 'v[]')
def get_pll_freqdelta(self, c, PLL_index):
"""Gets the PLL (1 or 2) latest output freq delta."""
setting = '/%s/plls/%d/freqdelta' % (self.dev_ID, PLL_index-1)
val = yield self.daq.getDouble(setting)
returnValue(val)
@setting(1522,PLL_index = 'i', returns = 'v[]')
def get_pll_error(self, c, PLL_index):
"""Gets the Auxilary Output (1 through 4) latest output error."""
setting = '/%s/plls/%d/error' % (self.dev_ID, PLL_index-1)
val = yield self.daq.getDouble(setting)
returnValue(val)
#------------------------------------------------------------------------------------------------------------------------------------------#
"""
The following section of code has all of the commands relevant to using the PID advisor
"""
@setting(128, PLL_index = 'i', targetBW = 'v[]', pidMode = 'i', returns = 'b')
def advise_PLL_PID(self, c, PLL_index, targetBW, pidMode):
"""Simulates and computes values for the PLL PID loop. Make sure that the Harmonic
and Filter Order is set on the advisor before running. Requires the index of the PLL,
the desired BW of the PLL PID, and the PID Mode (0 is P, 1 is I, 2 is PI, and 3 is PID)
Function returns true once calculations are complete. Computer parameters should be
retrieved using the appropriate get commands."""
yield self.pidAdvisor.set('pidAdvisor/pid/type', 'pll')
if pidMode == 0:
#P mode
yield self.pidAdvisor.set('pidAdvisor/pid/mode',1)
elif pidMode == 1:
#I mode
yield self.pidAdvisor.set('pidAdvisor/pid/mode',2)
elif pidMode == 2:
#PI mode
yield self.pidAdvisor.set('pidAdvisor/pid/mode',3)
elif pidMode == 3:
#PID mode
yield self.pidAdvisor.set('pidAdvisor/pid/mode',7)
yield self.pidAdvisor.set('pidAdvisor/pid/targetbw', targetBW)
# PID index to use (first PID of device: 0)
yield self.pidAdvisor.set('pidAdvisor/index', PLL_index-1)
#Reset everything to 0 prior to calculation
yield self.pidAdvisor.set('pidAdvisor/pid/p', 0)
yield self.pidAdvisor.set('pidAdvisor/pid/i', 0)
yield self.pidAdvisor.set('pidAdvisor/pid/d', 0)
yield self.pidAdvisor.set('pidAdvisor/calculate', 0)
# Start the module thread
yield self.pidAdvisor.execute()
yield self.sleep(2.0)
# Advise
yield self.pidAdvisor.set('pidAdvisor/calculate', 1)
print('Starting advising. Optimization process may run up to a minute...')
reply = yield self.pidAdvisor.get('pidAdvisor/calculate')
t_start = time.time()
t_timeout = t_start + 60
while reply['calculate'][0] == 1:
reply = yield self.pidAdvisor.get('pidAdvisor/calculate')
if time.time() > t_timeout:
yield self.pidAdvisor.finish()
raise Exception("PID advising failed due to timeout.")
print(("Advice took {:0.1f} s.".format(time.time() - t_start)))
"""
# Get all calculated parameters.
result = yield self.pidAdvisor.get('pidAdvisor/*')
# Check that the dictionary returned by poll contains the data that are needed.
assert result, "pidAdvisor returned an empty data dictionary?"
assert 'pid' in result, "data dictionary has no key 'pid'"
assert 'step' in result, "data dictionary has no key 'step'"
assert 'bode' in result, "data dictionary has no key 'bode'"
"""
returnValue(True)
@setting(156, P = 'v[]', returns = '')
def set_Advisor_P(self, c, P):
"""Sets the proportional term on the PID Advisor"""
setting = ['pidAdvisor/pid/p', P],
yield self.pidAdvisor.set(setting)
@setting(157, returns = 'v[]')
def get_Advisor_P(self, c):
"""Gets the proportional term on the PID Advisor"""
setting = 'pidAdvisor/pid/p'
dic = yield self.pidAdvisor.get(setting,True)
P = float(dic['/pid/p'])
returnValue(P)
@setting(158,I = 'v[]', returns = '')
def set_Advisor_I(self, c, I):
"""Sets the integral term on the PID Advisor"""
setting = ['pidAdvisor/pid/i', I],
yield self.pidAdvisor.set(setting)
@setting(159, returns = 'v[]')
def get_Advisor_I(self, c):
"""Gets the integral term on the PID Advisor"""
setting = 'pidAdvisor/pid/i'
dic = yield self.pidAdvisor.get(setting,True)
I = float(dic['/pid/i'])
returnValue(I)
@setting(160,D = 'v[]', returns = '')
def set_Advisor_D(self, c, D):
"""Sets the derivative term on the PID Advisor"""
setting = ['pidAdvisor/pid/d', D],
yield self.pidAdvisor.set(setting)
@setting(161, returns = 'v[]')
def get_Advisor_D(self, c):
"""Gets the derivative term on the PID Advisor"""
setting = 'pidAdvisor/pid/d'
dic = yield self.pidAdvisor.get(setting,True)
D = float(dic['/pid/d'])
returnValue(D)
@setting(162, returns = 'v[]')
def get_Advisor_PM(self, c):
"""Gets the PLL PID phase margin"""
setting = 'pidAdvisor/pm'
dic = yield self.pidAdvisor.get(setting,True)
PM = float(dic['/pm'])
returnValue(PM)
@setting(163, returns = 'i')
def get_Advisor_stable(self, c):
"""Returns whether or not the current parameters in the PID Advisor are stable."""
setting = 'pidAdvisor/stable'
dic = yield self.pidAdvisor.get(setting,True)
stable = int(dic['/stable'])
returnValue(stable)
@setting(164, tc = 'v[]', returns = '')
def set_Advisor_tc(self, c, tc):
"""Sets the PLL PID adivsor tc"""
setting = ['pidAdvisor/demod/timeconstant', tc],
yield self.pidAdvisor.set(setting)
@setting(165, returns = 'v[]')
def get_Advisor_tc(self, c):
"""Gets the PLL PID adivsor tc"""
setting = 'pidAdvisor/demod/timeconstant'
dic = yield self.pidAdvisor.get(setting,True)
tc = float(dic['/demod/timeconstant'])
returnValue(tc)
@setting(166,rate = 'v[]', returns = '')
def set_Advisor_rate(self, c, rate):
"""Sets the PLL PID advisor sampling rate"""
setting = ['pidAdvisor/pid/rate', rate],
yield self.pidAdvisor.set(setting)
@setting(167, returns = 'v[]')
def get_Advisor_rate(self, c):
"""Gets the PLL PID advisor sampling rate"""
setting = 'pidAdvisor/pid/rate'
dic = yield self.pidAdvisor.get(setting,True)
rate = float(dic['/pid/rate'])
returnValue(rate)
@setting(168, harm = 'i', returns = '')
def set_Advisor_harmonic(self, c, harm):
"""Sets the PLL PID advisor harmonic (can be 1 or 2)"""
setting = ['pidAdvisor/demod/harmonic', harm],
yield self.pidAdvisor.set(setting)
@setting(169, returns = 'i')
def get_Advisor_harmonic(self, c):
"""Gets the PLL PID advisor sampling rate"""
setting = 'pidAdvisor/demod/harmonic'
dic = yield self.pidAdvisor.get(setting,True)
rate = int(dic['/demod/harmonic'])
returnValue(rate)
@setting(170, order = 'i', returns = '')
def set_Advisor_filterorder(self, c, order):
"""Sets the PLL PID advisor harmonic (can be 1 or 2)"""
setting = ['pidAdvisor/demod/order', order],
yield self.pidAdvisor.set(setting)
@setting(171, returns = 'i')
def get_Advisor_filterorder(self, c):
"""Gets the PLL PID advisor sampling rate"""
setting = 'pidAdvisor/demod/order'
dic = yield self.pidAdvisor.get(setting,True)
order = int(dic['/demod/order'])
returnValue(order)
@setting(172, returns = 'v[]')
def get_Advisor_simbw(self, c):
"""Gets the PLL PID advisor simulated bandwidth"""
setting = 'pidAdvisor/bw'
dic = yield self.pidAdvisor.get(setting,True)
bw = float(dic['/bw'])
returnValue(bw)
@setting(173, returns = 'b')
def get_advisor_calc(self,c):
"""Returns True if the pid advisor is mid calculation. Fasle otherwise."""
reply = yield self.pidAdvisor.get('pidAdvisor/calculate')
if reply['calculate'][0] == 1:
returnValue(True)
else:
returnValue(False)
#------------------------------------------------------------------------------------------------------------------------------------------#
"""
The following section of code has all of the commands relevant to modifing the auxiliary outputs.
"""
@setting(177, aux_out_index = 'i', signal_index = 'i', returns = '')
def set_aux_output_signal(self, c, aux_out_index, signal_index):
"""Set the Auxilary Output Signal (1 through 4) to be proportional to the specified signal.
Signal index: Corresponding Signal
0 : Demod X
1 : Demod Y
2 : Demod R
3 : Demod Theta
4 : PLL 1 df
5 : PLL 2 df
-2 : PID 1 Out
-3 : PID 2 Out
-4 : PID 3 Out
-5 : PID 4 Out
-1 : Manual"""
setting = ['/%s/auxouts/%d/outputselect' % (self.dev_ID, aux_out_index-1), signal_index],
yield self.daq.set(setting)
@setting(178, aux_out_index = 'i', returns = 'i')
def get_aux_output_signal(self, c, aux_out_index):
"""Get the Auxilary Output Signal (1 through 4) to be proportional to the specified signal.
Signal index: Corresponding Signal
0 : Demod X
1 : Demod Y
2 : Demod R
3 : Demod Theta
4 : PLL 1 df
5 : PLL 2 df
-2 : PID 1 Out
-3 : PID 2 Out
-4 : PID 3 Out
-5 : PID 4 Out
-1 : Manual"""
setting = '/%s/auxouts/%d/outputselect' % (self.dev_ID, aux_out_index-1)
dic = yield self.daq.get(setting,True)
sig_ind = int(dic[setting])
returnValue(sig_ind)
@setting(179, aux_out_index = 'i', demod_index = 'i', returns = '')
def set_aux_output_demod(self, c, aux_out_index, demod_index):
"""If the Auxilary Output Signal (1 through 4) is set to a deomulator signal (signal index 0 through 3),
then this allows you to specify which demodulator is being used (1 through 6)."""
setting = ['/%s/auxouts/%d/demodselect' % (self.dev_ID, aux_out_index-1), demod_index - 1],
yield self.daq.set(setting)
@setting(180, aux_out_index = 'i', returns = 'i')
def get_aux_output_demod(self, c, aux_out_index):
"""If the Auxilary Output Signal (1 through 4) is set to a deomulator signal (signal index 0 through 3),
then this allows you to get which demodulator is being used (1 through 6)."""
setting = '/%s/auxouts/%d/demodselect' % (self.dev_ID, aux_out_index-1)
dic = yield self.daq.get(setting,True)
demod = int(dic[setting])+1
returnValue(demod)
@setting(181, aux_out_index = 'i', scale = 'v[]', returns = '')
def set_aux_output_scale(self, c, aux_out_index, scale):
"""Sets the Auxilary Output (1 through 4) Scale, or multiplier value."""
setting = ['/%s/auxouts/%d/scale' % (self.dev_ID, aux_out_index-1), scale],
yield self.daq.set(setting)
@setting(182, aux_out_index = 'i', returns = 'v[]')
def get_aux_output_scale(self, c, aux_out_index):
"""Gets the Auxilary Output (1 through 4) Scale, or multiplier value."""
setting = '/%s/auxouts/%d/scale' % (self.dev_ID, aux_out_index-1)
dic = yield self.daq.get(setting,True)
scale = float(dic[setting])
returnValue(scale)
@setting(183, aux_out_index = 'i', offset = 'v[]', returns = '')
def set_aux_output_offset(self, c, aux_out_index, offset):