Data acquisition with pre-trigger using low-level DAQmx functions (daq.ni.NIDAQmx library)

15 views (last 30 days)
Ray
Ray on 14 Oct 2011
For a while now, I've been on-and-off trying to write code for modal testing using a cDAQ chassis and several NI-9233 or NI-9234 cards. For this testing, I need to trigger off of the acceleration signal, and acquire not only the data after the trigger event, but a set number of samples beforehand. This is called a pre-trigger for those unfamiliar. The legacy DAQ toolbox gives a very easy way to do this with USB-9233 cards, but does not support multiple modules. The session-based interface works great with these modules as long as you only want to use functionality that is supported by the DAQmx drivers. Unfortunately, neither using a reference trigger (pre-trigger) nor even triggering off of the signal level at all is supported by the DAQmx drivers for these devices. Implementing the software trigger and maintaining the pre-trigger falls to the end user, therefore.
I've written most of a DAQ program in C to fit my needs, but if everything could be done in MATLAB that would be great, since extensive analysis in C is tedious (no built in complex types, etc). So I took a suggestion from a Mathworks programmer, and tried to use the low-level DAQmx functions from within MATLAB. Using the example piece of code that he gave ( http://www.mathworks.com/matlabcentral/fileexchange/29707 ), I wrote the code below to read in a single accelerometer channel, with acquisition triggered off of the signal level, and maintaining a set pre-trigger. For low sampling frequencies (2560Sa/Sec, for instance), it works just as intended. However, for anything higher than 5kSa/sec, it can't keep up with the sampling speed. A look at profile viewer shows that the call to DAQmxReadAnalogF64 within the for loop (reading one sample at a time), takes around 1/4800 seconds to execute, meaning only sampling frequencies below 4800 would work, and this is with only 1 channel. In practice, I would need this to be able to sample at 51.2kSa/sec for up to 64 channels. The C equivalent to this code that I wrote meets that standard.
So my question is: Why do calls to DAQmx functions from within MATLAB take so long, and is there any way to speed things up enough that this idea would work?
See the code below for what I was trying. Excuse the hard-coding and such; this was just meant to be a quick proof of concept.
function preTriggerInMATLABAttempt(~)
%%Configure task
% initialize task
[status,taskHandle] = daq.ni.NIDAQmx.DAQmxCreateTask (char(0),uint64(0));
if(status~=0), displayErrorInfo(taskHandle); return; end
% add accelerometer channel
[status] = daq.ni.NIDAQmx.DAQmxCreateAIAccelChan(...
taskHandle,... % taskHandle
'cDAQ3Mod1/ai0',... % physicalChannel
'ai0',... % nameToAssignToChannel
daq.ni.NIDAQmx.DAQmx_Val_PseudoDiff,... % terminal Config.
-5,... % minVal
5,... % maxVal
daq.ni.NIDAQmx.DAQmx_Val_AccelUnit_g, ... % units
1000,... % sensitivity
daq.ni.NIDAQmx.DAQmx_Val_mVoltsPerG,... % sensitivity units
daq.ni.NIDAQmx.DAQmx_Val_Internal,... % excitation source
.004, ... % excitation current
blanks(0)); % custom scale name
if(status~=0), displayErrorInfo(taskHandle); return; end
bufferSize = 10000;
samplingFrequency = 5120*2; % Sa/sec
% configure sample clock
[status]=daq.ni.NIDAQmx.DAQmxCfgSampClkTiming (...
taskHandle, blanks(0), samplingFrequency,...
daq.ni.NIDAQmx.DAQmx_Val_Rising,...
daq.ni.NIDAQmx.DAQmx_Val_ContSamps,...
uint64(bufferSize));
if(status~=0), displayErrorInfo(taskHandle); return;end
%%Get Data
threshold = .008;
preTriggerLen = 1000;
totalLen = 10000;
data = zeros(totalLen,1);
preTrigger = zeros(preTriggerLen,1);
% Start task
[status]= daq.ni.NIDAQmx.DAQmxStartTask(taskHandle);
% fill pre-trigger to start
[status,preTrigger,~,~] =...
daq.ni.NIDAQmx.DAQmxReadAnalogF64(...
taskHandle,... % taskHandle
int32(preTriggerLen),... % numSampsPerChan
10.0,... % timeout
uint32(daq.ni.NIDAQmx.DAQmx_Val_GroupByScanNumber),... % fillMode
preTrigger,... % readArray
uint32(preTriggerLen),... % arraySizeInSamps
int32(0),... % sampsPerChanRead
uint32(0)); % reserved
if(status~=0), displayErrorInfo(taskHandle); return; end
tic;
timeoutDuration = 60; % keep looking for a trigger for this many seconds
dataIndex = preTriggerLen;
while(toc<timeoutDuration)
%maintain a circular buffer for the pre-trigger
prevIndex = dataIndex;
if(dataIndex == preTriggerLen)
dataIndex = 1;
else
dataIndex = prevIndex+1;
end
[status,preTrigger(dataIndex),~,~] =...
daq.ni.NIDAQmx.DAQmxReadAnalogF64(...
taskHandle,... % taskHandle
int32(1),... % numSampsPerChan
10.0,... % timeout
uint32(daq.ni.NIDAQmx.DAQmx_Val_GroupByScanNumber),... % fillMode
0,... % readArray
uint32(1),... % arraySizeInSamps
int32(0),... % sampsPerChanRead
uint32(0)); % reserved
if(status~=0), displayErrorInfo(taskHandle); break; end
% Check for trigger condition (rising trigger)
if((preTrigger(dataIndex)>threshold) && (preTrigger(dataIndex) > preTrigger(prevIndex)))
disp('Triggered!');
% If triggered, acquire the rest of the signal
[status,data(preTriggerLen+1:end),~,~] =...
daq.ni.NIDAQmx.DAQmxReadAnalogF64(...
taskHandle,... % taskHandle
int32(totalLen-preTriggerLen),... % numSampsPerChan
10.0,... % timeout
uint32(daq.ni.NIDAQmx.DAQmx_Val_GroupByScanNumber),... % fillMode
data(preTriggerLen+1:end),... % readArray
uint32(totalLen-preTriggerLen),... % arraySizeInSamps
int32(0),... % sampsPerChanRead
uint32(0)); % reserved
if(status~=0), displayErrorInfo(taskHandle); break; end
break;
end
end
% clean up task
status=daq.ni.NIDAQmx.DAQmxStopTask(taskHandle);
status=daq.ni.NIDAQmx.DAQmxClearTask(taskHandle);
% fill in the pre-trigger into the main array
data(1:preTriggerLen) = [preTrigger(dataIndex:end);preTrigger(1:(dataIndex-1))];
% make a time vector
t = 0:1/samplingFrequency:(totalLen-1)/samplingFrequency;
%plot
figure;
plot(t,data);
xlabel('Time,seconds');
ylabel('Acceleration, g''s');
title('Acquired Acceleration Signal From ai0 in cDAQ3Mod1');
end
function displayErrorInfo(taskHandle)
% Get error message length
[numberOfBytes,~] = ...
daq.ni.NIDAQmx.DAQmxGetExtendedErrorInfo(' ', uint32(0));
% Get error message
[~,extMessage] = ...
daq.ni.NIDAQmx.DAQmxGetExtendedErrorInfo(...
blanks(numberOfBytes), uint32(numberOfBytes));
% Clean up the task handle
status=daq.ni.NIDAQmx.DAQmxStopTask(taskHandle);
status=daq.ni.NIDAQmx.DAQmxClearTask(taskHandle);
% Display error pop-up
msgbox(extMessage,'NI-DAQmx Error!','error');
end

Answers (1)

Rob Purser
Rob Purser on 14 Oct 2011
Hi Ray,
This is a really nice piece of code, and our exact intent when we provided undocumented access to the interface. However,I gotta go for the simple question here: Given that you're doing your trigger search in postprocessing, why not just use the Session based interface, and postprocess in MATLAB? I don't see anything here that can't be done in the Session based interface. We added support for accelerometer channels like this in R2011a. You could use the code from our continuous background acquisition demo almost unchanged. To get around the performance issues you encountered, the data acquisition toolbox has a high performance mechanims to ensure that we keep us with the hardware. It'll have no problem doing your 5000 samples/second.
All the best,
-Rob
  2 Comments
Raymond
Raymond on 8 Feb 2013
I would just like to support the suggestion for pre-trigger functionality in the session-based daq.
I rely heavily on the data acquisition toolbox, but I am constantly having to shift between the legacy and session-based daq, because some functions are available in one but not the other. This is obviously takes time and effort. Would be nice if mathworks finish work on the session based stuff!

Sign in to comment.

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!