7

Reading a battery voltage in an embedded device. However, the actual voltage varies greatly depending upon system load. We need a method to reduce the fluctuation of the voltage to display the best value.

Currently, we're using a rolling/moving average. However, over the last 15 readings, the result still fluctuates too much.

In reading about smoothing algorithms, it appears that b-splines, kernel filters, or some other smoothing algorithms would be ideal. However, I can't find a simple example that doesn't use numpy or intrinsic functions within mathcad or some such.

Anybody know of a simple-to-implement function that could help with this? This is a C++ project (using Qt 4.5) with just the bar minimum of libraries. I'd prefer to stay in the integer domain (showing the voltage in millivolts from 3300-4200).

TIA Mike

Mike Crowe
  • 2,203
  • 3
  • 22
  • 37

7 Answers7

7

Well, it's a bit tough to tell what you need without specifics on your situation. For example, what is your sensor sampling rate, and how is the sensor fluctuation and noise that you're trying to remove characterized?

However, if you already have moving average implemented, I might recommend trying a moving median instead. (Median of the last n samples, rather than average.) This will tend to reduce the impact of large short-term aberrations from normal from your output.

If you can find parameters that work, it would be preferrable for CPU and memory requirements to use some form of a discrete-time low-pass filter. These are quite easy to implement, and only require knowledge of the previous output value and the current input to compute the current output. For example:

Y = Y[n-1] + A * (X - Y[n-1])

(Where Y is the current output, Y[n-1] is the last computed output, and X is your latest sensor reading.)

A is effectively the time constant of the low pass filter, but it's discrete time, so it depends on the sampling rate. Specifically, A = dt / tau, where dt is your sampling period in seconds, and tau is roughly analagous to the continuous-time time constant.

potatoe
  • 1,070
  • 9
  • 14
  • This was perfect. Fast, easy to compute. Took a bit to figure out my coefficient, but works great. – Mike Crowe Apr 01 '11 at 23:40
  • @Mike: Tools such as Matlab (and its free clones such as FreeMat and Octave) have facilities for calculating IIR filter coefficients for specific bandwidth requirements. Though in this case with only a single coefficient, an empirical approach is probably sufficient (and probably quicker than learning Matlab). Your moving average method is a simple example of an FIR filter (with all coefficients equal 1). – Clifford Apr 02 '11 at 08:10
  • thanks, exactly what I was looking for, this is my function based on this for Arduino: //COMPRESOR int compreSSor(int signal,int lastSignal){ int result = lastSignal + 0.2 * (signal - lastSignal); return result; } – Macumbaomuerte Jul 04 '11 at 00:37
2

you can find explanations and source code in classical NR book: http://apps.nrbook.com/c/index.html,

namely chapter 3: http://www.arcetri.astro.it/irlab/library/recipes/bookcpdf/c3-3.pdf

Anycorn
  • 50,217
  • 42
  • 167
  • 261
1

Have you considered simply applying a skew limit to the value?

new_val = Read_From_HW();
diff = new_val - prev_val;

if (diff > SKEW_LIMIT)
    diff = SKEW_LIMIT;
else if (diff < -SKEW_LIMIT)
    diff = -SKEW_LIMIT;

reported_val = prev_val + diff;
prev_val = reported_val;
Tim Henigan
  • 60,452
  • 11
  • 85
  • 78
1

It would be possible to get very deep into signal processing techniques and complex math, but you have to ask yourself if it is really necessary?

If this display is a simple instantaneous numeric output, used for "indication only" rather than say a continuous graph or data log (i.e. you do not need to reconstruct the signal), then it would often be perfectly acceptable, to simply take a periodic average rather than a moving average. Since that requires no history storage, you can average over as many samples as you wish, and this would be determined by the required frequency of display update.

It is not clever, but it is often adequate for the task. Here's an example and a test simulation of its use.

class cPeriodicMean
{
    public :
        cPeriodicMean( int period ) : m_mean(0), 
                                      m_period(period),
                                      m_count(0),
                                      m_sum(0)
        { 
            // empty
        }

        void addSample( int sample )
        {
            m_sum += sample ;
            m_count++ ;
            if( m_count == m_period )
            {
                m_mean = m_sum / m_period ;
                m_count = 0 ;
                m_sum = 0 ;
            }
        }

        int getMean() 
        { 
            return m_mean ; 
        }

    private :
        int m_mean ;
        int m_period ;
        int m_count ;
        int m_sum ;
} ;

// Test Simulation
#include <cstdlib>
#include <cstdio>
#include <windows.h>  // for Sleep to simulate sample rate
int main()
{
    // Average over 100 samples
    cPeriodicMean voltage_monitor( 100 ) ;

    for(;;)
    {
        // Simulate 4000mV +/- 50mV input
        int sample = 4000 + (std::rand() % 100) - 50 ;
        voltage_monitor.addSample( sample ) ;

        // Simulate 100Hz sample rate
        Sleep(10) ;

        // Current output
        int millivolts = voltage_monitor.getMean() ;
        printf( "\r%d millivolts    ", millivolts ) ;
    }
}

A refinement of this technique that will produce even smoother output but generate results at the same frequency would be use the periodic mean output as input to your moving average filter. If you were to use my 100 samples per second example with the 100 sample period, and then put it through your 15 sample moving average, you will have used 15 seconds worth of sampling data while still getting a result every second, with little additional memory usage.

Obviously you can change the period, the moving average length, and the sampling rate to get the results you need at the update frequency you need. I suggest that you take as many samples as you can for the period for which you need an update, then make the moving average as long as you wish to afford.

Clifford
  • 88,407
  • 13
  • 85
  • 165
0

I know this doesn't directly answer your question, but would average bars help? In other words, show min/max/mean/median over say 15 second windows instead of just a mean.

Mark B
  • 95,107
  • 10
  • 109
  • 188
0

This really sounds like a hardware problem to me. Is it a Li-Io or NiMH battery? What does the discharge curve look like? What components are there between the battery cells and your ADC? You need to know these things before running off to implement various digital filters.

Lundin
  • 195,001
  • 40
  • 254
  • 396
-2

If you don't have your answer this is a good way to print out something on a robot such as a pololu 3pi.

{
    int bat = read_battery_millivolts();

    clear();
    print_long(bat);
    print("mV");
    lcd_goto_xy(0,1);
    print("Press B");

    delay_ms(100);
}

APC
  • 144,005
  • 19
  • 170
  • 281
  • Please be careful when answering old questions which have lots of answers, especially when one of them's accepted. You need to explain why your answer is better than any of the existing ones. – APC Dec 26 '15 at 20:01
  • This answer is completety off topic.. The question was how to filter/smooth sensor readings and to remove spikes... This answer basically shows a looping print of a int reading... – Angry 84 Dec 20 '18 at 01:17