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