Averaging

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
Page last modified on February 21, 2018, at 08:40 PM