Fuel Gauge signal smoothing with arduino?

Hey everyone, so here is my problem. Im relatively new to arduino, and I already used mine to make a gas gauge for my antique ford! It reads the Ohms from the gas level unit, in the gas tank, through the A0 pin on the board. But as the car drives, the gas sloshes around a lot, and so the 5 LED meter, bounces up and down on hills and on dips in the road and so on. So basically, I want to know if theres a way to prevent that. I have very little programming skill, I simply edited a simple example script that came with the arduino computer program. Im sorry I am new to this so bare with me!
Thanks for any help!
Here is the code I am using:

// these constants won't change:
const int analogPin = A0;   // the pin that the potentiometer is attached to
const int ledCount = 5;    // the number of LEDs in the bar graph

int ledPins[] = { 
  5, 6, 7,8,9 };   // an array of pin numbers to which LEDs are attached


void setup() {
  // loop over the pin array and set them all to output:
  for (int thisLed = 0; thisLed < ledCount; thisLed++) {
    pinMode(ledPins[thisLed], OUTPUT); 
  }
}

void loop() {
  // read the potentiometer:
  int sensorReading = analogRead(analogPin);
  // map the result to a range from 0 to the number of LEDs:
  int ledLevel = map(sensorReading, 425, 100, 0, ledCount);

  // loop over the LED array:
  for (int thisLed = 0; thisLed < ledCount; thisLed++) {
    // if the array element's index is less than ledLevel,
    // turn the pin for this element on:
    if (thisLed < ledLevel) {
      digitalWrite(ledPins[thisLed], HIGH);
    } 
    // turn off all pins higher than the ledLevel:
    else {
      digitalWrite(ledPins[thisLed], LOW); 
    }
  }
}

You really only need to update the gauge once every minute. So the simplest way to fix it is to take the average of all the sensor readings over one minute.

Any idea for the coding on that? The changes to my coding? Because I dont know enough about coding to do that :confused:

Without actually writing any code, conceptually you might try to do something like this:

Setup:
Capture current mills to a timer variable.

Start:
If current mills minus a timeout (60000 == 1 minute) isn't equal or greater than the timer variable, call the read function
else call the display function
loop back to start (happens automagically at the end of the start() function).

read function:
Read the analog pin and add to an accumulation variable (will probably need to be a long)
Increment a counter variable (will probably need to be a long)

display function:
divide the counter variable into the accumulation variable (finally computing the average).
Perform your existing algorithm to figure out LED display from the analog value (ok, I admit. I didn't read your code...)
Display your leds
(optional) blink an additional LED to know when the display updates.
Set the counter variable to 0
Set the accumulation variable to 0
Capture current mills to a timer variable.

Hopefully that should get you going.

But uhm... heres the problem. When I say inexperienced with programming, i mean INEXPERIANCED AS HELL :stuck_out_tongue: I mean I can decipher programming sometimes but cannot write it for my life.

This is doing stuff without changing any of your own code, just adding:

long lastRun = 0;  // this goes outside of the loop()

void loop() {
  if (millis() - lastRun > 60000) {
    // read the potentiometer:
    int sensorReading = analogRead(analogPin);
    // map the result to a range from 0 to the number of LEDs:
    int ledLevel = map(sensorReading, 425, 100, 0, ledCount);

    // loop over the LED array:
    for (int thisLed = 0; thisLed < ledCount; thisLed++) {
      // if the array element's index is less than ledLevel,
      // turn the pin for this element on:
      if (thisLed < ledLevel) {
        digitalWrite(ledPins[thisLed], HIGH);
      } 
      // turn off all pins higher than the ledLevel:
      else {
        digitalWrite(ledPins[thisLed], LOW); 
      }
    }
    lastRun = millis();
  }
}

Actually KirAsh4, you would need the analogRead() to average hundreds of readings over that 60000 milliseconds. Taking one reading every minute is still going to cause large swings in the tank level readings from one minute to the next.

FordGalaxie:
But uhm... heres the problem. When I say inexperienced with programming, i mean INEXPERIANCED AS HELL :stuck_out_tongue: I mean I can decipher programming sometimes but cannot write it for my life.

So, before we get into it, I'd like to judge your programming ability. The code that you supplied to us looks good and shows a bit of cleverness looping arrays to set pinmodes, set LED states, etc. Did you come up with that on your own? If no, do you understand what is happening (i.e. can you read each line and describe to yourself what each line does and why)?

I'd like to know how much hand-holding to provide, but I don't want to cross the line into condescending.

Unfortunately I found the coding under a built in example that was included with the Arduino program. So I was only able to modify it to fit my 5 LED's instead of the 10 it was intended for, and then make it so it read full and empty correctly.. Then I just changed the numbers around a bit to be calibrated to the sending unit in my fuel tank. In case you dont know how a gas gauge works, The sending unit in the gas tank, has a float on it. That floats on top of the gas and is attached to a lever. As it raises or lowers, it changed the resistance reading going through the wires to the gauge. For example, my gas tank sending unit reads about 75 ohms at Full, and about 10 ohms at empty. Or maybe thats reversed? I cant remember but you get the general idea!

Drat... too long. Cutting this reply into two messages... (Too late to trim...)

FordGalaxie:
Unfortunately I found the coding under a built in example that was included with the Arduino program. So I was only able to modify it to fit my 5 LED's instead of the 10 it was intended for, and then make it so it read full and empty correctly.. Then I just changed the numbers around a bit to be calibrated to the sending unit in my fuel tank. In case you dont know how a gas gauge works, The sending unit in the gas tank, has a float on it. That floats on top of the gas and is attached to a lever. As it raises or lowers, it changed the resistance reading going through the wires to the gauge. For example, my gas tank sending unit reads about 75 ohms at Full, and about 10 ohms at empty. Or maybe thats reversed? I cant remember but you get the general idea!

No problem. I'll try to write this as close to a low level tutorial as I can. Remember, this is just one way of approaching the problem.

One of the first things is to think about the values/variables we are using. Generally I see that there are four types of variables: hardware, input, output, and processing. Lets take them one at a time. (This also gives you a chance to correct anything that I've gotten wrong...)

The hardware variables generally describe the hardware connections, and usually defined as constant variables by using the const keyword. Things like pin numbers.

The input is simple, the analog input from the fuel float sensor. We know that the range is 10-75 ohms, but the analog input reads volts, not resistance, and only has a range of 0-5V (or 0-3.3V on the Due). Not only that it represents the voltages as 'count's from 0 to 1024. Is it possible to wire the fuel sensor with 5VDC? If so, put a DMM on the output of the sensor and move the float over the expected range and report back the actual voltages. Or, if you are feeling brave about it we could work that testing part into building this code.

The output is also simple on the surface. 5 LEDs as a bargraph display. This can be done several ways, but if we want to keep the amount of external components to a minimum lets first plan on 5 digital output pins, one for each LED. (Don't forget to put a current limiting in series with each LED so we don't burn out the Arduino or the LEDs with too much current.) Because this is how the original example does it we can steal that code. :smiley: Optionally we can use another digital output pin to flash once on display update.

The processing variables will probably change as we work through the program, but I can think of a few to start using:

  • Some way of keeping track of a timer. We don't need the fine grained timing of micros() so we will use millis(). Both time counters start at zero on power up and continually increment while power is on. The timer we are going to use, millis() increment once each millisecond (or 1000 times a second). This count is a 32bit number, that won't rollover for about 50 days, in Arduino language a long. Unless you plan on the car being on for 50 days straight, we don't even need to worry about rollover in our code. So, a variable of size long to take note of the value of mills() at the start of each timing interval.
  • A display update time interval. Basically, how long do we want to average readings before updating the display. For testing, I'd probably suggest 1 second update time, but we make this a constant variable at the beginning to make it easier to change in the future.
  • An accumulation variable to collect readings to. Need to make sure that it is large enough to hold all the numbers added together during the update interval.
  • A counter to know how many values were added to the accumulation for calculating the average.
  • For the optional update flasher will need another long for it's timer and another interval for how long the LED should stay on.

Let's first work on a structure. To keep the logical flow of reading the loop() function, lets do all the work in function calls, but keep the timing in loop(). I'm going to quote my original outline and try to fill things in. Sorry, but you will have to cut and paste several code blocks to get something as I'll be explaining as we go along both between code blocks and with code comments...

First our starting variables

// Hardware
byte const fuelSensorPin = A0; // Pin the fuel sensor signal is connected to, change to match your reality
const byte ledBargraph[] = // an array of pin numbers to which LEDs are attached
{
  5, 6, 7, 8, 9
};
const byte bargraphCount = sizeof(ledBargraph/sizeof(ledBargraph[0])); // automagically change the count of LED, and only do the calculation once.
const byte ledIndicator = 13; // even if you don't wire this to an external LED, it should help with troubleshooting to have the on-board LED do something useful.

// Input variables
unsigned int fuelSensor; // This may not be needed...

// Output variables
byte bargraphDisplay; // Can get away with byte because the value will never get near the max value of 255

// Processing variables
long averageStart; // Timer for collecting readings for the average
long displayInterval = 1000; // number of milliseconds between calculate average and display output
long averageSum = 0; // not sure if we will need this size, but the continuous additions will add up quickly.
unsigned int averageCount = 0; // not sure if we will need this size, but I imagine averageSum as a long would overflow before this would as an unsigned int. Hopefully...
unsigned int finalAverage; // for calculating the final average into. Probably could instead be a local variable to the display function instead of this global...
long indicatorStart; // Timer for blinking the indicator led
long indicatorInterval = 250; // number of milliseconds to keep the indicator led on.
boolean indicatorFlag = FALSE; // a flag to help keep track of the indicator status.
const unsigned int sensorHigh = 1024; // High and low ranges for the expected sensor inputs in analog input counts. Modify these here once we know the actual values.
const unsigned int sensorLow = 0;

You can change the order of the above variables. I haven't plugged any of this into the IDE, so I'm not sure if we can get away with the function calls for calculating bargraphCount here, or if the actual calculation will have to move to setup().

[continued next posting]

[continued from previous posting]

Sembazuru:
Without actually writing any code, conceptually you might try to do something like this:

Setup:
Capture current mills to a timer variable.

Well for setup lets also start up the serial port for diagnostics, and this is where we set the many led pins as outputs

void setup()
{
  // Set pins for LEDs as digital outputs.
  for (int thisLed = 0; thisLed < bargraphCount; thisLed++)
  {
    pinMode(ledBargraph[thisLed], OUTPUT);
  }
  pinMode(ledIndicator, OUTPUT);
  // Start up the serial port for diagnostic outputs
  Serial.begin(115200); // Change this to whatever your like running your Serial Monitor at.
  while (!Serial) {} // Wait for serial port to connect. Needed for Leonardo only. I include this for anyone who takes my code and tries to use it on a Leonardo.
  // Start the timer for counting averages
  averageStart = mills();
  }
}

Start:
If current mills minus a timeout (60000 == 1 minute) isn't equal or greater than the timer variable, call the read function
else call the display function
loop back to start (happens automagically at the end of the start() function).

Now for the main loop. We only take care of timing issues here, and do the actual work elsewhere in function calls. That helps keep the code clutter down a bit.

void loop()
{
  if (mills() - displayInterval < averageStart)
  {
    readSensor();
  }
  else
  {
    updateDisplay();
  }
  if (indicatorFlag) // check to see if the indicator is on, if not don't waste the clock cycles doing the math with mills()
  {
    if (mills() - indicatorInterval >= indicatorStart)
    {
      digitalWrite(ledIndicator, LOW); // LED indicator timed out so turn it off
      indicatorFlag = FALSE;
    }
  }
}

read function:
Read the analog pin and add to an accumulation variable (will probably need to be a long)
Increment a counter variable (will probably need to be a long)

display function:
divide the counter variable into the accumulation variable (finally computing the average).
Perform your existing algorithm to figure out LED display from the analog value (ok, I admit. I didn't read your code...)
Display your leds
(optional) blink an additional LED to know when the display updates.
Set the counter variable to 0
Set the accumulation variable to 0
Capture current mills to a timer variable.

Hopefully that should get you going.

I'm running out of time tonight, so I'll leave you with the above. Except for one small thing... It won't compile without at least the framework for the two function calls. So tack this on the end. Feel free to attempt something based on my start.

readSensor();
{
// Here we need to read the sensor and add the value to the continuing to increasing averageSum variable. Also increment the averageCount variable.
}

updateDisplay();
{
// well we do more than just update the display.
// We need to
// * actually calculate the final average
// * use the map() function to change it to a number of LEDs to light
// * update the bargraph display
// * turn on the indicator LED
// * set the indicatorLED flag False
// * set the indicatorStart variable to the current mills()
// * reset the averageSum and averageCount variables back to zero
// * set the averageStart variable to the current mills()
// * and anything else I've forgotten at 3AM... ;-)
}

See if you can get the whole thing to compile, and then play around with the code using the serial monitor to get a feel of what is happening under the rug. Might actually have to throw some delay()s in there somewhere if we averageSum starts overflowing...

I can steal a little time at work during lunch... For a start, and something to play with, here is what (should) be all you need for readSensor(), and some diagnostics for updateDisplay().

readSensor();
{
// Here we need to read the sensor and add the value to the continuing to increasing averageSum variable. Also increment the averageCount variable.
  averageSum = averageSum + analogRead(fuelSensorPin); //  instead of reading analogRead() to a variable (I had defined fuelSensor for this) I'm using it directly.
  averageCount++; // this is a short cut syntax for averageCount = averageCount + 1;
}

updateDisplay();
{
// well we do more than just update the display.
// We need to
// * actually calculate the final average
  finalAverage = averageSum / averageCount;
  Serial.println(F("Average calculations:")); // using the F() macro is just a habit I've gotten into to not waste valuable SRAM with diagnostic character strings...
  Serial.print(F("Accumulated sum of analogRead() = "));
  Serial.println(averageSum);
  Serial.print(F("Number of analogRead()s = "));
  Serial.println(averageCount);
  Serial.print(F("Calculated average = "));
  Serial.println(finalAverage);
  Serial.println();
// * use the map() function to change it to a number of LEDs to light
  bargraphDisplay = map(finalAverage, sensorHigh, sensorLow, 0, bargraphCount); // Stealing the code from the original sample, using our variables
  Serial.print(F("Number of LEDs to light up = "));
  Serial.println(bargraphDisplay);
// * update the bargraph display
  // See if you can come up with this. It should be basically directly from the original sample, but updated with the variable names we are using.
// * turn on the indicator LED
  digitalWrite(ledIndicator, HIGH);
// * set the indicatorLED flag -F-a-l-s-e- ... Nope. Need to set it True. I wrote the wrong thing late last night. My apologies.
  indicatorFlag = TRUE;
// * set the indicatorStart variable to the current mills()
  indicatorStart = mills();
// * reset the averageSum and averageCount variables back to zero
  averageSum = 0;
  averageCount = 0;
// * set the averageStart variable to the current mills()
  averageStart = mills();
// * and anything else I've forgotten at 3AM... ;-)
  Serial.println();
  Serial.println(F(" -*-*-*-*-*-*-*-*-*-*-*-*-*-"));
  Serial.println();
}

I just threw this out there, trying to explain some to you with comments. Any questions please ask.

At some point I should probably cut and paste all this code into an IDE just to make sure it compiles... Ehh... I'll wait for you to have problems. :wink: (Then we can work through fixing the problems...) :stuck_out_tongue:

At some point I should probably cut and paste all this code into an IDE just to make sure it compiles

Yes, you should. No, it won't.

Functions need return types. They don't end with a semicolon.

PaulS:

At some point I should probably cut and paste all this code into an IDE just to make sure it compiles

Yes, you should. No, it won't.

Functions need return types. They don't end with a semicolon.

You are right. What I really should have done was write it in the IDE last night with my UNO to make sure it works, then cut and paste into my message(s).

I checked it out and found many type-os. Like using mills() instead of millis(), capitalizing TRUE and FALSE instead of lowercase true and false, the malformed function declarations. I also had an extra brace (found using Auto Format in the IDE). I also had the parentheses wrong for calculating the size of the array of pin numbers for the bargraph. I guess that's what I get for writing it piecemeal in the message editor while watching a couple Nova episodes and some House Hunters...

Here is the current code so far (it does include a project for the OP, so I'm not handing him everything) that compiles. I don't have my UNO here with me at work so I don't know if it actually works as expected...

// Hardware
const byte fuelSensorPin = A0; // Pin the fuel sensor signal is connected to, change to match your reality
const byte ledBargraph[] = // an array of pin numbers to which LEDs are attached
{
  5, 6, 7, 8, 9
};
const byte bargraphCount = sizeof(ledBargraph) / sizeof(ledBargraph[0]); // automagically change the count of LED, and only do the calculation once.
const byte ledIndicator = 13; // even if you don't wire this to an external LED, it should help with troubleshooting to have the on-board LED do something useful.

// Input variables
unsigned int fuelSensor; // This may not be needed...

// Output variables
byte bargraphDisplay; // Can get away with byte because the value will never get near the max value of 255

// Processing variables
long averageStart; // Timer for collecting readings for the average
long displayInterval = 1000; // number of milliseconds between calculate average and display output
long averageSum = 0; // not sure if we will need this size, but the continuous additions will add up quickly.
unsigned int averageCount = 0; // not sure if we will need this size, but I imagine averageSum as a long would overflow before this would as an unsigned int. Hopefully...
unsigned int finalAverage; // for calculating the final average into. Probably could instead be a local variable to the display function instead of this global...
long indicatorStart; // Timer for blinking the indicator led
long indicatorInterval = 250; // number of milliseconds to keep the indicator led on.
boolean indicatorFlag = false; // a flag to help keep track of the indicator status.
const unsigned int sensorHigh = 1024; // High and low ranges for the expected sensor inputs in analog input counts. Modify these here once we know the actual values.
const unsigned int sensorLow = 0;

void setup()
{
  // Set pins for LEDs as digital outputs.
  for (int thisLed = 0; thisLed < bargraphCount; thisLed++)
  {
    pinMode(ledBargraph[thisLed], OUTPUT);
  }
  pinMode(ledIndicator, OUTPUT);
  // Start up the serial port for diagnostic outputs
  Serial.begin(115200); // Change this to whatever your like running your Serial Monitor at.
  while (!Serial) {
  } // Wait for serial port to connect. Needed for Leonardo only. I include this for anyone who takes my code and tries to use it on a Leonardo.
  // Start the timer for counting averages
  averageStart = millis();
}

void loop()
{
  if (millis() - displayInterval < averageStart)
  {
    readSensor();
  }
  else
  {
    updateDisplay();
  }
  if (indicatorFlag) // check to see if the indicator is on, if not don't waste the clock cycles doing the math with mills()
  {
    if (millis() - indicatorInterval >= indicatorStart)
    {
      digitalWrite(ledIndicator, LOW); // LED indicator timed out so turn it off
      indicatorFlag = false;
    }
  }
}

void readSensor()
{
  // Here we need to read the sensor and add the value to the continuing to increasing averageSum variable. Also increment the averageCount variable.
  averageSum = averageSum + analogRead(fuelSensorPin); //  instead of reading analogRead() to a variable (I had defined fuelSensor for this) I'm using it directly.
  averageCount++; // this is a short cut syntax for averageCount = averageCount + 1;
}

void updateDisplay()
{
  // well we do more than just update the display.
  // We need to
  // * actually calculate the final average
  finalAverage = averageSum / averageCount;
  Serial.println(F("Average calculations:")); // using the F() macro is just a habit I've gotten into to not waste valuable SRAM with diagnostic character strings...
  Serial.print(F("Accumulated sum of analogRead() = "));
  Serial.println(averageSum);
  Serial.print(F("Number of analogRead()s = "));
  Serial.println(averageCount);
  Serial.print(F("Calculated average = "));
  Serial.println(finalAverage);
  Serial.println();
  // * use the map() function to change it to a number of LEDs to light
  bargraphDisplay = map(finalAverage, sensorHigh, sensorLow, 0, bargraphCount); // Stealing the code from the original sample, using our variables
  Serial.print(F("Number of LEDs to light up = "));
  Serial.println(bargraphDisplay);
  // * update the bargraph display
  // See if you can come up with this. It should be basically directly from the original sample, but updated with the variable names we are using.
  // * turn on the indicator LED
  digitalWrite(ledIndicator, HIGH);
  // * set the indicatorLED flag -F-a-l-s-e- ... Nope. Need to set it True. I wrote the wrong thing late last night. My apologies.
  indicatorFlag = true;
  // * set the indicatorStart variable to the current mills()
  indicatorStart = millis();
  // * reset the averageSum and averageCount variables back to zero
  averageSum = 0;
  averageCount = 0;
  // * set the averageStart variable to the current mills()
  averageStart = millis();
  // * and anything else I've forgotten at 3AM... ;-)
  Serial.println();
  Serial.println(F(" -*-*-*-*-*-*-*-*-*-*-*-*-*-"));
  Serial.println();
}

I brought my UNO to work yesterday and played with this code a bit. (Yes, I just can't leave well enough alone...) I made a few discoveries.

At first I was noticing that the averages were coming out higher than the maximum. Seems that unsigned int wasn't large enough for counting all the analogRead()s so I upped it to unsigned long. Now, even with a 60 second update interval the averages appear to be good.

Then I was noticing an odd behavior with map(). Consider the line of code:

bargraphDisplay = map(finalAverage, sensorHigh, sensorLow, 0, bargraphCount);

All the variables are unsigned. (bargraphDisplay and bargraphCount are byte and the other three are all unsigned int.) Replacing the constant variables with values that line becomes:

bargraphDisplay = map(finalAverage, 1024, 0, 0, 5);

Oh, hey... looks like I have the sensorHigh and sensorLow backwards... Lets fix that...

bargraphDisplay = map(finalAverage, 0, 1024, 0, 5);

Now the interesting part is that the only time the map() function would give me a 5 is if finalAverage == 1024. I would have expected that given a range of 6 values (0 through 5) map() would create 6 equal sized 'bins' of the input range. I haven't done any math or testing to see where the changes occur other than replacing finalAverage with 1023 and 1024. The only time when the output of map() was 5 is when finalAverage == 1024. So to kludge this I forced an offset on the input range. I'm not sure how well this will hold up to expanding the output range to more LEDs, but basically I took the input range (sensorHigh - sensorLow) divided by the number of output values (bargraphCount + 1), and subtracted the quotient from sensorHigh to use as the high of the input range of map(). So now the (working?) map() statement reads:

  bargraphDisplay = map(finalAverage, sensorLow, sensorHigh - ((sensorHigh - sensorLow) / (bargraphCount + 1)), 0, bargraphCount);

While figuring this out I noted an odd thing with my UNO. During many of my tests I had A0 jumpered to the +5VDC output and the average ADC counts were 1022. Must be an odd ADC and/or noise issue on my board as summing many 1024 instead of the analogRead()s results in an average of 1024.

I wanted to add a couple more diagnostic lines (especially since there is no code to drive a LED barcode display yet...) and decided that updateDisplay() was getting too cluttered with diagnostics lines. So I moved all the diagnostic lines to a new function, updateDiagnostics().

At this stage I allowed the sketch to run overnight at work with displayInterval set to 30000 (30 seconds). This morning when I returned to work I didn't see any indication of overflows or crashes. Yay!

One thing that bugged me was the way I was calculating the timer. It seemed to spam the serial port once for each iteration of loop() until the length of time of the interval. Going back to sources I looked at the if statement in BlinkWithoutDelay.ino and noticed that it was subtracting the last captured time from the current time and comparing that with the interval. Whereas, I was subtracting the interval from the current time and comparing it to the last captured time. I changed the method to the BlinkWithoutDelay.ino style and it now seems to work. I think the issue was until the current time was greater than the interval, my original subtraction was rolling around and returning an exceedingly large number. But the method from BlinkWithoutDelay.ino, the last captured time will never be larger than the current time, so the subtraction will never try to be negative (which results in a very large number with unsigned datatypes...) While playing around with this, I decided that we don't need a conditional to take a reading. So the first few lines of loop() now read:

  readSensor(); // Always want to take a reading
  if(millis() - averageStart > displayInterval) // Check to see if it is time to update the display
  {
    updateDisplay();
  }

I may have made a few other minor changes so I'll attach the full code to this post for the insanely curious. Here is some output with the current code:

Actual output after the cut line.
The update interval is 30000ms (30.000 seconds).
The first two updates are with A0 connected to +5V
During the data collection period for the third update, the jumper from A0 to +5V was removed
The fourth update A0 is floating
During the data collection period for the fifth update, A0 is jumpered to +3.3V
The sixth and seventh updates are with A0 connected to +3.3V

------------------------8<------------------------
Average calculations:
	Accumulated sum of analogRead() = 254659464
	Number of analogRead()s = 248934
	Calculated average = 1022

Number of LEDs to light up = 5

Number of reads per second since last update = 8297.80
Number of reads per second since start = 8297.80

 -*-*-*-*-*-*-*-*-*-*-*-*-*-

Average calculations:
	Accumulated sum of analogRead() = 254631852
	Number of analogRead()s = 248907
	Calculated average = 1022

Number of LEDs to light up = 5

Number of reads per second since last update = 8296.90
Number of reads per second since start = 8297.35

 -*-*-*-*-*-*-*-*-*-*-*-*-*-

Average calculations:
	Accumulated sum of analogRead() = 128993638
	Number of analogRead()s = 248908
	Calculated average = 518

Number of LEDs to light up = 3

Number of reads per second since last update = 8296.93
Number of reads per second since start = 8297.21

 -*-*-*-*-*-*-*-*-*-*-*-*-*-

Average calculations:
	Accumulated sum of analogRead() = 252
	Number of analogRead()s = 248899
	Calculated average = 0

Number of LEDs to light up = 0

Number of reads per second since last update = 8296.63
Number of reads per second since start = 8297.07

 -*-*-*-*-*-*-*-*-*-*-*-*-*-

Average calculations:
	Accumulated sum of analogRead() = 102654376
	Number of analogRead()s = 248906
	Calculated average = 412

Number of LEDs to light up = 2

Number of reads per second since last update = 8296.87
Number of reads per second since start = 8297.03

 -*-*-*-*-*-*-*-*-*-*-*-*-*-

Average calculations:
	Accumulated sum of analogRead() = 170236541
	Number of analogRead()s = 248907
	Calculated average = 683

Number of LEDs to light up = 3

Number of reads per second since last update = 8296.90
Number of reads per second since start = 8297.01

 -*-*-*-*-*-*-*-*-*-*-*-*-*-

Average calculations:
	Accumulated sum of analogRead() = 170231831
	Number of analogRead()s = 248909
	Calculated average = 683

Number of LEDs to light up = 3

Number of reads per second since last update = 8296.97
Number of reads per second since start = 8297.00

 -*-*-*-*-*-*-*-*-*-*-*-*-*-

Fuel_Gauge_signal_smoothing_with_arduino.ino (5.96 KB)

I would have expected that given a range of 6 values (0 through 5) map() would create 6 equal sized 'bins' of the input range.

Nothing we can do about incorrect assumptions on your part.

During many of my tests I had A0 jumpered to the +5VDC output and the average ADC counts were 1022.

That's reasonable. The ADC charges a capacitor, timing how long that takes. Getting a 1023 reading is the extreme. 1022 is more common as the upper limit when 5V is input.

I changed the method to the BlinkWithoutDelay.ino style and it now seems to work.

I hope you learned something in the process.

But the method from BlinkWithoutDelay.ino, the last captured time will never be larger than the current time, so the subtraction will never try to be negative (which results in a very large number with unsigned datatypes...)

The last captured time can be larger than the current time, after about 49 days, when the value returned by millis() rolls over. But, subtraction is guaranteed to work. There have been several threads that have gone into excruciating detail as to why.

PaulS:

I would have expected that given a range of 6 values (0 through 5) map() would create 6 equal sized 'bins' of the input range.

Nothing we can do about incorrect assumptions on your part.

Agreed, and I wasn't expecting you to do anything about it. :stuck_out_tongue: I haven't fully grokked the stated algorithm on the reference page for map() yet, but it looks like a simple polynomial with slope and offset. I don't have a complete feel for how the decimal truncation errors effect the result. My kludge seems to work, but I recognize it for what it is. A kludge.

During many of my tests I had A0 jumpered to the +5VDC output and the average ADC counts were 1022.

That's reasonable. The ADC charges a capacitor, timing how long that takes. Getting a 1023 reading is the extreme. 1022 is more common as the upper limit when 5V is input.

Good to remember. Theory is fine, but empirical results are best. Especially if one forgets (or doesn't realize) to take some details in the theory. I work with both theoretical and experimental physicists, so I get to see both sides.

I changed the method to the BlinkWithoutDelay.ino style and it now seems to work.

I hope you learned something in the process.

But the method from BlinkWithoutDelay.ino, the last captured time will never be larger than the current time, so the subtraction will never try to be negative (which results in a very large number with unsigned datatypes...)

The last captured time can be larger than the current time, after about 49 days, when the value returned by millis() rolls over. But, subtraction is guaranteed to work. There have been several threads that have gone into excruciating detail as to why.

I've read through many of those, but didn't remember the details (nor exactly what threads those were), other than addition is a Bad Ideatm. That's why I went back to the provided example instead of slogging through an indeterminate number of search results. I knew exactly where to find the provided example. 8)

Also, with the OP's expected application (fuel gauge on a car), I wouldn't expect it to be running constantly for anywhere near 49 days. (But there I am making assumptions again. When will I ever learn?) :wink:

I recognise there has been some considerable effort given to helping the OP in this topic. But just for discussion, couldn't we just put a loading on the latest reading so it didn't influence the average so much. I would suggest giving it a penalty of 1/100th.

create a global integer

int averageReading;

in setup initialise this average

averageReading = analogRead(analogPin);

and then in loop where we read the sensor and map lets do

int sensorReading = analogRead(analogPin);
averageReading = (averageReading*100 + sensorReading) / 101;
int ledLevel = map(averageReading, 0, 1022, 0, ledCount);

so putting that all together with OP original sketch

// these constants won't change:
const int analogPin = A0;   // the pin that the potentiometer is attached to
const int ledCount = 5;    // the number of LEDs in the bar graph

int ledPins[] = { 
  5, 6, 7,8,9 };   // an array of pin numbers to which LEDs are attached
int averageReading;

void setup() {
  // loop over the pin array and set them all to output:
  for (int thisLed = 0; thisLed < ledCount; thisLed++) {
    pinMode(ledPins[thisLed], OUTPUT); 
  }
  averageReading = analogRead(analogPin); //set an initial value
}

void loop() {
  // read the potentiometer:
  int sensorReading = analogRead(analogPin);
  //calculate a new average from last 100 readings
  averageReading = (averageReading*100 + sensorReading) / 101;
  
  // map the result to a range from 0 to the number of LEDs:
  int ledLevel = map(averageReading, 0, 1022, 0, ledCount);

  // loop over the LED array:
  for (int thisLed = 0; thisLed < ledCount; thisLed++) {
    // if the array element's index is less than ledLevel,
    // turn the pin for this element on:
    if (thisLed < ledLevel) {
      digitalWrite(ledPins[thisLed], HIGH);
    } 
    // turn off all pins higher than the ledLevel:
    else {
      digitalWrite(ledPins[thisLed], LOW); 
    }
  }
}

KrazeyNKrusty:
I recognise there has been some considerable effort given to helping the OP in this topic. But just for discussion, couldn't we just put a loading on the latest reading so it didn't influence the average so much. I would suggest giving it a penalty of 1/100th.

create a global integer

int averageReading;

in setup initialise this average

averageReading = analogRead(analogPin);

and then in loop where we read the sensor and map lets do

int sensorReading = analogRead(analogPin);

averageReading = (averageReading*100 + sensorReading) / 101;
int ledLevel = map(averageReading, 0, 1022, 0, ledCount);





so putting that all together with OP original sketch


// these constants won't change:
const int analogPin = A0;   // the pin that the potentiometer is attached to
const int ledCount = 5;    // the number of LEDs in the bar graph

int ledPins[] = {
  5, 6, 7,8,9 };   // an array of pin numbers to which LEDs are attached
int averageReading;

void setup() {
  // loop over the pin array and set them all to output:
  for (int thisLed = 0; thisLed < ledCount; thisLed++) {
    pinMode(ledPins[thisLed], OUTPUT);
  }
  averageReading = analogRead(analogPin); //set an initial value
}

void loop() {
  // read the potentiometer:
  int sensorReading = analogRead(analogPin);
  //calculate a new average from last 100 readings
  averageReading = (averageReading*100 + sensorReading) / 101;
 
  // map the result to a range from 0 to the number of LEDs:
  int ledLevel = map(averageReading, 0, 1022, 0, ledCount);

// loop over the LED array:
  for (int thisLed = 0; thisLed < ledCount; thisLed++) {
    // if the array element's index is less than ledLevel,
    // turn the pin for this element on:
    if (thisLed < ledLevel) {
      digitalWrite(ledPins[thisLed], HIGH);
    }
    // turn off all pins higher than the ledLevel:
    else {
      digitalWrite(ledPins[thisLed], LOW);
    }
  }
}

Interesting concept. A bit leaner on SRAM usage because it doesn't use as many longs. Thinking over it, I don't really see any major issues, especially given application.

It's up to the OP which direction he wants to take his project. (Though I still think I'd have ledCount calculated. That way if more LEDs are added in the future one just has to change one thing instead of two.)