Averaging 16 bit values without using 32 bit math?
The typical way to average numbers is to add them all together and then divide by the number of samples. However, adding several 16 bit numbers isn't easy to do in PBP because the largest variable size is 16 bits (Word).
While 32 bit or larger math is possible with PBP, sometimes it's just not worth writing the extra code.
In this case the solution is simple... Do the division first. This simple idea yields several benefits:
- It eliminates the 32 bit math.
- It allows you to keep a running average. After each sample, the new average is available.
- Otherwise you may have to wait for many samples to be taken before getting a new average value.
- Response times can be improved for rapid changes in the data.
The calculation goes as follows:
Simply choose the number of samples that you want to average and assign it to AvgCount
Divide to current average (ADavg) by AvgCount and subtract that number from ADavg
Divide the new Value by AvgCount and add that number to the average.
So if your AvgCount is 16 then it Reduces the average by 1/16 then adds 1/16th of the new number.
One effect of averaging can be a long delay before it "Catches up" to fast changing data. For instance, when you turn on power, it may be several seconds before the average shows the correct value of the initial data. It seems to slowly count up until finally reaching the target. By assigning a value to FAspread you can allow it to "Catch Up" if the difference between the average and the new Value is greater that the specified amount. This will improve the appearance of the displayed numbers considerably. ( for 10 bit A/D a FAspread of 200 may be suitable, but it really depends on your application)
One drawback of "Dividing first" is that when the difference between the average and the new Value is less than the number of samples (AvgCount), the numbers being added (Value/AvgCount) are too small to make a difference due to the truncating of integer math. To overcome this, the RealClose section was added to reduce the number of samples being averaged when the two numbers a very close to each other. If your averaging less than 8 samples, change it to (AvgCount/2) instead of (AvgCount/4)
The Lean version. (Keeps a running average of only 1 value)
' This routine will keep a "Running Average" ' Instead of adding a bunch of values together and then dividing by the number of samples, ' it averages each sample into the final result immediately. ' This eliminates the need for 32 bit math. ' To allow it to "Catch up" to large changes, set the FAspread to an acceptable range. ' Simply place the new number in VALUE and GoSub AVERAGE ' The Average will be returned into the same variable VALUE AvgCount CON 16 ' = Number of samples to average FAspread CON 1000 ' = Fast Average threshold +/- ADavg VAR WORD Value VAR WORD ' -=-=-=-=-=-= Average Analog values -=-=-=-=-=-=-=-=-=-= Average: IF Value = ADavg Then NoChange IF ABS (Value - ADavg) > FAspread OR Value < AvgCount Then FastAvg IF ABS (Value - ADavg) < AvgCount Then RealClose ADavg = ADavg - (ADavg/AvgCount) ADavg = ADavg + (Value/AvgCount) GoTo AVGok FastAvg: ADavg = Value GoTo AVGok RealClose: ADavg = ADavg - (ADavg/(AvgCount/4)) ADavg = ADavg + (Value/(AvgCount/4)) AVGok: Value = ADavg ' Put Average back into Value NoChange: Return
Of course, just about any "useful" program will have to deal with more than 1 averaged value, so consider the following example which allows you to average as many values as you may need.
' This routine will keep a "Running Average" of several inputs ' Instead of adding a bunch of values together and then dividing by the ' number of samples, it averages each sample into the final ' result immediately. ' This illiminates the need for 32 bit math. ' To allow it to "Catch up" to large changes, set the FAspread to an ' acceptable range. ' First, Select the sensor. ie. Sensor = Temperature ' then place the new number in VALUE and GoSub AVERAGE ' The Average will be returned into the same variable VALUE AvgCount CON 16 ' = Number of samples to average FAspread CON 1000 ' = Fast Average threshold +/- ADavg VAR WORD[3] ' Change this array size to match the number of inputs Value VAR WORD Sensor VAR BYTE Temperature CON 0 ' Change these to represent your inputs Pressure CON 1 Flow CON 2 ' -=-=-=-=-=-= Average Analog values -=-=-=-=-=-=-=-=-=-= Average: IF Value = ADavg[Sensor] Then NoChange IF ABS (Value - ADavg[Sensor]) > FAspread OR Value < AvgCount Then FastAvg IF ABS (Value - ADavg[Sensor]) < AvgCount Then RealClose ADavg[Sensor] = ADavg[Sensor] - (ADavg[Sensor]/AvgCount) ADavg[Sensor] = ADavg[Sensor] + (Value/AvgCount) GoTo AVGok FastAvg: ADavg[Sensor] = Value GoTo AVGok RealClose: ADavg[Sensor] = ADavg[Sensor] - (ADavg[Sensor]/(AvgCount/4)) ADavg[Sensor] = ADavg[Sensor] + (Value/(AvgCount/4)) AVGok: Value = ADavg[Sensor] ' Put Average back into Value NoChange: Return