Calculating correct potentiometer reading

I am working on this project to show propane tank level. Basically, the circuit has two potentiometers, one is for adjusting the LCD contrast and the other is the actual sensor. It has one green LED that stays solid as long as the tank level is above 10% and another red LED that blinks when the sensor falls below 10%.

NOTE: This is for a large capacity propane tank that has a sensor port not like a barbeque tank.

The issue I'm trying to solve is how to better calculate the sensor reading more accurately to show 100% when full and 0% when completely empty. Currently it shows reading like 99.2% for full and .78 for empty. The sensor is also not exactly 10K, 10.75k is the actual reading.

This is the code I am using:

int myVoltPin = A2;         // Initialize variable to Analog pin A2
int readVal;                // Initialize variable to hold sensor value
float v2;                   // Initialize variable used to calculate display %
int delayT = 250;           // Initialize variable for serial print *Optional
int ledPin13 = 13;          // Initialize digital pin 13
int ledPin8 = 8;            // Initialize digital pin 8
#include <LiquidCrystal.h>  // include the library code:

const int rs = 12, en = 11, d4 = 5, d5 = 4, d6 = 3, d7 = 2;  // initialize the library by associating any needed LCD interface pin
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);                   // with the arduino pin number it is connected to

void setup()

{
  pinMode(ledPin13, OUTPUT);  // Set pinmode on pin 13 to OUTPUT
  pinMode(ledPin8, OUTPUT);   // Set pinmode on pin 8 to OUTPUT
  Serial.begin(9600);         // Set baud rate to 9600
  lcd.begin(16, 2);           // Set the cursor to column 16, line 2
  lcd.print("Tank Monitor");  // Print a message to the LCD.
}

void loop() {
  readVal = analogRead(myVoltPin);  // Read sensor resistance
  v2 = (5.15 / 1023.) * readVal;    // Calculate value from sensor to proper scale
                                    //Serial.println(v2);                 // optional serial print calculated sensor value during adjustment
                                    //delay(delayT);                      // Optional delay for serual print of calculated sensor value during adjustment

  if (v2 <= 0.50)                  // Set condition to run if calculated sensor value is less than or equal to .5 value
  {                                // This will blink the LED set on the condition
    digitalWrite(ledPin13, HIGH);  // Set digital pin 13 to HIGH
    delay(100);                    // Delay ON time for digital pin 13
    digitalWrite(ledPin13, LOW);   // Set digital pin 13 to LOW
    delay(500);                    // Delay OFF time for digital pin 13
  }

  if (v2 >= 0.51)                 // Set condition to run if calculated sensor vaue is greater than or equal to .51
  {                               // This will keep this LED on while the condition is true
    digitalWrite(ledPin8, HIGH);  // Set digital pin 8 to HIGH
  } else                          // If condition is false then run cpde below
  {
    digitalWrite(ledPin8, LOW);  // Set  digital pin 8 to low if condition is no longer true
  }

  lcd.setCursor(0, 1);  // set the cursor to column 0, line 1
  lcd.print(v2 * 20.);  // Print calculated value multiplied by 20 to the LCD on line 1.

  lcd.setCursor(5, 1);  // set the cursor to column 5, line 1
  lcd.print("%");       // Print % character after calculated value to LCD on line 1

  lcd.setCursor(7, 1);    // set the cursor to column 7, line 1
  lcd.print("to empty");  // Print message after % character to LCD on line 1
}

This is the breadboard layout:
image

This is the schematic:

And here is the component list:
Name - Quantity - Description
U1 - 1 - Arduino Uno R3
RpotSensor Resistance, RpotContrast Adjust - 2 - 10 kΩ Potentiometer
DWarning LED - 1 - Red LED
DStatus LED - 1 - Green LED
RWarning, RStatus, RBacklight Resistor - 3 - 220 Ω Resistor
U2 - 1 - LMB162ABC LCD 16 x 2

Talk to your propane delivery people. A propane tank is NEVER 100% full. We are talking about the liquid level never being 100%.
You need to take a reading immediately after the tank is filled and assume that to be 100%.

Then you need to take a reading while emptying the tank down to where only gas is remaining and that will be your zero level.

I understand what you are saying but I am referring to the actual limit of the sensor. I am trying to show 100% when the sensor is at its top limit and 0% when the sensor is at its bottom limit.

Eventually I will build this up to log history of high levels when refilled and history of low levels before it was refilled.

Subtract the empty reading, now empty reads zero. Scale it by multiplication with (100/(full-empty)). Now full reads 100.

You can do a "standard" straight-line calibration.

Typically, you find offset at (or near) zero and add or subtract from the actual reading to correctly read at zero. (If there is no correction needed, the offset is zero.)

Then you use a multiplication factor to adjust the slope. This is normally calculated at (or near) 100%. (If there is no correction needed the factor is 1.)

By itself, that's not a problem. It's a ratio or percentage so the value isn't critical as long as it goes close to 0 and close to 100%, and as long as the center is approximately 50% of the total resistance.

You will have to remove the sensor to do that! The physical device will have stops before the resistor reaches it's ends because the ends make connections that distort the resistive value.

Another way is to use map(), but it will not return a float, but instead a long type. This will not really matter, though.

1 Like

... and ... unless it is constrained, the scaled value might become <0 or >100

So then you would constrain it!

int out = map(whateverInput, inMin, inMax, 0, 100);

if (out < 0) {
  out = 0;
} else if (out > 100) {
  out = 100;
}

// 'out' is the output
1 Like

a bit concise version of your code.

#include <LiquidCrystal.h>  // include the library code:

#define myVoltPin A2         // Initialize variable to Analog pin A2
#define delayT 250           // Initialize variable for serial print *Optional
#define ledPin13 13          // Initialize digital pin 13
#define ledPin8 8            // Initialize digital pin 8
#define rs 12
#define en 11
#define d4 5
#define d5 4
#define d6 3
#define d7 2
#define SERIAL_SPEED 9600
#define WELCOME_MESSAGE "TANK MONITOR"

#define global_init()\
  pinMode(ledPin13, OUTPUT);\
  pinMode(ledPin8, OUTPUT);\
  Serial.begin(SERIAL_SPEED);\
  lcd.begin(16, 2);\
  lcd.print(WELCOME_MESSAGE)
 
#define lcd_manipulations()\
 lcd.setCursor(0, 1);\
 lcd.print(v2 * 20.);\
 lcd.setCursor(5, 1);\
 lcd.print("%");\
 lcd.setCursor(7, 1);\
 lcd.print("to empty")

LiquidCrystal lcd(rs, en, d4, d5, d6, d7); 

inline void start(void){
  float v2 = 5.15 / 1023. * analogRead(myVoltPin);

if(v2 <= 0.50) {
 digitalWrite(ledPin13, HIGH);
 delay(100);
 digitalWrite(ledPin13, LOW);
 delay(500);

  }
digitalWrite(ledPin8,v2>=51?HIGH:LOW);
lcd_manipulations();
  }
  
void setup() {
  global_init();
}

void loop() {
  start();
}
1 Like

That is a much cleaner version. I didn't even know you could write it out like that...

That would work in a different situation, but the readings are under not over, .78 and 99.7.

Thank you all for providing your time in showing me many solutions so far. Much appreciated.

@surepic Whichever way or combination I end up reconciling the readings I will definitely follow your lead in coding style. Looks cleaner and easier to read.

While looking over your example I'm guessing I could use "(out < .9)" and "(out > 99)".

The float is not that important, but I think something in this direction will work out.

I just “translated” your code to a bit concise version. Thats not the way i am writing code tho.
Regarding float that u worry about and division compiler sees that its a constant expression and makes it constant so even if u get rid of division and write a constant there there wont be any gains in code size or speed. If u can switch to ints then yes u will get some gains.
I would suggest making code smaller and fit into smaller ic i bet its possible to reduce whole code by 30-50%.
Havent done arduino programming for over 4 years :-)))

Arduino uno is overkill for that project.

I see your point if this was all that I was going to do with it, but I think "Arduino uno is overkill for that project." is not really correct in this case because this is not the end of the project, just an attempt of refinement for this section.

I am eventually incorporating a data logging shield and either an RF of WIFI option to transmit the data but I didn't think that was relevant to the issue at this point.

So, wouldn't I be limiting myself to walk away from the Uno board?

This will be in a new installation, so the sensor is not even installed on the tank yet.

If you think that way then u will never actually finish the project cos there will always be something to add and scale the project.

Better to define what that device will be doing and stick with it. Trust me i have gone thru scaling projects and at the end it was enormous waste of time cos the device was doing what it suppose to and by wasting time on new features didnt bring any new users or increase the sales.

So steps are what it should do then prototyping to make sure it runs perfectly then field testing then starting to cut on unnecessary parts and shrinking code to fit cheapest ic thats gonna do the job.

Want new features then think of version 2,3,4 etc.

Instead of converting from an integer analog reading to a floating-point voltage, it may be more helpful to convert from the integer analog reading to an integer percentage:

int percent = (readVal * 100ul) / 1023;
(Note: the '100' is made 'unsigned long' because multiplying any integer over 327 by 100 will overflow a signed 16-bit integer.)

Warning: 0.50 is not 10% of 5.15