File: DatarecordingFromSerialPortDemo.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 (278 lines) | stat: -rw-r--r-- 10,784 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
function DatarecordingFromSerialPortDemo(maxReadQuantum, lineTerminator, sampleFreq, baudRate, portSpec, specialSettings)
% Template for asynchronous data collection and timestamping from serial port.
%
% DatarecordingFromSerialPortDemo([maxReadQuantum=15][, lineTerminator=10][, sampleFreq=120][, baudRate=115200][, portSpec=auto-detect][, specialSettings=None])
%
% This demo shows how to perform efficient data recording of data from an
% external device which is connected to the serial port or a USB-Serial
% converter. The device is assumed to stream data in packets of at most
% 'maxReadQuantum' bytes, each packet ending with a special ASCII or byte
% code 'lineTerminator', the packets streaming at a rate of 'sampleFreq'
% Hz. Typical candidates would be serial port data acquisition devices or
% eyetrackers.
%
% 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.
%

% History:
% 18.8.09  mk  Written.

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

%joker = 'Lenient';

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

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

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

if isempty(lineTerminator)
    % Default to ASCII code 10, aka LF, aka NL aka newline/linefeed:
    lineTerminator = 10;
end

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

if isempty(sampleFreq)
    % Choose an expected sampling frequency for incoming data packets of
    % 120 Hz:
    sampleFreq = 120;
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', 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...


% ---- 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 BlockingBackgroundRead=1 ReadFilterFlags=4 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');


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