help with smoothing sensor data

iv built an adjustable altimeter (thanks to those who helped with some code issues)

its working great but the altitude jumps around quite a bit so id like to smooth it out a bit.

Id prefer rounding this function
"float Af = Am * 3.28084 + 40;" so the altitude is closest 5 feet.

but I guess smoothing out the "event.pressure" reading from the BMP-180 sensor itself could work and make the altimeter more accurate but I couldnt figure out how to do that with sensor data coming in on I2C. 1 foot accuracy would be nice but 5' would be plenty as this will be used for experimental aviation.

What is the most simple way to do this with keeping the sketch size and variable use to a minimum as i want to have this on a pro mini with lcd and Attiny85 sending data via BT to an android app.

Currently Sketch uses 14150 bytes, Global variables use 1119 bytes

and could you please show me where in my code to put it and how to write it as i have had issues in the past transposing code without causing issues in my sketch.

heres the full code

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP085_U.h>
#include <ClickEncoder.h>
#include <TimerOne.h>
#include <LCD5110_Graph.h>
Adafruit_BMP085_Unified bmp = Adafruit_BMP085_Unified(10085);
LCD5110 lcd(8, 9, 10, 12, 11);
extern unsigned char SmallFont[];
extern unsigned char TinyFont[];
ClickEncoder *encoder;
int16_t last, value = 11968;
void timerIsr()
{
  encoder->service();
}
void setup(void)
{
  encoder = new ClickEncoder(A1, A0, A2);
  Timer1.initialize(1000);
  Timer1.attachInterrupt(timerIsr);
  last = -1;
  Serial.begin(9600);
  if (!bmp.begin())
  {
    Serial.print("Ooops, no BMP085 detected ... Check your wiring or I2C ADDR!");
    while (1);
  }
  lcd.InitLCD();
  lcd.setFont(SmallFont);
  lcd.print("ALTIMETER", CENTER, 2);
  lcd.print("PROTOTYPE", CENTER, 12);
  lcd.update();
  delay(2000);
}
void loop(void)
{
  float q = value / 4 * 0.01;
  value += encoder->getValue();
  sensors_event_t event;
  bmp.getEvent(&event);
  if (event.pressure)
  {
    Serial.print(event.pressure);
    Serial.println(" hPa");
    float temperature;
    bmp.getTemperature(&temperature);
    float seaLevelPressure = (q * 33.8638870320855);
    float Am = (bmp.pressureToAltitude(seaLevelPressure, event.pressure));
    float Af = Am * 3.28084 + 40;
    lcd.clrScr();
    char qnh[6];
    char Alt[6];
    dtostrf(q, 3, 2, qnh);
    dtostrf(Af, 3, 0, Alt);
    lcd.print(Alt, RIGHT, 13);
    lcd.print(qnh, LEFT, 13);
    lcd.update();

  }
}

Could you try a simple
((current sample * 3 )/ 4) + (newsample / 4)

type filter?

CtrlAltElite:
Could you try a simple
((current sample * 3 )/ 4) + (newsample / 4)

type filter?

Like this?

float Af =( Am * 3.28084 + 40* 3 )/ 4 +(Am *3.28084+40 /4)
Or just include this line:
Float Af2 =( Af * 3)/4 +(Af/4)

Then change the LCD info to reflect Af2

For smoothing raw data, consider using a first order low pass digital filter. Adds one or two lines of code.

I know you’re probably set with using the barometric altimeter, but an even better choice would be a LiDAR solution like the LiDAR Lite V3. I use it myself on my RC planes and it’s much more accurate and reliable than a BMP-180 as long as you have line of sight to the ground and fly under 40 meters.

Just a thought.

A more precise version of the algorithm posted by Jremington would be:

[color=#00979c]class[/color] [color=#000000]EMA[/color] [color=#000000]{[/color]
  [color=#00979c]public[/color][color=#434f54]:[/color]
    [color=#000000]EMA[/color][color=#000000]([/color][color=#00979c]uint8_t[/color] [color=#000000]shiftFac[/color][color=#000000])[/color]
        [color=#434f54]:[/color] [color=#000000]shiftFac[/color][color=#000000]([/color][color=#000000]shiftFac[/color][color=#000000])[/color][color=#434f54],[/color] [color=#000000]fixedPointAHalf[/color][color=#000000]([/color][color=#000000]1[/color] [color=#434f54]<<[/color] [color=#000000]([/color][color=#000000]([/color][color=#000000]shiftFac[/color] [color=#434f54]*[/color] [color=#000000]2[/color][color=#000000])[/color] [color=#434f54]-[/color] [color=#000000]1[/color][color=#000000])[/color][color=#000000])[/color] [color=#000000]{[/color][color=#000000]}[/color]
    [color=#00979c]int32_t[/color] [color=#d35400]filter[/color][color=#000000]([/color][color=#00979c]int32_t[/color] [color=#000000]value[/color][color=#000000])[/color] [color=#000000]{[/color]
        [color=#000000]value[/color] [color=#434f54]=[/color] [color=#000000]value[/color] [color=#434f54]<<[/color] [color=#000000]([/color][color=#000000]shiftFac[/color] [color=#434f54]*[/color] [color=#000000]2[/color][color=#000000])[/color][color=#000000];[/color]
        [color=#000000]filtered[/color] [color=#434f54]=[/color] [color=#000000]filtered[/color] [color=#434f54]+[/color] [color=#000000]([/color][color=#000000]([/color][color=#000000]value[/color] [color=#434f54]-[/color] [color=#000000]filtered[/color][color=#000000])[/color] [color=#434f54]>>[/color] [color=#000000]shiftFac[/color][color=#000000])[/color][color=#000000];[/color]
        [color=#5e6d03]return[/color] [color=#000000]([/color][color=#000000]filtered[/color] [color=#434f54]+[/color] [color=#000000]fixedPointAHalf[/color][color=#000000])[/color] [color=#434f54]>>[/color] [color=#000000]([/color][color=#000000]shiftFac[/color] [color=#434f54]*[/color] [color=#000000]2[/color][color=#000000])[/color][color=#000000];[/color]
    [color=#000000]}[/color]

  [color=#00979c]private[/color][color=#434f54]:[/color]
    [color=#00979c]const[/color] [color=#00979c]uint8_t[/color] [color=#000000]shiftFac[/color][color=#000000];[/color]
    [color=#00979c]const[/color] [color=#00979c]int32_t[/color] [color=#000000]fixedPointAHalf[/color][color=#000000];[/color]
    [color=#00979c]int32_t[/color] [color=#000000]filtered[/color] [color=#434f54]=[/color] [color=#000000]0[/color][color=#000000];[/color]
[color=#000000]}[/color][color=#000000];[/color]

It performs rounding, and has greater precision. Otherwise, the value could get stuck at a value lower or higher than the actual value.

Pieter

Power_Broker:
I know you’re probably set with using the barometric altimeter, but an even better choice would be a LiDAR solution like the LiDAR Lite V3. I use it myself on my RC planes and it’s much more accurate and reliable than a BMP-180 as long as you have line of sight to the ground and fly under 40 meters.

Just a thought.

Yea unfortunately that won't work for me. I need an adjustable baro pressure altimeter as that's what we use on aircraft an dial in the correct pressure to show the correct field elevation

PieterP:
A more precise version of the algorithm posted by Jremington would be:

[color=#00979c]class[/color] [color=#000000]EMA[/color] [color=#000000]{[/color]

public:
    EMA(uint8_t shiftFac)
        : shiftFaccolor=#000000[/color], fixedPointAHalf(1 << ((shiftFac * 2) - 1)) {}
    int32_t filter(int32_t value) {
        value = value << (shiftFac * 2);
        filtered = filtered + ((value - filtered) >> shiftFac);
        return (filtered + fixedPointAHalf) >> (shiftFac * 2);
    }

private:
    const uint8_t shiftFac;
    const int32_t fixedPointAHalf;
    int32_t filtered = 0;
};





It performs rounding, and has greater precision. Otherwise, the value could get stuck at a value lower or higher than the actual value.

Pieter

Ok so here is the bit of code that calculates the altitude in feet

float Af = Am * 3.28084 + 40;

I then have Af being covered to ALT to be displayed on the LCD like this

dtostrf(Af, 3, 0, Alt);

So how do I work the code you provided to round the "Af' data and still have it displayed on the LCD?

ok so filters helped smooth things out. thanks guys!

But id like to make it so the result of this: " float Af2 = (Af * 52) / 53 + (Af / 53);" is rounded to the nearest 5 as it still seems to be jumping around between 0-4' even with * 53 and / by 54

My favorite way of smoothing jittery raw input from incoming a/d conversions, is to continually calculate a running base 2 logarithmic integral, using a number of samples that is a multiple of 2, to avoid CPU intensive floating point math. It sounds complex but code wise its not, and it will generally produce a usable representative result with little processor overhead, at the expense of delay time before the result is representative. It functions as the mathematical equivalent of an an electrical RC (resistor/capacitor) filter. In an RC filter, the output voltage will settle to the equivalent of the input voltage after 5 "time constants". In this case, a time constant will be our "divisor" multiplied by the time between samples, multiplied again by 5. more on that in a minute. First, a code example...

Here's I'm deriving a result by continually combining 8 samples over time. I'll grab a new 16 bit analog value and store it. Next, though it seems backwards, I take a (currently empty) 32 bit accumulator and subtract from it 1/8 of its current value. Note that the division is done by bit shifting "shifting", which allows for very low CPU overhead. After this, I add my 'newSample' to the modified accumulator. Finally, I take the 32 bit accumulator value and divide it by 8 (also by shifting), and store the result as "smoothedResult".

the rest of the code is there to let you monitor how it all works on the Serial monitor, pausing every time 5 simulated "time constants" have gone by. What you'll see is that after each run of (in this case 40) iterations, the output will "settle" to the same value as the input.

After every 5 X the number of samples ( in this case 5 x 8, or 40 samples), you'll have a smoothed version. Again, it is the mathematical equivalent of having an electrical "RC" filter on the analog input. In an RC circuit, the output voltage will 'settle" to the input value after 5 "time constants". At 50 mS per sample. The initial delay may seem long, but any your samples probably come in faster than 50 ms, and ANY smoothing algorithm MUST cause some delay. And this is just the initial delay.... once the system is running, you can fine tune the response. For example, If you make DIV_SHIFT 2 (instead of 3) in this example, it will be the equivalent of dividing by 4 instead of 8, so the initial smoothed result will be available in 20 iterations instead of 40. If you make DIV_SHIFT 1, your initial smoothed result will settle in just 10 iterations.

Note that the important advantage of this method over a straight linear average is that more recent samples have a greater affect on the current result, than the older samples. Give it a try. You may find it useful.

void setup()
{
  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
}

#define DIV_SHIFT 3 /* divcide by 8 */
#define SAMPLE_TIME 50

uint32_t myAccumulator = 0;
uint16_t smoothedResult=0;
uint16_t sampCount= 0;

loop()
{

uint16_t newSample = analogRead(A0);
 delay(SAMPLE_TIME);
 
 myAccumulator -= (myAccumulator >> DIV_SHIFT); 

 myAccumulator += newSample;

 smoothedResult = (myAccumulator >> DIV_SHIFT);
 
 if (!sampCount) Serial.println("---------------------"); //myAccumulator =0;
 if (sampCount++ >= (1 << DIV_SHIFT) * 5) return; // 40 if DIV_SHIFT = 3

 
 Serial.print("iter=");
 Serial.print(sampCount, DEC);
 Serial.print("  raw=");
 Serial.print(newSample, DEC);
 Serial.print("  smoothed=");
 Serial.println(smoothedResult, DEC);

}

No longer need an average, but a rounding to the nearest 5' increment.

Divide by 5, round it, then multiply by 5 again.

Rounding can be done by adding 2 to the value before division, then just truncate that value before multiplying.

Qhergt1987:
No longer need an average, but a rounding to the nearest 5' increment.

That will just cause larger fluctuations: if your float value was previously fluctuating between 2.24 and 2.26, (an error of 0.02) it will fluctuate between 0 and 5 after truncating (an error of 5).

PeterPan321:
My favorite way of smoothing jittery raw input from incoming a/d conversions, is to continually calculate a running base 2 logarithmic integral, using a number of samples that is a multiple of 2, to avoid CPU intensive floating point math.

Looks like you're doing an exponential moving average.

But your coefficients don't seem right. If I understand you're code, it's result is:

YN = (7/8 * YN-1 + XN) * 1/8

= 7/64 * YN-1 + 8/64 * XN

So, the output value will asymptotically approach just 15/64 times the input value. Am I missing something?

Correction, looking at the math again, I'm not sure what the output approaches. But, it doesn't seem to be the input value.

PieterP:
That will just cause larger fluctuations: if your float value was previously fluctuating between 2.24 and 2.26, (an error of 0.02) it will fluctuate between 0 and 5 after truncating (an error of 5).

For example, it fluctuates between 148 and 152 im not showing any decimals in the
result. So rounding to nearest 5 would in this case just show 150 and won't be jumping around until it hits the 153-157 mark.

Most analog aircraft altimeters have 20' graduations on them and have a maximum tolerance of -+ 25' so I could even have it round to the nearest 10 or 20

Here is a video so you all can seehttps://youtu.be/YS9ndKRrKTc

gfvalvo:
Looks like you're doing an exponential moving average.

But your coefficients don't seem right. If I understand you're code, it's result is:

YN = (7/8 * YN-1 + XN) * 1/8

= 7/64 * YN-1 + 8/64 * XN

So, the output value will asymptotically approach just 15/64 times the input value. Am I missing something?

I guess it could be described as an Exponential_moving_average, except maybe that when you run calculations fo an electrical RC network, most formulas I've seen to calculate what I'm calling the "settling time" use either base 10 Logs, or Ln (the natural log base).

I cam up with this method (though I'm sure I just rediscovered one of those principals invented by "the universe") years ago, while working on embedded code to gather electrical data from various automated relay control systems, for electric utility substations. The readings from one device only offered amperes in increments of 10, so when the total amps were low, it looked like a sudden surge and would trigger alarms. The embedded systems I was working probably had less resources than the Arduino, and had to handle many tasks, so a math intensive (meaning floating point solution) was problematic.

Now truth be told, I'm good at coming up with algorithms that work, but have a hard time analyzing the math backwards to figure out WHY it works. So considering the level of responsibility here, its no surprise my managers made me create a similar demo and send it up to one of our top math coding gurus. He did confirm that as I theorized, it worked like an RC filter, and its output would settle to what was deemed "equally, for all 'practical' purposes" to the input, after what I'm calling 5 time constants. Make the right shift "2" or "1", and you'll have a near result in 20 or 10 iterations, respectively. And of course as you allow the simulation to continue, eventually they would become completely equal.

But yes, as with all integer approximations, you will lose "something". which usually can be corrected with a few adjustments.

As far as your last post, that the "output approaches something, but its not the input", my example is a short enough bit of code so you can try it for yourself. Of course if you cast everything to floats, and do FP division instead of using integer math, it will offer much less loss, at the expense of CPU overhead.

Also understand that the "5 time constants" is just a minimum. If you let the simulation run continuously, maybe with a potentiometer on the Analog input so you can try various voltage shifts, you'll see that the output is is indeed "tracking the output and behaving like an RC filter. the output approaches, but theoretically never exactly equals the input, and yet an RC filter has always been considered an acceptable "smoothing" solution.

Well, then I don't understand the implementation. Here's a simple Matlab version of your algorithm as I understand it. All the action happens in the 'for' loop. The format is somewhat C-like. Let me know if it doesn't faithfully follow your algorithm. I have the input set to a constant value of 1.

iterations = 25;
output = zeros(1,iterations);
input = 1;
for index = 2:iterations
    temp = output(index-1)- output(index-1) / 8;
    temp = temp + input;
    temp = temp / 8;
    output(index) = temp;
end
plot(0:(iterations-1), output, '-ms', 'LineWidth',2,...
'MarkerEdgeColor','k',...
'MarkerFaceColor',[.49 1 .63])

xlim([-2 iterations+5])
xlabel('Interation')
ylabel('Output Value')

Here's the output plot:
Plot.jpg

Obviously, the output doesn't approach 1. Can you spot the disconnect with your implementation?

Plot.jpg

The division by 8 doesn't happen to the stored variable in the loop. It only happens to the final result.

Not sure about MatLab or if this would work, but you could try this:

iterations = 25;
output = zeros(1,iterations);
input = 1;
for index = 2:iterations
    temp = output(index-1)- output(index-1) / 8;
    temp = temp + input;
    output(index) = temp;
end
plot(0:(iterations-1), output / 8, '-ms', 'LineWidth',2,...
'MarkerEdgeColor','k',...
'MarkerFaceColor',[.49 1 .63])

xlim([-2 iterations+5])
xlabel('Interation')
ylabel('Output Value')