-
Notifications
You must be signed in to change notification settings - Fork 4
/
rigClass.m
238 lines (206 loc) · 12.8 KB
/
rigClass.m
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
classdef rigClass < dynamicprops
%rigClass holds all the variables linked with the setup hardware (Analog I/O channels, triggers etc.)
properties (SetAccess=private) %check these settings. If you are not sure about your device names, check NI MAX Automation explorer
AIrate % analog input sample rate in Hz
AOrate % analog output sample rate in Hz (has to be divisor of AIrate)
AIrange % analog input voltage range (2-element vector)
AIchans % path to AI channels (primary DAQ card)
shutterline % path to shutter output line (primary DAQ card)
AOchans % cell array of AO channel paths. For a single AO card, this would be a 1-element cell, e.g. {'Dev1/ao0:1'}, for two cards, this could be {'Dev1/ao0:1', 'Dev2/ao0:2'}
channelOrder % cell array of signal to channel assignments. Assign [X,Y,Z,Blank,Phase] signals (in that order, 1-based indexing) to output channels. To assign X to the first output channel, Y to the second, blank to the first of the second card and Z to the second of the second card, use {[1 2], [4 3]}. For a single output card, this could be e.g. {[1 2]}
pmtPolarity % invert PMT polarity, if needed (value: 1 or -1)
gateline % path to digital output of gating/blanking signal
stageCreator % function that takes no arguments and returns a stage object (containing methods getPos and setPos, e.g. @() MP285('COM3', [10 10 25])) or empty
powercontrolCreator % function that takes no arguments and returns a powercontrol object (containing methods getPos and setPos) or an empty scalar ('@() []')
end
properties
laserSyncPort % leave empty if not syncing, sets SampleClock source of AI and TimeBaseSource of AO object, pulse rate is assumed to be AIRate
stage
powercontrol
isScanning = false;
end
properties (SetAccess=private)
AItask
AIreader
AIlistener
AOtask
AOwriter
ParkTask
ParkWriter
TriggerTask
ShutterTask
GateTask
GateTaskWriter
GateCloseTask
GateCloseWriter
ShutterWriter
end
methods
%rigClass constructor
function obj = rigClass(rigcfg, fStatus)
if nargin >=1
for iFn = fieldnames(rigcfg)'
obj.(iFn{1}) = rigcfg.(iFn{1});
end
end
if nargin < 2 || isempty(fStatus)
fprintf(1, 'Starting up rig: ');
fStatus = @(fraction, text) fprintf(1, '\b\b\b%02.0f%%', fraction*100);
end
% load NIDAQmx .NET assembly
fStatus(0/6, 'starting up: loading DAQmx...')
try
NET.addAssembly('NationalInstruments.DAQmx');
import NationalInstruments.DAQmx.*
catch
error('Error loading .NET assembly! Check NIDAQmx .NET installation.')
end
% Reset DAQ boards
fStatus(1/6, 'starting up: resetting DAQ...')
for iDev = unique(strtok([{obj.AIchans} obj.AOchans], '/'))
DaqSystem.Local.LoadDevice(iDev{1}).Reset
end
% Setting up device objects
fStatus(2/6, 'starting up: setting up DAQ...')
obj.AItask = NationalInstruments.DAQmx.Task;
obj.AItask.AIChannels.CreateVoltageChannel(obj.AIchans, '', AITerminalConfiguration.Differential,obj.AIrange(1),obj.AIrange(2), AIVoltageUnits.Volts);
obj.AItask.Timing.ConfigureSampleClock(obj.laserSyncPort, obj.AIrate, SampleClockActiveEdge.Rising, SampleQuantityMode.ContinuousSamples, 100)
%obj.AItask.Timing.ConfigureSampleClock('', obj.AIrate, SampleClockActiveEdge.Rising, SampleQuantityMode.ContinuousSamples, 100)
%obj.AItask.Timing.SampleClockTimebaseSource = 'PFI5';
%obj.AItask.Timing.SampleClockTimebaseRate = 4e6;
obj.AItask.Triggers.StartTrigger.ConfigureDigitalEdgeTrigger('PFI0', DigitalEdgeStartTriggerEdge.Rising); %obj.AItask.ExportSignals.ExportHardwareSignal(ExportSignal.StartTrigger, 'PFI0');
obj.AItask.Control(TaskAction.Verify);
obj.AIreader = AnalogUnscaledReader(obj.AItask.Stream);
obj.AOtask{1} = NationalInstruments.DAQmx.Task;
obj.AOtask{1}.AOChannels.CreateVoltageChannel(obj.AOchans{1}, '',-10, 10, AOVoltageUnits.Volts);
obj.AOtask{1}.Stream.WriteRegenerationMode = WriteRegenerationMode.AllowRegeneration;
obj.AOtask{1}.Timing.ConfigureSampleClock('', obj.AOrate, SampleClockActiveEdge.Rising, SampleQuantityMode.ContinuousSamples, 100)
if ~isempty(obj.laserSyncPort)
obj.AOtask{1}.Timing.SampleClockTimebaseSource = obj.laserSyncPort;
obj.AOtask{1}.Timing.SampleClockTimebaseRate = obj.AIrate;
else
obj.AOtask{1}.Timing.SampleClockTimebaseSource = '100MHzTimebase';
obj.AOtask{1}.Timing.SampleClockTimebaseRate = 100e6;
end
obj.AOtask{1}.Triggers.StartTrigger.ConfigureDigitalEdgeTrigger('PFI0', DigitalEdgeStartTriggerEdge.Rising);
obj.AOtask{1}.ExportSignals.ExportHardwareSignal(ExportSignal.SampleClock, 'PFI7');
obj.AOtask{1}.Control(TaskAction.Verify);
obj.AOwriter{1} = AnalogMultiChannelWriter(obj.AOtask{1}.Stream);
for i = 2:numel(obj.AOchans)
obj.AOtask{i} = NationalInstruments.DAQmx.Task;
obj.AOtask{i}.AOChannels.CreateVoltageChannel(obj.AOchans{2}, '',-10, 10, AOVoltageUnits.Volts);
obj.AOtask{i}.Stream.WriteRegenerationMode = WriteRegenerationMode.AllowRegeneration;
obj.AOtask{i}.Timing.ConfigureSampleClock('PFI7', obj.AOrate, SampleClockActiveEdge.Rising, SampleQuantityMode.ContinuousSamples, 100)
obj.AOtask{i}.Triggers.StartTrigger.ConfigureDigitalEdgeTrigger('PFI0', DigitalEdgeStartTriggerEdge.Rising);
obj.AOtask{i}.Control(TaskAction.Verify);
obj.AOwriter{i} = AnalogMultiChannelWriter(obj.AOtask{i}.Stream);
end
for i = 1:numel(obj.AOchans)
obj.ParkTask{i} = NationalInstruments.DAQmx.Task;
obj.ParkTask{i}.AOChannels.CreateVoltageChannel(obj.AOchans{i}, '',-10, 10, AOVoltageUnits.Volts);
obj.ParkTask{i}.Control(TaskAction.Verify);
obj.ParkWriter{i} = AnalogMultiChannelWriter(obj.ParkTask{i}.Stream);
end
obj.GateTask = NationalInstruments.DAQmx.Task;
obj.GateTask.DOChannels.CreateChannel(obj.gateline,'',ChannelLineGrouping.OneChannelForEachLine);
obj.GateTask.Stream.WriteRegenerationMode = WriteRegenerationMode.AllowRegeneration;
obj.GateTask.Timing.ConfigureSampleClock('', obj.AOrate, SampleClockActiveEdge.Rising, SampleQuantityMode.ContinuousSamples, 100)
obj.GateTask.Timing.SampleClockTimebaseSource = obj.AOtask{1}.Timing.SampleClockTimebaseSource;
obj.GateTask.Timing.SampleClockTimebaseRate = obj.AOtask{1}.Timing.SampleClockTimebaseRate;
obj.GateTask.Triggers.StartTrigger.ConfigureDigitalEdgeTrigger('PFI0', DigitalEdgeStartTriggerEdge.Rising);
obj.GateTask.Control(TaskAction.Verify);
obj.GateTaskWriter = DigitalSingleChannelWriter(obj.GateTask.Stream);
obj.GateCloseTask = NationalInstruments.DAQmx.Task;
obj.GateCloseTask.DOChannels.CreateChannel(obj.gateline,'',ChannelLineGrouping.OneChannelForEachLine);
obj.GateCloseTask.Control(TaskAction.Verify);
obj.GateCloseWriter = DigitalSingleChannelWriter(obj.GateCloseTask.Stream);
obj.ShutterTask = NationalInstruments.DAQmx.Task;
obj.ShutterTask.DOChannels.CreateChannel(obj.shutterline,'',ChannelLineGrouping.OneChannelForEachLine);
obj.ShutterTask.Control(TaskAction.Verify);
obj.ShutterWriter = DigitalSingleChannelWriter(obj.ShutterTask.Stream);
obj.TriggerTask = NationalInstruments.DAQmx.Task;
primaryDev = strtok(obj.AIchans, '/');
obj.TriggerTask.COChannels.CreatePulseChannelTime(['/' primaryDev '/Ctr0'], '', COPulseTimeUnits.Seconds, COPulseIdleState.Low, 0, 0.1, 0.1);
obj.TriggerTask.Control(TaskAction.Verify);
fStatus(4/6, 'starting up: adding stage...');
if ~isempty(obj.stageCreator)
obj.stage = obj.stageCreator();
end
fStatus(5/6, 'starting up: adding power control...');
if ~isempty(obj.powercontrolCreator)
obj.powercontrol = obj.powercontrolCreator();
end
fStatus(1); fprintf(1, '\n');
end
function shutterClose(obj)
% Closes shutter
obj.ShutterWriter.WriteSingleSampleSingleLine(true, false);
end
function shutterOpen(obj)
% Opens shutter
obj.ShutterWriter.WriteSingleSampleSingleLine(true, true);
end
function hwTrigger(obj)
% Launch hardware trigger
obj.TriggerTask.Start
obj.TriggerTask.WaitUntilDone(-1)
obj.TriggerTask.Stop
end
function park(obj)
for iWriter = 1:numel(obj.ParkWriter)
obj.channelOrder{iWriter}
obj.ParkWriter{iWriter}.WriteMultiSample(true, zeros(numel(obj.channelOrder{iWriter}), 2));
end
end
function queueOutputData(obj, scannerOut)
% called to load data to the output cards
import NationalInstruments.DAQmx.*
nsamples = size(scannerOut, 1);
obj.AOtask{1}.Timing.ConfigureSampleClock('', obj.AOrate, SampleClockActiveEdge.Rising, SampleQuantityMode.ContinuousSamples, nsamples)
if ~isempty(obj.laserSyncPort)
obj.AOtask{1}.Timing.SampleClockTimebaseSource = obj.laserSyncPort;
obj.AOtask{1}.Timing.SampleClockTimebaseRate = obj.AIrate;
obj.GateTask.Timing.SampleClockTimebaseSource = obj.laserSyncPort;
obj.GateTask.Timing.SampleClockTimebaseRate = obj.AIrate;
else
obj.AOtask{1}.Timing.SampleClockTimebaseSource = '100MHzTimebase';
obj.AOtask{1}.Timing.SampleClockTimebaseRate = 100e6;
obj.GateTask.Timing.SampleClockTimebaseSource = '100MHzTimebase';
obj.GateTask.Timing.SampleClockTimebaseRate = 100e6;
end
obj.AOwriter{1}.WriteMultiSample(false, scannerOut(:, obj.channelOrder{1})');
obj.GateTaskWriter.WriteMultiSamplePort(false, uint32(scannerOut(:, 4)'>0));
for iWriter = 2:numel(obj.AOwriter)
obj.AOtask{iWriter}.Timing.ConfigureSampleClock('PFI7', obj.AOrate, SampleClockActiveEdge.Rising, SampleQuantityMode.ContinuousSamples, nsamples)
obj.AOwriter{iWriter}.WriteMultiSample(false, scannerOut(:, obj.channelOrder{iWriter})');
end
end
function setupAIlistener(obj, fun, nsamples)
% sets up a samples acquired listener (execute <fun> every <nsamples> samples)
import NationalInstruments.DAQmx.*
buffersize = max([nsamples*2 1000000]);
buffersize = ceil(buffersize/nsamples)*nsamples; %to make sure buffer size is an integer multiple of nsamples
obj.AItask.Timing.ConfigureSampleClock(obj.laserSyncPort,obj.AIrate,SampleClockActiveEdge.Rising,SampleQuantityMode.ContinuousSamples,buffersize);
obj.AItask.EveryNSamplesReadEventInterval = nsamples;
obj.AIlistener = addlistener(obj.AItask, 'EveryNSamplesRead', @(~, ev) fun(obj.pmtPolarity.*obj.AIreader.ReadInt16(nsamples).int16));
end
function start(obj)
% start AI and AO
for iTask = [obj.AOtask {obj.AItask} {obj.GateTask}]
iTask{1}.Start
end
end
function stopAndCleanup(obj, varargin)
% stop everything and cleanup appropriately
obj.shutterClose;
delete(obj.AIlistener)
for iTask = [{obj.AItask} obj.AOtask {obj.GateTask}]
iTask{1}.Stop
iTask{1}.Control(NationalInstruments.DAQmx.TaskAction.Unreserve)
end
obj.GateCloseWriter.WriteSingleSampleSingleLine(true, false);
obj.park();
obj.isScanning = false;
end
end %methods
end %classdef