% SW_PRETRIGGER
% A simple analog data acquisition gui with multiple channels and custom
% software pre-triggering
%
% Requires: the Data Acquisition Toolbox and the Winsound adapter. It does
% not depend on any hardware-specific capabilities.
%
% This file demonstrates how to:
%
% 1) Use nested functions to simplify data acquisition programs
%
% 2) Use a simple GUI (e.g., a couple of pushbuttons) to start and stop
% data acquisition.
%
% 3) Wait for an event/trigger (like a button press) and log pre-event or
% pre-trigger data from multiple synchronized channels. (See more
% detailed explanation below).
%
% 4) Record pretrigger data based on custom criteria. In the example below,
% the triggering event is a button click. However, the event could be
% any custom criterion, such as:
% * the data amplitude is greater than a threshold *and* a toggle button
% is enabled, or
% * the short-term correlation between the two channels is greater than
% some threshold
%
% ----------------------------
% RECORDING PRE-TRIGGER DATA WITH MULTIPLE SYNCHRONIZED CHANNELS
% (without using any hardware-specific capabilities)
%
% The Data Acquisition Toolbox supports triggered acquisition and
% "pre-triggering" (i.e., the acquisition includes samples that occurred
% before the trigger event).
%
% However pretriggering can be tricky if you have multiple *synchronized*
% channels, as the hardware may not support synchronized acquisition of the
% pretrigger data. In this situation, it is simpler to do the
% pre-triggering yourself. The key idea is the following:
%
% * The DAQ hardware is started, synchronized and triggered at the
% beginning of the session
% * The data is acquired continuously from all the channels and added to a
% circular buffer (http://en.wikipedia.org/wiki/Circular_buffer).
% * When the triggering is initiated (by pressing a button in a GUI)
% the pretrigger data is extracted from the circular buffer and logged.
%
% ----------------------------
% RELEVANT MATLAB DOCUMENTATION
%
% Data Acquisition Toolbox
% http://www.mathworks.com/access/helpdesk/help/toolbox/daq/index.html
%
% Configuring analog input triggers
% http://www.mathworks.com/access/helpdesk/help/toolbox/daq/f9-49610.html
%
% Configuring the trigger delay (negative delay records "pretrigger" data)
% This is effective for single channels, but it does not guarantee
% synchronized acquisition across multiple channels.
% http://www.mathworks.com/access/helpdesk/help/toolbox/daq/triggerdelay.html
function sw_pretrigger
if ~(exist('analoginput','file') == 2)
error('This example requires the Data Acquisition Toolbox');
end
% ---------- create the gui
myFigure = figure('units', 'pixels', ...
'menubar', 'none', ...
'DeleteFcn', @figureDelete);
pos = get(myFigure,'Position');
set(myFigure, 'Position', [pos(1:2) 130 130]);
uicontrol('style', 'pushbutton', ...
'string', 'Start data logging', ...
'position', [10 60 100 40], ...
'callback', @startLogging, ...
'parent', myFigure);
uicontrol('style', 'pushbutton', ...
'string', 'Stop data logging', ...
'position', [10 10 100 40], ...
'callback', @stopLogging, ...
'parent', myFigure);
% ----------- configure the analog input objects
% Using winsound for demo purposes.
% Winsound doesn't allow selection of only channel 2, so
% myAIObj0 and myAIObj1 both use channel 1.
myAIObj0 = analoginput('winsound');
addchannel(myAIObj0, 1);
myAIObj1 = analoginput('winsound');
addchannel(myAIObj1, 1);
% code to synchronize the channels (e.g., configuring an external
% clock or using HWDigitialTrigger) would go here
mySampleRate = 11025; % sample rate in hertz
set([myAIObj0 myAIObj1], 'SampleRate', mySampleRate, ...
'TriggerType', 'Manual', ...
'SamplesPerTrigger', inf);
nativeDataType = daqhwinfo(myAIObj0, 'NativeDataType');
% The samples are stored in a circular buffer, which is a
% computationally efficient way of storing the samples over the past 4
% seconds (myBufferRefillPeriod * myNumBuffers = 4).
myBufferRefillPeriod = 0.4; % in seconds
myBufferSize = ceil(myBufferRefillPeriod * mySampleRate);
myNumBuffers = 10;
myNumChannels = 2;
myCurrentBuffer = 1; % index of current buffer
% the set of buffers
myCircularBuffers = zeros(myBufferSize, myNumChannels, ...
myNumBuffers, nativeDataType);
% the acquisition time of the first sample of each buffer
myCircularBufferTimes = -1 * ones(myNumBuffers, 2);
% The channels are presumed to be time-locked, so we only need to
% configure SamplesAcquiredFcn for one channel
set(myAIObj0, 'SamplesAcquiredFcnCount', myBufferSize, ...
'SamplesAcquiredFcn', @refillBuffers);
myDataBeingLogged = false;
start([myAIObj0 myAIObj1]);
trigger([myAIObj0 myAIObj1]);
%% --------------------------------------
function figureDelete(obj,eventdata) %#ok<INUSD,INUSD>
% figure got deleted, so clean up analoginput objects
stop([myAIObj0 myAIObj1]);
delete([myAIObj0 myAIObj1]);
fprintf('Cleaned up analoginput objects\n');
end
%% --------------------------------------
function startLogging(obj,eventdata) %#ok<INUSD,INUSD>
if myDataBeingLogged
return;
end
% reorder the buffers to get the right time sequence
indices = [myCurrentBuffer:myNumBuffers 1:myCurrentBuffer-1];
% If you have additional trigger conditions, put the logic here.
% proceedWithLogging = customCriterion(myCircularBuffers, indices);
% if ~proceedWithLogging
% return;
% end
logStartTime = get(myAIObj0,'SamplesAcquired') / get(myAIObj0, 'SampleRate');
fprintf('\nStarting data logging (trigger at %4.4f sec)\n', logStartTime);
% log the pre-trigger data
fprintf('Pre-trigger data\n');
for i = indices
if myCircularBufferTimes(i) >= 0
logData(myCircularBuffers(:,:,i), myCircularBufferTimes(i,:));
end
end
% all the data logged from here on is post-trigger
fprintf('Post-trigger data\n');
myDataBeingLogged = true;
end
%% --------------------------------------
function stopLogging(obj,eventdata) %#ok<INUSD,INUSD>
if ~myDataBeingLogged
return;
end
fprintf('Stopping data logging\n');
myDataBeingLogged = false;
end
%% --------------------------------------
function refillBuffers(obj,eventdata) %#ok<INUSD,INUSD>
ai0NumSamples = get(myAIObj0, 'SamplesAvailable');
ai1NumSamples = get(myAIObj1, 'SamplesAvailable');
if ai0NumSamples < myBufferSize || ai1NumSamples < myBufferSize
return;
end
[ai0data,ai0time] = getdata(myAIObj0, myBufferSize, 'native');
ai1data = getdata(myAIObj1, myBufferSize, 'native');
newData = [ai0data ai1data];
newDataTime = [ai0time(1) ai0time(end)];
if myDataBeingLogged
logData(newData, newDataTime);
end
myCircularBuffers(:,:,myCurrentBuffer) = newData;
myCircularBufferTimes(myCurrentBuffer,:) = newDataTime;
if myCurrentBuffer == myNumBuffers
myCurrentBuffer = 1;
else
myCurrentBuffer = myCurrentBuffer + 1;
end
end
%% --------------------------------------
function logData(data,times)
[nsamples, nchannels] = size(data);
fprintf(' Logging %04d samples x %d channels (samples from %2.4f to %2.4f sec)\n', ...
nsamples, nchannels, times(1), times(2));
% custom code for logging the data (e.g., saving to a file or
% plotting) would go here
end
end