LabVIEW

cancel
Showing results for 
Search instead for 
Did you mean: 

How do I create an n-point running average VI?

I've been programming in sequential written languages like C for many years now. I've been using LabVIEW for the last 4 months and am having problems writing an n-point running average VI. It is really bothering me, because this would be really trivial in C or anything else. But for some reason I can't conceive how I would do this in LabVIEW. I used LabVIEW 5 for the bulk of that time, and we just upgraded to LabVIEW 8 last week so I've been learning it. Wow, some pretty big differences between the two but the gist is the same.

I have a 5-point running average subVI written that uses a run-once while loop with shift-registers that keep their value between subsequent VI calls. This works for what I need, but have 2 problems I would like to overcome.

1. I can't have multiple instances of the same subVI in my top-level VI program because the way the shift-registers are keeping their value between subsequent VI calls. I have to make duplicates of the .vi file (i.e., 5_point_running_average.vi, 5_point_running_average_2.vi) in order to keep all the shift registers seperate.

2. I want it to be an n-point running average. Currently I have to jump into the VI and add another shift register to add another point. I often change my sample rate, and thus being able to have a different averaging window is really handy.

So the VI takes 2 inputs:

n - (integer) the number of points to computer the average over.
in - (real) the current data point.

output:

out[i] = (in[i] + in[i-1] + ... + in[i-n-1])/n

I've also thought about the possibility of needing a 3rd input that is "instance" which might help keep variables within the subVI separated and thus not needing separate duplicate files.

In case you are wondering, I'm using a running average to make digital HUDs easy to read. The actual data that I'm recording to file will not be going through the running average function, because as you can see by the output function, there will be a phase shift in the output. At high sample rates, the digital displays can be moving very rapidly and that makes them hard to read.

Other recommendations as to how to make digital displays easy to read to help operators while performing tests? As for instantaneous data smoothing, so to speak. I already make the displays very large if they are critical.

I'm very interested in this n-point running average solution. I've been scratching my head over it for awhile now and am quite stumped on how to approach this in LabVIEW.

Help is very much appreciates,
-nickerbocker
0 Kudos
Message 1 of 17
(28,359 Views)

Instead of using a scalar shift register and expanding it on the diagram to return the last several values, you could use an array shift register and initialize its size based on the input to the VI.  Then you could just use the Replace Array Subset function to replace elements in the array as new elements come in.

As for the uninitialized shift register problem, that's easy...make the subVI reentrant.  You can do this through VI Properties > Execution > Reentrant Execution.  A reentrant VI maintains separate dataspaces for every instance of that VI in your hierarchy.  So if your VI was reentrant, every subVI would maintain its own values in the uninitialized shift register.  You can read more about Reentrant VIs in the LabVIEW Help.

Hope these answers made sense,

-D

Message 2 of 17
(28,342 Views)

As darren has already mentioned, making your moving avg sub VI re-entrant should solve your initialising shift register problem.

In my cache, i happened to find these two VI's for calculating moving average.

Hope it helps you

Regards

Dev

 

Download All
Message 3 of 17
(28,339 Views)
ahh... that makes sense about the array shift register.  So it works like a stack.  Thanks guys.  I think I'll try to write my own VI before steeling yours ;).  It will be good exercise.

-Nickerbocker
0 Kudos
Message 4 of 17
(28,316 Views)
Ok...here is my solution.  I looked over the two you posted and found that my needs were slightly different.  The design appears to be working well, though.

It was one of those "ah ha" moments, realizing I could just pipe an array through the shift registers.  Thanks for the tip (although it should have been obvious to me *doh*).

Message Edited by Nickerbocker on 01-30-2006 04:04 PM

Message 5 of 17
(28,308 Views)
It is often relatively expensive to constantly shrink and grow arrays, because of potential memory issues (delete from array followed by insert into array).
 
It is probably better to just rotate the array in place, then replace the first element with the new value. See attached (LabVIEW 8.0) for a possible alternative.
Message 6 of 17
(28,297 Views)
altenbach,

Thank you for looking over my work and providing me with constructive critisism!  I didn't realize that the delete from array was resizing the array, and then the add to array was resizing the array again.  I realize that would be expensive, but further explanation as to why would be greatly appreciated.

Is it because the process in memory looks like this:

1. Have 5-element array.
2. Initialize 4-element array.
3. Copy last 4 elements in 5-element array to 4-element array.
4. Free 5-element array memory and destroy pointer.
5. Initialize 5-element array.
6. Copy 4-element array into 5-element array + supplied value.

Do I have that concept right?  I'm always very interested in performance analysis, so further explanation of how LabVIEW executes code and handles arrays is greatly appreciated.

I see 2 new functions I haven't used before.  The rotate array looks like it acts like a 'shift register' (in the more traditional sense), where instead of the last element falling into the bit-bucket it is copied to the first element.  Then the insert element function overrights it.  Sweet.  The "first call?" function troubles me.  Wouldn't the VI always be on "first call" since each execution of the VI is seperate or something?  The VI just retains its values in its shift registers between sub-sequent calls, but isn't it always on "first call"?  Wouldn't it be the "first call" of the while loop, always.  Smiley Indifferent

Thank you for your time.

-Nickerbocker
0 Kudos
Message 7 of 17
(28,282 Views)

The atomic operations (delete/insert) MUST do the following:

  • When you insert into array, all higher elements need to be moved up one slot, then the element is inserted.
  • When you delete from array, the lement needs to be deleted and all higher elements moved down one slot

Both operations involve an array resizing operation and data shuffling in memory.

How can LabVIEW be sure that the memory can be re-used? What if your code would delete one, but insert two at each iteration? It might well be that the LabVIEW compiler properly re-uses the memory in your particular case, but there are no guarantees. A five element array is peanuts. so it would probably be difficult to notice a difference.

You might want to run a benchmark of the different algoritms using a huge buffer. 😉

"First call?" is defined as followes in the online help:

First Call? returns TRUE the first time the VI runs after the first top-level caller starts running, such as when the Run button is clicked or the Run VI method executes. If a second top-level caller calls the VI while the first top-level caller is still running, First Call? does not return TRUE a second time.

It will only be true exactly once during the entire execution of the toplevel VI, only the first time it is called from anywhere.
0 Kudos
Message 8 of 17
(28,273 Views)
altenbach:

I'm not so sure you are right about the "first call?" function.  Something isn't right with your modification and I think that is it but to be honest I haven't had time to check it out and I'm about to leave for the day.  Running your subVI and then my original one with the attatched random data + sinewave program so very different result.

-Nic
0 Kudos
Message 9 of 17
(28,263 Views)
OK, try the attached. It seems to work fine. (LabVIEW 8.0)
 
(The problem was not with "first call?" but there was a mistake in my posted modification. I "thought" I replaced the insert into array" with "replace array subset", but in fact I did NOT. This makes the averaged array grow without bounds.. The attached VI is a corrected version. Also notice in the toplevel VI that you only need to calculate the simulated function once, not with every loop iteration 😉 )
Message 10 of 17
(28,256 Views)