MATLAB Answers

Ray
0

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

Asked by 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

  0 Comments

Sign in to comment.

1 Answer

Answer by 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

Hi Rob,
Thanks for your response.
I'm actually not doing the trigger search in post-processing here. Perhaps I didn't make my code clear enough. One sample is read in at a time, and after that sample is read, the program checks for a trigger condition. If that trigger condition is not met, samples continue to be read and stored in the pre-trigger buffer. If the condition is met, the user is immediately notified that a trigger has occurred, and the remaining samples are read. The effect of this is, with the values used in the script, every recorded signal will have 1000 samples before the trigger event, and 9000 samples after that event. No unnecessary data is collected after the desired duration, and the rising event always fully captured in the pre-trigger.
If I was to use the continuous background acquisition demo that you cited, I could set src.NotifyWhenDataAvailableExceeds to 10000 (my desired total duration), and I could then check each 10000 samples for the trigger condition. By doing this, I could easily acquire a 10000 sample duration signal that captured my trigger event. There are several problems with this, however.
-The trigger could have occurred at any point in that signal, so I might have 1 sample before the trigger event and 9999 after the trigger event, or 9999 samples before, and 1 after.
-I could certainly work around the above issue by keeping the last 10000 sample block in memory, and reading an additional 10000 samples after the block of data in which the trigger event occurs. Then, I could put these blocks together to form a 30000 sample signal that would definitely include the 1000 sample pre-trigger and 9000 sample post-trigger data that I need. However, this would require two times the acquisition duration than I need. If I need a 1000 2 second signals, that's over half an hour of wasted time.
-If I implemented the above solution, the user would not be notified that the trigger condition was met until the entire block of data is collected, which would be a significant delay. In modal impact testing, the user impacts an object with an instrumented hammer, and the transient vibration response is recorded. If the user does not impact hard enough, the system is not triggered, and they would have to impact again. Therefore, they need to know promptly (with a visual or audio cue) that the system was triggered and no further action is necessary until the acquisition has stopped.
I suspect that the low-level, high-performance functions needed to make this work are only available to the Mathworks, but I just wanted to call this to your attention, because this sort of issue is very important for vibration testing. It wouldn't be too difficult to implement, and would be very useful. The legacy daq interface has this functionality built-in, but doesn't have support for the hardware that the session-based interface has.
Thanks,
Ray
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.