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;
|