File: DatarecordingFromISCANDemo.m

package info (click to toggle)
psychtoolbox-3 3.0.19.14.dfsg1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 86,796 kB
  • sloc: ansic: 176,245; cpp: 20,103; objc: 5,393; sh: 2,753; python: 1,397; php: 384; makefile: 193; java: 113
file content (349 lines) | stat: -rw-r--r-- 14,213 bytes parent folder | download | duplicates (6)
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
function DatarecordingFromISCANDemo(portSpec, sampleFreq, maxComponents, baudRate, specialSettings)
% Template for asynchronous data collection and timestamping from serial port.
%
% DatarecordingFromSerialPortDemo([portSpec=auto-detect][, sampleFreq=240][maxComponents=2][, baudRate=115200][, specialSettings=None])
%
% This demo shows how to perform efficient data recording of data from an
% ISCAN eyetracker which is connected to the serial port or a USB-Serial
% converter. The device is assumed to stream data in packets of at most
% 'maxComponents' gaze sample components in ASCII streaming mode.
%
% The demo connects to the first found serial port, or optionally the port
% given by the 'portSpec' parameter, e.g., 'COM5'. It connects at a
% baudrate of 'baudRate' Baud, by default without flow-control, with 8
% databits, 1 stopbit and no parity, but you can set arbitrary settings via
% the optional 'specialSettings' string (see IOPort OpenSerialPort?
% online-help for possible parameters).
%
% Then it allocates receivebufferspace for up to 1 hour of uninterrupted
% recording, then starts background recording of data.
%
% Datapackets can be read out in realtime, as demonstrated in the main
% while loop here, or offline at end of a session. Each packet is padded to
% be 'maxReadQuantum' bytes in size (zeros are added for shorter packets).
% Each packet comes with a GetSecs() timestamp of when the first byte of a
% packet was received.
%
% Of course you'll need to understand the code of this demo and then
% customize it for your needs. We don't know what kind of weird stuff
% you're going to connect, but the default parameter settings may work on
% some simple devices like eyetrackers.
%
% For accurate timestamping and data reception with low latency, make sure
% that you've configured your serial ports properly. Search the
% Psychtoolbox forum for posts on that topic, e.g., message 9873.
%
% The following setup has to be performed in the ISCAN DQW software,
% citing ISCAN software documentation:
%
% To enable the serial output capability in DQW: In the DQW "System
% Configuration" dialog, select the "Serial I/O" tab. Of the available COM
% channels, specify the COM port to be used as "Data I/O", the baud rate to
% match the receiving system, and select "Output" as active. Click on "OK"
% to register changes to the system configuration. Click on the "Options"
% button until the "Auxiliary Output Controls" panel appears as the lower
% left DQW screen. Then click on the "Serial" tab if this is not already in
% the foreground. Choose "Raw Binary" or "Raw ASCII" as the output type
% depending on your preference. Two parameter banks with six parameters
% each, for a total of up to 12 parameters may be output with each data
% sample. To begin, select "Param Bank" 1 and fill the channels (01 -> 06)
% with the desired parameters by selecting from the pop up list activated
% by clicking on the "..." button to the right of each channel. Fill the
% channels in order and be sure that unused channels have parameter
% "................" selected to minimize data transmission time. If more
% than 6 parameters are desired, select "Param Bank" 2 and proceed as above
% to fill in channels 07 -> 12. With the above steps completed, whenever
% "Track Active" is enabled a data packet will be output via the specified
% COM port for each system data sample. The data packet will consist of
% each of the non-blank channel data values for the parameters specified in
% channel order (01 -> 12).
%
% To enable the serial input capability in DQW: In the DQW "System
% Configuration" dialog, select the "Serial I/O" tab. Of the available COM
% channels, specify the COM port to be used as "Data I/O", the baud rate to
% match the remote data sending system, and select "Input" as active. Click
% on "OK" to register changes to the system configuration. With the above
% steps completed, the DQW system is ready to receive serial commands from
% a remote source. All DQW remote serial commands are single bytes between
% 128 and 255 (80 -> FF Hex). Whenever "Track Active" is enabled, the DQW
% system can receive and record any single serial byte between 0 and 127
% (00 and 7F Hex) as a synchronizing data marker. These bytes are
% represented as parameter "SerIn0" wherever parameter selections are made
% for display or recording in the DQW program. To trigger data recording
% via the serial port, in the DQW "System Configuration" dialog, select the
% ?Recording? tab and set the recording ?Trigger? to ?Serial.? 
%
% Thanks to Sebastian Moeller for finding this out.
%

% History:
% 16.9.09  mk  Written, derived from DataRecordingFromSerialPortDemo.

% joker variable: Ignore this! Only for MK's internal testing:
joker = '';

% MK: Only for testing at MK's site: joker = 'Lenient';

% maxReadQuantum provided?
if ~exist('maxComponents', 'var')
    maxComponents = [];
end

if isempty(maxComponents)
    % Assume that a single datapacket is no more than 16 Bytes, including
    % any terminator bytes:
    maxComponents = 2;
end

% Each component is 7 Bytes (space + xxx.x digits + tab delimiter), and
% there is a CR+LF combo at the end with the lineTerminator being the last
% entry:
maxReadQuantum = maxComponents * 7 + 2;

% Line terminator for ISCAN is ASCII code 10, aka LF, aka NL aka newline/linefeed:
lineTerminator = 10;

% sampleFreq provided?
if ~exist('sampleFreq', 'var')
    sampleFreq = [];
end

if isempty(sampleFreq)
    % Choose an expected sampling frequency for incoming data packets of
    % 240 Hz for the ISCAN:
    sampleFreq = 240;
end

% Baudrate provided?
if ~exist('baudRate', 'var')
    baudRate = [];
end

if isempty(baudRate)
    % Choose an optimitic default of 115.2 KBaud:
    baudRate = 115200;
end

% Any serial port specification provided?
if ~exist('portSpec', 'var')
    portSpec = [];
end

if isempty(portSpec)
    % Try to auto-detect a suitable serial port:
    portSpec = FindSerialPort([], 1);
end

% Any serial specialSettings provided?
if ~exist('specialSettings', 'var')
    % Set to empty aka "use defaults" which is 8-N-1 encoding, no flow
    % control...
    specialSettings = [];
end

% Compute maximum input buffer size for 1 hour worth of datapackets coming
% in at a expected sampleFreq Hz with a size of at most maxReadQuantum Bytes
% each:
InputBufferSize = maxReadQuantum * sampleFreq * 3600;

% Assign an interbyte readtimeout which is either 15 seconds, or 10 times
% the expected time between consecutive datapackets at the given sampleFreq
% sampling frequency, whatever's higher. Could go higher or lower than
% this, but this seems a reasonable starter: Will give code and devices
% time to start streaming, but will prevent script from hanging longer than
% 15 seconds if something goes wrong with the connection:
readTimeout = max(10 * 1/sampleFreq, 15);

% HACK: Restrict maximum timeout to 21 seconds. This is needed on Macintosh
% computers, because at least OS/X 10.4.11 seems to have a bug which can
% cause the driver to hang when trying to stop at the end of a session if
% the timeout value is set higher than 21 seconds!
readTimeout = min(readTimeout, 21);

% Assemble initial configuration string for opening the port with
% reasonable settings: Given special settings, baudrate, inputbuffersize.
% Also set the special delimiter character code 'lineTerminator' that
% signals the end of a valid data packet:
portSettings = sprintf('%s %s BaudRate=%i InputBufferSize=%i Terminator=%i ReceiveTimeout=%f ReceiveLatency=0.001', joker, specialSettings, baudRate, InputBufferSize, lineTerminator, readTimeout );

% Open port portSpec with portSettings, return handle:
myport = IOPort('OpenSerialPort', portSpec, portSettings);

fprintf('Link online: Hit a key on keyboard to start data recording, after that hit any key to finish data collection.\n');
KbStrokeWait;

% ---- Here you'd put any IOPort setup calls, e.g., write and read commands
% to setup your device, enable streaming of data etc...

iscan_start_code = char(hex2dec('84'));
iscan_stop_code = char(hex2dec('88'));

% Stop tracker, if not already stopped:
IOPort('Write', myport, iscan_stop_code);

% Wait a bit...
WaitSecs(1);

% Purge data buffers, read and discard remaining junk:
IOPort('Purge', myport);

WaitSecs(0.5);

IOPort('Read', myport);

% Really empty? Hope so.
if IOPort('BytesAvailable', myport) > 0
    warning('Trouble ahead!! Check your eyetracker!'); %#ok<WNTAG>
end

% All buffers empty, device idle, ready to go:

% Configure background read.
asyncSetup = sprintf('%s BlockingBackgroundRead=1 ReadFilterFlags=4', joker);
IOPort('ConfigureSerialPort', myport, asyncSetup);

% Start tracker:
IOPort('Write', myport, iscan_start_code);

% ---- End of device specific setup ----

% Start asynchronous background data collection and timestamping. Use
% blocking mode for reading data -- easier on the system:
asyncSetup = sprintf('%s StartBackgroundRead=%i', joker, maxReadQuantum);
IOPort('ConfigureSerialPort', myport, asyncSetup);

% Data collection started: From now on, the driver will read data from the
% serial port, byte by byte. Whenever either maxReadQuantum bytes have
% accumulated or the packet delimiter code 'lineTerminator' is detected,
% the driver assumes a packet is complete. If less than 'maxReadQuantum'
% bytes have been received before the 'lineTerminator' is detected, the
% driver will pad the remaining bytes with zeros. --> Any packet will
% always be exactly 'maxReadQuantum' bytes in size, zero-padded or not.
%
% Reception of the first byte of a new packet is timestamped in GetSecs()
% time, and the later IOPort('Read') calls will return those timestamps...

% ---- From here on, only a limited set of IOPOrt commands is allowed if
% you are working on MS-Windows. On OS/X and Linux no such restrictions
% apply afaik.


% Alloc timestamp array for 1 hour worth of samples at 'sampleFreq' Hz:
tpkt = zeros(1, sampleFreq * 3600);
count = 0;

% Fake experiment loop: Run until any key is pressed:
while ~KbCheck
    % Wait blocking for a new data packet of 'maxReadQuantum' bytes from
    % the serial port, then return the packet data as uint8's plus the
    % GetSecs receive timestamp 'treceived' of the start of each packet:
    [pktdata, treceived] = IOPort('Read', myport, 1, maxReadQuantum);
    
    % Empty data packet?
    if ~any(pktdata)
        % All zeroes: An empty packet due to error? Skip it.
        fprintf('Empty packet received (all zeros) --> SKIPPED\n');
        % Back to start of while loop:
        continue;
    end
    
    % Some data available to process. For the sake of demonstration assume
    % the pktdata actually encodes an ASCII text string. Convert it to a
    % string, print it and its timestamp. We deblank() to strip trailing
    % zero bytes:
    pktstring = deblank(char(pktdata));
    fprintf('ReceiveTime: %f seconds GetSecs time: Datastring = %s\n', treceived, pktstring);
    
    % Store timestamp for the fun of it:
    count = count + 1;
    tpkt(count) = treceived;
    
    % HERE YOU'D DO something useful with the data...
    
    
    % Next loop iteration...
end

% We'd like to stop data collection here. However there may be still data
% queued up in the receive buffers, so we will now try to get all data that
% has been received up to this point in time:
tEnd = GetSecs;

fprintf('TRIAL LOOP STOPPED AT t = %f seconds. Now fetching pending data up to that point...\n', tEnd);

% Fetch all pending data that has been received up to systemtime tEnd:
while treceived < tEnd
    % Same as above, but now a non-blocking read (flag == 0), ie., once the
    % end of the dataqueue is reached, simply return an empty 'pktdata'
    % variable immediately:
    [pktdata, treceived] = IOPort('Read', myport, 0, maxReadQuantum);
    
    % Empty data packet?
    if isempty(pktdata) || ~any(pktdata)
        % All zeroes or an empty packet. We stop here.
        fprintf('Empty packet received (all zeros) --> END OF DATA FETCH\n');
        % Break out of loop:
        break;
    end
    
    % Some data available to process. For the sake of demonstration assume
    % the pktdata actually encodes an ASCII text string. Convert it to a
    % string, print it and its timestamp. We deblank() to strip trailing
    % zero bytes:
    pktstring = deblank(char(pktdata));
    fprintf('ReceiveTime: %f seconds GetSecs time: Datastring = %s\n', treceived, pktstring);
    
    % Store timestamp for the fun of it:
    count = count + 1;
    tpkt(count) = treceived;

    % Next iteration...
end
    
% All data up to time tEnd fetched. There may be more data lingering in the
% queue but we don't care...


% End of data recording. Stop background read operation. This will discard
% any remaining data in the receive buffers. Stopping is a bit tricky if
% the source device doesn't transmit anymore. Then this may hang until the
% ReceiveTimeout timeout value expires, ie., up to 'readTimeout' seconds.
%
% Therefore it is better to only stop data transmission from the sending
% device after we have executed the following stop commands.
fprintf('STOPPING DATA COLLECTION: This may take up to %f seconds...\n', readTimeout);
% This line may or may not help if you experience hangs at this place: IOPort('ConfigureSerialPort', myport, [joker ' BlockingBackgroundRead=0']);
IOPort('ConfigureSerialPort', myport, [joker ' StopBackgroundRead']);

% Now the driver has discarded the data and is in synchronous manual mode
% of operation again. You could add any kind of IOPort commands to shut
% down your serial device now...
fprintf('SHUTTING DOWN DEVICE\n');

% Stop tracker:
IOPort('Write', myport, iscan_stop_code);

% Wait a bit...
WaitSecs(1);

% Close port and driver:
fprintf('CLOSING SERIAL PORT\n');
IOPort('Close', myport);
fprintf('DONE.\n');

% Plot delta between consecutive received packets for the fun of it:
tpkt = tpkt(1:count);

% Deltas in msecs:
tpkt = 1000 * diff(tpkt);

% Print mean delay and sample frequency:
fprintf('Mean delay between packets is %f msecs, i.e., %f Hz real datarate vs. expected %f Hz.\n', mean(tpkt), 1000/mean(tpkt), sampleFreq);

% Plot it:
close all;
plot(tpkt);

fprintf('Done. Bye!\n');

return;