Multifunction DAQ

cancel
Showing results for 
Search instead for 
Did you mean: 

Optimal Acquisition Method and Speed

Hi All,

 

I am trying to do a high sample rate acquisition, simultaneously processing the acquired data to provide graphical feedback.

 

My problem is two-sided:

1: I am not sure which data acquisition method is best for me: continuous, hardware timed single point combined with

single or N sample read VI etc.

2: How to determine the optimal sample frequency I can reach?

 

I tried many ways using the different acquisition methods but none leads to to desired behavior.

 

A snapshot of my code:

TriggProg.PNG

 

The upper while loop is the loop which does all the data processing (calculation removed for display purposes).

This is just a snapshot of one of my tries. I have been playing around with the 'Wait Until ms' timer as well moving it to the 'Processing loop' as well.

The reason for not using single sample acquisition is that this led to a filling buffer significantly delaying the feedback 

given about acquired data. This likely has to do with the heavy calculations made. I used the Nsample acquisition instead, reading a single value 

of the array a time as my calculations are based on single point acquisition. It does however effectively reduce my sampling frequency to

10 Hz where about 2000 Hz is desired.

 

Any recommendations are very much welcome,

 

Mark

 

(Using a BNC-2090 with PCI-6024E DAQ board)

 

 

 

0 Kudos
Message 1 of 12
(4,387 Views)

Mark,

 

1.   At high sample rates you always need to be doing hardware timed acquisition. Most likely a continuous sampling process will be the best unless your sampling rate is so high that you cannot read the data fast enough to avoid buffer overflows.  It looks like you may be using 10000 samples per second. At that rate you should have no problems keeping up.

2. Switch to a Producer/Consumer architecture.  Put the AI Read in the Producer loop. Transfer the data using a queue. Do not put anything else in that loop. No indicators. No waits. This will let the producer loop run as fast as data come in.  If you are not using the timing data, use the 1ChanNSamp DBL Array data format. In the consumer loop Dequeue the data anytime there is data in the queue. Do all the processing and display in this loop.

 

Other comments: In your image you acquire a waveform with 1000 datapoints and then throw away the timing information and all but the last point.  If you are only going to use 10 points per second, you do not need to sample one thousand times faster.

Local variables are not intended to be used for passing data around the program.  They are for remote access to a control or indicator. Queues and some other methods are better for passing data.

All parallel loops should have some kind of wait or delay to avoid hogging all CPU resources in one loop.  The Analog Read will wait until the specified data is available so no other wait is required. Similarly the timeout on the queue functions will provide this capability.  Your top loop with the Waveform Chart could be grabbing the CPU and might be part of your performance problem.

 

You talk about "heavy calculations" but do not describe them beyond that.  What kinds of caclulations are you doing? What is the end result of your process? You mention displays. Are you doing any kind of feedback or control?  For display purposes updating the display more than about 10 times per second is meaningless because the human eye/mind visual processing works at about that rate.

 

Lynn 

Message 2 of 12
(4,381 Views)

Hi,

 

Thanks for this reply. As you probably noticed I am a fairly new LabView user being used to Matlab programming.

 

So to clarify a bit on my code a short outline:

 

This was meant as an example to show how my full code has been build. The purpose of my full code is indeed user feedback by 

controlling a SW LED being either green, task is performed well, or red, indicating the user he has to improve and get it green again.

Control is by the ElectroMyoGraphy (EMG) signal of a muscle. In the same time the operator monitors the EMG in a waveform chart.

The feedback is done based on a RMS calculation of the EMG.

So this RMS value is calculated from an array of single samples in a circular buffer way over a window of 100 ms.

(An initialization recording is done to set the boundaries based on mean and std of the RMS of x time windows).

 

Ideal would be to have a sample rate of about 5000 Hz. Currently the code is set up to process each single sample acquired updating one

single element of the build array, resulting in a new RMS value. So a fixed array length is used where the RMS is calculated from each single time a new sample is acquired.

Feedback is indeed given a a lower rate but I'd like to have the running RMS value for analysis. 

 

Most importantly: I can't have a significant delay between data acquisition and giving feedback is this will confuse / annoy the user 

resulting in a unstable task performance. An this last point has been my main problem when using single point acquisition

as apparantly a buffer build up delaying feedback. This is why I switched to Nsample acqusition, taking 1 sample of the acquired array a time. 

 

I hope this makes sense, My conern about the queing would be introducing a significant (>0.1 sec) delay between acquiring and giving feedback.

 

Any further comments on this relating data acquisition and processing are very much welcome,

 

Thanks,

 

Mark

0 Kudos
Message 3 of 12
(4,371 Views)

Mark,

 

Thanks for the clarification.

 

To summarize:

1. You would like to sample at 5 kS/s.

2. You are calculating RMS values from 100 ms segments of the data. Your preference is to calculate an RMS value for each sample based on the previous 100 ms of data. This data will be saved for "off-line" analysis.

3. You are updating a boolean indicator to the user approximately 10 times per second based on the value of the RMS calculation at that time.

 

Calculating the RMS value of 500 points (100 ms at 5 kS/s) takes about 0.8 us.  This is not "heavy calculations" when you consider that the samples are taken 200 us apart.

 

This is the way I would approach this. (I have done something very similar except taking FFT on larger datasets). Set up continuous acquistion at 5 kS/s in the Producer loop. Read 500 samples at a time as an array of DBL. Pass the data to the Consumer via a queue. Queues are very fast.  If any delays occur it will be due to the way you wrote your program, not the queue.

 

In the Consumer loop, dequeue all available data and put it into an array in a shift register. Extract 500 sample segments from the array and calculate the RMS value. Put these values into an array to be saved later.  Every 100 ms take the most recent value of RMS and update the boolean.  I would implement the code in the Consumer as a state machine so that it will be relatively easy to change things as the research progresses. I can assure you that something will change! You will probably need an additional shift register to keep track of the index in the array for the current 500 sample subset.

 

Unless you have a very slow or old computer it should not be difficult to do what you have described.  If you run into performance problems, postpone the RMS per sample calculations until after the session.

 

Lynn

0 Kudos
Message 4 of 12
(4,362 Views)

Thanks, working on it. Havent't uses queues before so first doing some reading and testing outside my full program.

 

Just two more questions:

1: This method basically means I am building a big data array from which I extract 500 samples a time constantly removing an old value and adding a new one, extracted from this big data array.

Could you confirm I get the principle?

 

2: Why doesn't this work with just the 1Chan1Samp option of the read VI?  Is this just because reading consumes time and therefore the whole VI can't keep up with the data acquisition at 5000 Hz?

 

Regards,

Mark

 

0 Kudos
Message 5 of 12
(4,342 Views)

Mark,

 

Queues do take a bit of experimentation at first.  After getting an idea of how they work, I think you will find them useful.

 

1. Yes. No. Maybe. This is one of the thngs you need to give some thought and planning. You always need to be careful about building an array in a loop as this can cause frequent memory allocations with the consequence being poor performance.  The best way is to initialize an array outside the loop and pass it from one iteration to the next via a shift register.  Inside the loop use Replace Array Subset. This does not change the size of the array avoiding the memory allocation issue. With some extra bookkeeping you can operate this as a circular buffer. The size of the buffer depends on how much data you need to keep available, how much data you read each time, how often you write the data to file.  I am guessing that 5000-10000 samples in the buffer might be about right, but many things can affect that size.

 

Some questions: Are you saving the raw data to a file or displaying it? How long does one trial last?  Are there multiple trials in one session?  If so, what is the interval between trials?  Is there anything else going on while the EMG/feedback process is running?

 

2. I doubt you can read one sample at a time and achieve 5 kS/s. Also the timing would likely be affected by software latency resulting in jitter and possibly lost data. Run continuous sampling and read all available samples about 20 times per second.  That way you have access to current data within the 0.1 second limit that your human subjects require.

 

You will likely need to run some benchmarks to see what combinations of samples to read and various processing approaches gives you the best performance.

 

Lynn

0 Kudos
Message 6 of 12
(4,335 Views)

Hi,

 

To answer your questions first:

Raw data is displayed as well (separate tab so the participant won't see it), this is just to make sure I do have proper EMG.

There aren't multiple trials. The way it works is a predefined initialization period in which RMS is calculated likewise as discussed.

The array of RMS values resulting from this are used to set the boundaries for correctly completing the task (based on mean and std).

What the subject get to see is only the boolean LED. There is a lot going on, as this is part of a bigger experiment, but this feedback

is given on a separate running computer.

 

I am currently struggling to build the array containing all the raw data, 500 samples coming in each time, and in the same time

calculating the 'running' RMS. Not sure how to get this all in one while loop. While the data acquisition is an intermittent process

calculating the RMS calculation should be continuous. My feeling is that those two can't go together in one while loop.

 

I need to be able to track how much data is coming in, to correctly assign it to the buffer array, and run through all data shifting my

fixed 500 sample array by one element to calculate the RMS.

 

Thanks again for the great help, learned a lot so far!

 

Mark

 

 

 

 

0 Kudos
Message 7 of 12
(4,324 Views)

Mark,

 

So you have an intialization phase and a run phase.

 

I suggest the two loop Producer/Consumer architecture. There are Design Patterns or examples which come with LV.

 

The Producer loop contains only the data acquisition code and a queue to pass the data to the Consumer loop.

 

The Consumer loop contains a state machine. States might include: Initialization, Recieve Data, RMS, Boundaries, Feedback, Save, Error, and Shutdown. In the Intialization state you could initialize the data buffer, set the bookkeeping indexes to zero, and other start up tasks. Recieve Data dequeues data, replaces the appropriate segment of the data buffer with the new data and updates the buffer indexes. RMS calculates the RMS values of any new data which meets the 500 sample criteria and keeps track of which data has been calculated so it does not repeatedly calculate the same segments. Boundaries does the calculations necessary to set the limits for the feedback. Feedback runs every 100 ms and updates the front panel boolean according to the latest data and the boundaires. Save runs infrequently (maybe only once at the end) and saves the RMS array. How often it runs depends on how much RMS data you are willing to accumulate and how long the sessions last. Error is a state for handling any error which may occur. Shutdown will stop the data acquisition, reset the boolean to the default condition, possibly notify the user that the session has ended, close any files, and anything else needed to be done before stopping the program. It may release the queue or that may happen outside the loop.

 

The consumer will have several shift registers.  One will be for the state control for the state machine. One will be for the data buffer.  Another for the indexes and one for RMS. Probably one for errors. One for the file path.

 

I realize that it starts to sound complicated, and it is. But your program is trying to do a lot of things and this kind of structure not only can handle these tasks readily but is easily changed when new features are added.

 

Lynn

0 Kudos
Message 8 of 12
(4,318 Views)

Thanks again,

 

Working on it, will let your know if it all works. Had a lot of work on making the circular buffer work today but all sorted now. Next bit is the feedback and an overall initialization phase to set boundaries. (I started all over from scratch again to avoid copying any mistakes from my old code). 

 

Mark

0 Kudos
Message 9 of 12
(4,307 Views)

Hi,

 

The code seems to work! So thanks for all the useful comments.

 

One final question for my feedback state in the state machine. Updating the feedback LED now goes with the same rate as that I read samples.

(If I read 500 samples a time with a sampling rate of 5000 Hz it updates each 100 ms). So it is dependent on how often is passes the state

in state machine. Is there a simple way to make this independent? (So that I for example can update the feedback LED each 0.5 s or 1 s).

Ultimately I could make this all dependent on a array size as this one is build during acquisition but I was wondering if there is a simpler method

which doesn't stop execution of the state machine, which a wait VI does.

 

Regards,

Mark

 

 

 

 

 

 

0 Kudos
Message 10 of 12
(4,288 Views)