Main Content

Capture Data with Software-Analog Triggering

This example shows how to implement a triggered data capture based on a trigger condition defined in software. Data Acquisition Toolbox provides functionality for hardware triggering a data acquisition object, for example starting acquisition from a DAQ device based on an external digital trigger signal (rising or falling edge). For some applications however, it is desirable to start capturing or logging data based on the analog signal being measured, allowing for capturing only the signal of interest out of a continuous stream of digitized measurement data (such as an audio recording when the signal level passes a certain threshold).

A custom graphical user interface (UI) is used to display a live plot of the data acquired in continuous mode, and allows you to input trigger parameters values for a custom trigger condition, which is based on the acquired analog input signal level and slope. Captured data is displayed in the interactive UI, and is saved to a MATLAB base workspace variable.

This example can be easily modified to instead use audio input channels with a DirectSound supported audio device.

The code is structured as a single program file, with a main function and several local functions.

Hardware Setup

  • A DAQ device (such as NI USB-6218) with analog input channels, supported by the DataAcquisition interface in background acquisition mode.

  • External signal connections to analog input channels. The data in this example represents measured voltages from a series resistor-capacitor (RC) circuit: total voltage across RC (in this example supplied by a function generator) is measured on channel AI0, and voltage across the capacitor is measured on channel AI1.

Configure Data Acquisition and Capture Parameters (Main Function)

Configure a data acquisition object with two analog input channels and set acquisition parameters. Background continuous acquisition mode provides the acquired data by calling a user defined callback function (dataCapture) when ScansAvailable events occur. A custom graphical user interface (UI) is used for live acquired data visualization and for interactive data capture based on user specified trigger parameters.

function softwareAnalogTriggerCapture
%softwareAnalogTriggerCapture DAQ data capture using software-analog triggering
%   softwareAnalogTriggerCapture launches a user interface for live DAQ data
%   visualization and interactive data capture based on a software analog
%   trigger condition.

% Configure data acquisition object and add input channels
s = daq('ni');
ch1 = addinput(s, 'Dev1', 0, 'Voltage');
ch2 = addinput(s, 'Dev1', 1, 'Voltage');

% Set acquisition configuration for each channel
ch1.TerminalConfig = 'SingleEnded';
ch2.TerminalConfig = 'SingleEnded';
ch1.Range = [-10.0 10.0];
ch2.Range = [-10.0 10.0];

% Set acquisition rate, in scans/second
s.Rate = 10000;

% Specify the desired parameters for data capture and live plotting.
% The data capture parameters are grouped in a structure data type,
% as this makes it simpler to pass them as a function argument.

% Specify triggered capture timespan, in seconds
capture.TimeSpan = 0.45;

% Specify continuous data plot timespan, in seconds
capture.plotTimeSpan = 0.5;

% Determine the timespan corresponding to the block of samples supplied
% to the ScansAvailable event callback function.
callbackTimeSpan = double(s.ScansAvailableFcnCount)/s.Rate;
% Determine required buffer timespan, seconds
capture.bufferTimeSpan = max([capture.plotTimeSpan, capture.TimeSpan * 3, callbackTimeSpan * 3]);
% Determine data buffer size
capture.bufferSize =  round(capture.bufferTimeSpan * s.Rate);

% Display graphical user interface
hGui = createDataCaptureUI(s);

% Configure a ScansAvailableFcn callback function
% The specified data capture parameters and the handles to the UI graphics
% elements are passed as additional arguments to the callback function.
s.ScansAvailableFcn = @(src,event) dataCapture(src, event, capture, hGui);

% Configure a ErrorOccurredFcn callback function for acquisition error
% events which might occur during background acquisition
s.ErrorOccurredFcn = @(src,event) disp(getReport(event.Error));

% Start continuous background data acquisition
start(s, 'continuous')

% Wait until data acquisition object is stopped from the UI
while s.Running
    pause(0.5)
end

% Disconnect from hardware
delete(s)
end

Background Acquisition Callback Function

The dataCapture user-defined callback function is being called repeatedly, each time a ScansAvailable event occurs. With each callback function execution, the latest acquired data block and timestamps are added to a persistent FIFO data buffer, a continuous acquired data plot is updated, latest data is analyzed to check whether the trigger condition is met, and -- once capture is triggered and enough data has been captured for the specified timespan -- captured data is saved in a base workspace variable. The captured data is an N x M matrix corresponding to N acquired data scans, with the timestamps as the first column, and the acquired data corresponding to each channel as columns 2:M.

function dataCapture(src, ~, c, hGui)
%dataCapture Process DAQ acquired data when called by ScansAvailable event.
%  dataCapture processes latest acquired data and timestamps from data
%  acquisition object (src), and, based on specified capture parameters (c
%  structure) and trigger configuration parameters from the user interface
%  elements (hGui handles structure), updates UI plots and captures data.
%
%   c.TimeSpan        = triggered capture timespan (seconds)
%   c.bufferTimeSpan  = required data buffer timespan (seconds)
%   c.bufferSize      = required data buffer size (number of scans)
%   c.plotTimeSpan    = continuous acquired data timespan (seconds)
%

[eventData, eventTimestamps] = read(src, src.ScansAvailableFcnCount, ...
    'OutputFormat', 'Matrix');


% The read data is stored in a persistent buffer (dataBuffer), which is
% sized to allow triggered data capture.
% Since multiple calls to dataCapture will be needed for a triggered
% capture, a trigger condition flag (trigActive) and a corresponding
% data timestamp (trigMoment) are used as persistent variables.
% Persistent variables retain their values between calls to the function.

persistent dataBuffer trigActive trigMoment

% If dataCapture is running for the first time, initialize persistent vars
if eventTimestamps(1)==0
    dataBuffer = [];          % data buffer
    trigActive = false;       % trigger condition flag
    trigMoment = [];          % data timestamp when trigger condition met
    prevData = [];            % last data point from previous callback execution
else
    prevData = dataBuffer(end, :);
end

% Store continuous acquisition timestamps and data in persistent FIFO
% buffer dataBuffer
latestData = [eventTimestamps, eventData];
dataBuffer = [dataBuffer; latestData];
numSamplesToDiscard = size(dataBuffer,1) - c.bufferSize;
if (numSamplesToDiscard > 0)
    dataBuffer(1:numSamplesToDiscard, :) = [];
end


% Update live data plot
% Plot latest plotTimeSpan seconds of data in dataBuffer
samplesToPlot = min([round(c.plotTimeSpan * src.Rate), size(dataBuffer,1)]);
firstPoint = size(dataBuffer, 1) - samplesToPlot + 1;
% Update x-axis limits
xlim(hGui.Axes1, [dataBuffer(firstPoint,1), dataBuffer(end,1)]);
% Live plot has one line for each acquisition channel
for ii = 1:numel(hGui.LivePlot)
    set(hGui.LivePlot(ii), 'XData', dataBuffer(firstPoint:end, 1), ...
                           'YData', dataBuffer(firstPoint:end, 1+ii))
end


% If capture is requested, analyze latest acquired data until a trigger
% condition is met. After enough data is acquired for a complete capture,
% as specified by the capture timespan, extract the capture data from the
% data buffer and save it to a base workspace variable.

% Get capture toggle button value (1 or 0) from UI
captureRequested = hGui.CaptureButton.Value;

if captureRequested && (~trigActive)
    % State: "Looking for trigger event"

    % Update UI status
    hGui.StatusText.String = 'Waiting for trigger';

    % Get the trigger configuration parameters from UI text inputs and
    %   place them in a structure.
    % For simplicity, validation of user input is not addressed in this example.
    trigConfig.Channel = sscanf(hGui.TrigChannel.String, '%u');
    trigConfig.Level = sscanf(hGui.TrigLevel.String, '%f');
    trigConfig.Slope = sscanf(hGui.TrigSlope.String, '%f');

    % Determine whether trigger condition is met in the latest acquired data
    % A custom trigger condition is defined in trigDetect user function
    [trigActive, trigMoment] = trigDetect(prevData, latestData, trigConfig);


elseif captureRequested && trigActive && ((dataBuffer(end,1)-trigMoment) > c.TimeSpan)
    % State: "Acquired enough data for a complete capture"
    % If triggered and if there is enough data in dataBuffer for triggered
    % capture, then captureData can be obtained from dataBuffer.

    % Find index of sample in dataBuffer with timestamp value trigMoment
    trigSampleIndex = find(dataBuffer(:,1) == trigMoment, 1, 'first');
    % Find index of sample in dataBuffer to complete the capture
    lastSampleIndex = round(trigSampleIndex + c.TimeSpan * src.Rate());
    captureData = dataBuffer(trigSampleIndex:lastSampleIndex, :);

    % Reset trigger flag, to allow for a new triggered data capture
    trigActive = false;

    % Update captured data plot (one line for each acquisition channel)
    for ii = 1:numel(hGui.CapturePlot)
        set(hGui.CapturePlot(ii), 'XData', captureData(:, 1), ...
                                  'YData', captureData(:, 1+ii))
    end

    % Update UI to show that capture has been completed
    hGui.CaptureButton.Value = 0;
    hGui.StatusText.String = '';

    % Save captured data to a base workspace variable
    % For simplicity, validation of user input and checking whether a variable
    % with the same name already exists are not addressed in this example.
    % Get the variable name from UI text input
    varName = hGui.VarName.String;
    % Use assignin function to save the captured data in a base workspace variable
    assignin('base', varName, captureData);

elseif captureRequested && trigActive && ((dataBuffer(end,1)-trigMoment) < c.TimeSpan)
    % State: "Capturing data"
    % Not enough acquired data to cover capture timespan during this callback execution
    hGui.StatusText.String = 'Triggered';

elseif ~captureRequested
    % State: "Capture not requested"
    % Capture toggle button is not pressed, set trigger flag and update UI
    trigActive = false;
    hGui.StatusText.String = '';
end

drawnow

end

Create a Graphical User Interface for Live Data Capture

Create a user interface programmatically, by creating a figure, one plot for live acquired data, one plot for captured data, buttons for starting capture and stopping acquisition, and text fields for entering trigger configuration parameters and status update.

For simplicity, the figure and all user interface components have a fixed size and position defined in pixels. For high DPI displays the position values might have to be adjusted for optimum dimensions and layout. Another option for creating a custom UI is to use App Designer.

function hGui = createDataCaptureUI(s)
%createDataCaptureUI Create a graphical user interface for data capture.
%   hGui = createDataCaptureUI(s) returns a structure of graphics
%   components handles (hGui) and creates a graphical user interface, by
%   programmatically creating a figure and adding required graphics
%   components for visualization of data acquired from a data acquisition
%   object (s).

% Create a figure and configure a callback function (executes on window close)
hGui.Fig = figure('Name','Software-analog triggered data capture', ...
    'NumberTitle', 'off', 'Resize', 'off', ...
    'Toolbar', 'None', 'Menu', 'None',...
    'Position', [100 100 750 650]);
hGui.Fig.DeleteFcn = {@endDAQ, s};
uiBackgroundColor = hGui.Fig.Color;

% Create the continuous data plot axes with legend
% (one line per acquisition channel)
hGui.Axes1 = axes;
hGui.LivePlot = plot(0, zeros(1, numel(s.Channels)));
xlabel('Time (s)');
ylabel('Voltage (V)');
title('Continuous data');
legend({s.Channels.ID}, 'Location', 'northwestoutside')
hGui.Axes1.Units = 'Pixels';
hGui.Axes1.Position = [207 391 488 196];
% Turn off axes toolbar and data tips for live plot axes
hGui.Axes1.Toolbar.Visible = 'off';
disableDefaultInteractivity(hGui.Axes1);

% Create the captured data plot axes (one line per acquisition channel)
hGui.Axes2 = axes('Units', 'Pixels', 'Position', [207 99 488 196]);
hGui.CapturePlot = plot(NaN, NaN(1, numel(s.Channels)));
xlabel('Time (s)');
ylabel('Voltage (V)');
title('Captured data');
hGui.Axes2.Toolbar.Visible = 'off';
disableDefaultInteractivity(hGui.Axes2);

% Create a stop acquisition button and configure a callback function
hGui.DAQButton = uicontrol('style', 'pushbutton', 'string', 'Stop DAQ',...
    'units', 'pixels', 'position', [65 394 81 38]);
hGui.DAQButton.Callback = {@endDAQ, s};

% Create a data capture button and configure a callback function
hGui.CaptureButton = uicontrol('style', 'togglebutton', 'string', 'Capture',...
    'units', 'pixels', 'position', [65 99 81 38]);
hGui.CaptureButton.Callback = {@startCapture, hGui};

% Create a status text field
hGui.StatusText = uicontrol('style', 'text', 'string', '',...
    'units', 'pixels', 'position', [67 28 225 24],...
    'HorizontalAlignment', 'left', 'BackgroundColor', uiBackgroundColor);

% Create an editable text field for the captured data variable name
hGui.VarName = uicontrol('style', 'edit', 'string', 'mydata',...
    'units', 'pixels', 'position', [87 159 57 26]);
% Create an editable text field for the trigger channel
hGui.TrigChannel = uicontrol('style', 'edit', 'string', '1',...
    'units', 'pixels', 'position', [89 258 56 24]);
% Create an editable text field for the trigger signal level
hGui.TrigLevel = uicontrol('style', 'edit', 'string', '1.0',...
    'units', 'pixels', 'position', [89 231 56 24]);
% Create an editable text field for the trigger signal slope
hGui.TrigSlope = uicontrol('style', 'edit', 'string', '200.0',...
    'units', 'pixels', 'position', [89 204 56 24]);
% Create text labels
hGui.txtTrigParam = uicontrol('Style', 'text', 'String', 'Trigger parameters', ...
    'Position', [39 290 114 18], 'BackgroundColor', uiBackgroundColor);
hGui.txtTrigChannel = uicontrol('Style', 'text', 'String', 'Channel', ...
    'Position', [37 261 43 15], 'HorizontalAlignment', 'right', ...
    'BackgroundColor', uiBackgroundColor);
hGui.txtTrigLevel = uicontrol('Style', 'text', 'String', 'Level (V)', ...
    'Position', [35 231 48 19], 'HorizontalAlignment', 'right', ...
    'BackgroundColor', uiBackgroundColor);
hGui.txtTrigSlope = uicontrol('Style', 'text', 'String', 'Slope (V/s)', ...
    'Position', [17 206 66 17], 'HorizontalAlignment', 'right', ...
    'BackgroundColor', uiBackgroundColor);
hGui.txtVarName = uicontrol('Style', 'text', 'String', 'Variable name', ...
    'Position', [35 152 44 34], 'BackgroundColor', uiBackgroundColor);

end

function startCapture(obj, ~, hGui)
if obj.Value
    % If button is pressed clear data capture plot
    for ii = 1:numel(hGui.CapturePlot)
        set(hGui.CapturePlot(ii), 'XData', NaN, 'YData', NaN);
    end
end
end

function endDAQ(~, ~, s)
if isvalid(s)
    if s.Running
        stop(s);
    end
end
end
Background operation has started.
To stop the background operation, use stop.
To read acquired scans, use read.

Detect Trigger Condition in Acquired Data

In this example, the trigger condition is defined by the signal level on the trigger channel and the corresponding slope. Depending on the application and actual data being acquired, data filtering or more complex trigger conditions can be implemented.

function [trigDetected, trigMoment] = trigDetect(prevData, latestData, trigConfig)
%trigDetect Detect if trigger condition is met in acquired data
%   [trigDetected, trigMoment] = trigDetect(prevData, latestData, trigConfig)
%   Returns a detection flag (trigDetected) and the corresponding timestamp
%   (trigMoment) of the first data point which meets the trigger condition
%   based on signal level and slope specified by the trigger parameters
%   structure (trigConfig).
%   The input data (latestData) is an N x M matrix corresponding to N acquired
%   data scans, with the timestamps as the first column, and channel data
%   as columns 2:M. The previous data point prevData (1 x M vector of timestamp
%   and channel data) is used to determine the slope of the first data point.
%
%   trigConfig.Channel = index of trigger channel in data acquisition object channels
%   trigConfig.Level   = signal trigger level (V)
%   trigConfig.Slope   = signal trigger slope (V/s)

% Condition for signal trigger level
trigCondition1 = latestData(:, 1+trigConfig.Channel) > trigConfig.Level;

data = [prevData; latestData];

% Calculate slope of signal data points
% Calculate time step from timestamps
dt = latestData(2,1)-latestData(1,1);
slope = diff(data(:, 1+trigConfig.Channel))/dt;

% Condition for signal trigger slope
trigCondition2 = slope > trigConfig.Slope;

% If first data block acquired, slope for first data point is not defined
if isempty(prevData)
    trigCondition2 = [false; trigCondition2];
end

% Combined trigger condition to be used
trigCondition = trigCondition1 & trigCondition2;

trigDetected = any(trigCondition);
trigMoment = [];
if trigDetected
    % Find time moment when trigger condition has been met
    trigTimeStamps = latestData(trigCondition, 1);
    trigMoment = trigTimeStamps(1);
end
end