Forum Index
Calculating running averages

 
Author Message
Don_Kirby



Joined: 15 Oct 2006

Posted: 03 December 2006, 21:38 PM    Post subject: Calculating running averages

I'm away from my develpment station at the moment, and had an idea to impliment a method of keeping a running average of GetADC() values. One criteria that I must stick to is for the number of samples included in the average to be adjustable, which rules out simply defining an array.

This code is untested (as I'm away at the moment), but I'd like any input on the method I'm using.

Code:

Sub ADCTask(DampRate as Integer)
Dim ADCValue as Integer, addr as UnsignedInteger, i as UnsignedInteger, AddEmUp as Integer
addr = System.Alloc(DampRate*2) 'allocate the block of memory   
   Do
      If addr > 0 Then 'check if the address value is valid
         For i = addr to (addr+(CUInt(DampRate*2))) 'Count through the memory addresses
            Call RamPokeWord(CUInt(GetADC(SensorInput)), i) 'store the most recent value
            AddEmUp = AddEmUp + CInt(RamPeekWord(i)) 'add up the values
            ADCValue = AddEmUp\DampRate 'and divide to get the average
         Next i
      Else
         ADCValue = GetADC(SensorInput) 'if the number of samples is 0 or if the memory allocation otherwise fails
      End If
      Sleep(0.0) Let the other tasks run
      
   Loop
Call System.Free(addr) 'this never gets called as the task never ends
End Sub



One thing I can't seem to get around is having the For/Next loop. I would much rather not use it, but I don't see any other way. Depending on the number of samples being averaged, it could significantly hinder the operating speed of the task.

Any thoughts, comments, obvious problems?

-Don
Back to top
stevech



Joined: 23 Feb 2006

Posted: 03 December 2006, 23:32 PM    Post subject:

since the do loop is infinite, the RAM used for data is used for the duration of the do loop. If the loop is supposed to run forever, why not just declare the array as an ordinary local or global rather than using system.alloc?

If the sub is really supposed to exit on occasion, then you could declare the array as a conventional (scalar) local and get the same effect, rather than using system.alloc.

As to the math, there was a recent chat thread on avrfreaks.net on algorithms for running averages. I think the classic is to have an array of n datums, and each new measurement replaces the oldest one in the array. This is done using pointers or indicies. The simple average is taken for the array, dividing by n, where n is smaller than the size of the array until n datums have been acquired.

Of course the biggest problem with a simple average is that the sum is a number that may overflow, depending on the range of the sampled data - hence the use of signed or unsigned long for the sum is often required.
--
A couple of nits in the code as shown...
The system.alloc could be retried in the loop rather than just once.
My coding style for pointers is to check only for zero/non-zero. Checking for > 0 implies that an address has a sign which it doesn't.
Back to top
Don_Kirby



Joined: 15 Oct 2006

Posted: 03 December 2006, 23:42 PM    Post subject:

stevech wrote:
since the do loop is infinite, the RAM used for data is used for the duration of the do loop. If the loop is supposed to run forever, why not just declare the array as an ordinary local or global rather than using system.alloc?


I didn't think it was possible for the upper bound of the array to be a variable. During normal operation, the loop never ends once its' started, but the user does have the ability to change the number of samples used in the devices' setup, which may or may not be changed in the future.


Quote:
Of course the biggest problem with a simple average is that the sum is a number that may overflow, depending on the range of the sampled data - hence the use of signed or unsigned long for the sum is often required.


Good point, I'll change that.

Quote:

My coding style for pointers is to check only for zero/non-zero. Checking for > 0 implies that an address has a sign which it doesn't.


Another good point. Is <>0 sufficient? Or perhaps NOT(0).

-Don
Back to top
Don_Kirby



Joined: 15 Oct 2006

Posted: 03 December 2006, 23:48 PM    Post subject:

Made a few changes to eliminate the For/Next. Still untested though... Can't wait to get home and try it.

Code:

Dim addr as UnsignedInteger, i as UnsignedInteger, AddEmUp as UnsignedLong, CurrentAddr as UnsignedInteger
addr = System.Alloc(DampRate*2) 'allocate the block of memory   

Do
      'Hysteresis calculations
      If addr <> 0 Then 'check if the address value is valid
         CurrentAddr = addr Mod   (addr+(CUInt(DampRate*2)))   
         Call RamPokeWord(CUInt(GetADC(SensorInput)), i) 'replace the with the current value
         AddEmUp = AddEmUp + CULng(RamPeekWord(i)) 'add up the values
         ADCValue = CInt(AddEmUp\CULng(DampRate)) 'and divide to get the average
      Else
         ADCValue = GetADC(SensorInput)
      End If
            
   Loop
Back to top
dkinzer
Site Admin


Joined: 03 Sep 2005
Location: Portland, OR

Posted: 04 December 2006, 0:18 AM    Post subject:

stevech wrote:
As to the math, there was a recent chat thread on avrfreaks.net on algorithms for running averages. I think the classic is to have an array of n datums, and each new measurement replaces the oldest one in the array. This is done using pointers or indicies. The simple average is taken for the array, dividing by n, where n is smaller than the size of the array until n datums have been acquired.

A queue can be used for this purpose. That simplifies the management of the samples - take out an old one, add a new one.

I sent the code below to someone that asked me about the method. This code updates two average as new elements of each are stored. In this case, the data were floating point values but the idea can be used equally well with integral data. The Main() just adds one values of each to show how to do so. The values used are constant; in a real application they would come from some data source.
Code:
Const queueExtra as Integer = 9
Const sampleCnt as Integer = 5
Const sampleSize as Integer = SizeOf(Single)
Dim iSample(1 to ((sampleCnt + 1) * sampleSize) + queueExtra) as Byte
Dim vSample(1 to ((sampleCnt + 1) * sampleSize) + queueExtra) as Byte
Dim iAvg as Single
Dim vAvg as Single

Sub Main()

   ' initialize
   Call OpenQueue(iSample, SizeOf(iSample))
   Call OpenQueue(vSample, SizeOf(vSample))
   iAvg = 0.0
   vAvg = 0.0

   Call addSample(iSample, iAvg, 1.4)
   Call addSample(vSample, vAvg, 2.3)
End Sub

Sub addSample(ByRef qSample() as Byte, ByRef avg as Single, ByVal val as Single)
   Dim numSamp as Integer
   Dim sampTotal as Single

   ' determine the number of samples currently in the queue
   numSamp = CInt(GetQueueCount(qSample)) \ sampleSize

   ' compute the total value of all the stored samples
   sampTotal = avg * CSng(numSamp) + val

   ' make sure there is room for the next sample
   Do While (numSamp >= sampleCnt)
      ' remove the oldest sample from the queue and the running total
      Call GetQueue(qSample, val, SizeOf(val))
      sampTotal = sampTotal - val
      numSamp = numSamp - 1
   Loop

   ' add the new value to the sample queue
   Call PutQueue(qSample, val, SizeOf(val))

   ' compute the new average value
   avg = sampTotal / CSng(numSamp + 1)
End Sub
Back to top
Don_Kirby



Joined: 15 Oct 2006

Posted: 04 December 2006, 3:01 AM    Post subject:

Another option to do almost the same thing is shown below. The lower the number [DampRate], the faster it responds to changes.

Code:

ADCValue = (ADCValue+(GetADC(SensorInput)-ADCValue)\DampRate)


Any thoughts on the advantages/disadvantages of this method over the previous one?

-Don
Back to top
spamiam



Joined: 13 Nov 2005

Posted: 04 December 2006, 23:59 PM    Post subject:

Well, advantages/disadvantages are like beauty. It is in the eye of the beholder.

By this I mean that it all depends on what you are looking for.

The running average of N samples will go to read zero N samples after the signal drops to zero and stays.

The second technique with subtracting, then dividing, then adding exhibits an exponential decay. Therefore N samples after the signal goes to zero, the running "average" will still be non-zero.

When I want to compare these mathematical functions, what are essentially low pass filters, I put them in a spread sheet. The first column are the samples over time, then next columns have the results of the variety of averaging operations. Then you can use a chart to compare the results.

Then you either decide to use one of these or usemore complicated math to get the "right" effect. You decide.

Just remember addition and subtraction are faster than multiplication and especially division, and higher order functions, especially transcendental operations are very very slow by comparison. So, for instance, in the running average, you might be able to avoid dividing by N after adding N samples, and just use the total instead of the actual average.

If using the ADC, use the integer reading, and do not scale it to a floating point. Unless you need to....

-Tony
Back to top
Don_Kirby



Joined: 15 Oct 2006

Posted: 05 December 2006, 0:57 AM    Post subject:

Thanks for the insight Tony. I think I'll make up a test scenario and try out the different options.

To be honest, this isn't a particularly high-end device, so any method to smooth out the ADC values (and is adjustable) will do just fine. Perhaps I'm just enjoying experimenting with the ZX's abilities a bit too much.

As far as mathmatical speed is concerned, I'm not worried. If I wind up with only 10 readings per second, the project would be considered a success.

Off to devise some tests...

-Don
Back to top
sparxfly



Joined: 19 Dec 2005
Location: New Zealand

Posted: 05 December 2006, 3:12 AM    Post subject: Calculating running averages

Don-
 
That is exponential averaging, not true averaging.
Step response is an exponential rather than linear ramp, and never quite settles at final value.
New samples carry a greater weight than old.
But its a good, practical way to get an average without requiring memory hungry arrays.
I use that technique on my weather stations for wind analysis.
 
Cheers,
Stuart Parker
www.sparxfly.co.nz
 
Quote:
----- Original Message -----
From: ZBasic (zbasic.forum@zbasic.net)
To: zbasic.forum@zbasic.net (zbasic.forum@zbasic.net)
Sent: Monday, December 04, 2006 4:01 PM
Subject: RE: Calculating running averages


Another option to do almost the same thing is shown below. The lower the number [DampRate], the faster it responds to changes.

Code:
ADCValue = (ADCValue+(GetADC(SensorInput)-ADCValue)DampRate)


Any thoughts on the advantages/disadvantages of his method over the previous one?

-Don




-------------------- m2f --------------------

Read this topic online here:
http://www.zbasic.net/forum/viewtopic.php?p=2930#2930

-------------------- m2f --------------------
Back to top
Display posts from previous:   
Page 1 of 1

 



ZBasic Microcontrollers Home
All content Copyright © 2005, 2006, 2007, 2008, 2009, 2010 Elba Corp. All Rights Reserved.
Opinions expressed in posts are those of the author and not necessarily those of Elba Corp.
Powered by phpBB © 2001, 2005 phpBB Group