Proton BASIC Compiler - Simple averaging routine


  • PicŪ Basic


  • Simple averaging routine

    I had been looking for an averaging routine that would give me a new average value after each A/D reading, instead of having to make several A/D readings and then add the numbers together and divide by the number of samples. I often need a routine like this to add "damping" to voltage or current readings. I found some code posted by Darrel Taylor on a PICŪ basic Pro forum that works quite well in PDS. Here is the code from the other board with a couple of minor tweaks to make it compatible with PDS:

    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 are very close to each other. If you're averaging less than 8 samples, change it to (AvgCount/2) instead of (AvgCount/4)
    Code:
    ' 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
    
            
    Symbol AvgCount =  16        ' = Number of samples to average
    Symbol FAspread    =  1000    ' = Fast Average threshold +/-
    Dim   ADavg as WORD
    Dim   Value as WORD
    
    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)            
      Fastavg:
        ADavg = Value
        GoTo avgok
      Realclose:
        ADavg = ADavg - (ADavg/(AVGCount/4))
        ADavg = ADavg + (Value/(AVGCount/4))
      Avgok:
        Value = ADAVG 
      Nochange:
        return
    Something I discovered is that if you're really strapped for code space, you can reduce the above routine to this, which is used inline instead of as a subroutine:

    Code:
    Value = Value - (Value/AvgCount)
    Value = Value + (Raw/Avgcount)
    Where RAW is the input value and Value is the averaged output value.

    Note that even though this last example uses less code, it will run slower because it has to do all of the math for each pass.

    Article by Rcurl