JSN-SR04 Water Level calibration issue

Hi, I have been using HC-SR04 successfully in a water level indicator for quite some time. But that sensor isn't waterproof and moisture seeped its way into its electronics prompting me to replace it with a JSN-SR04 and since then there has been an error in measuring the volume when the tank is full. The tank holds 476L water when it’s full. That would be 100% water if it's calculations were with done with HC-SR04. But with the same setup and arrangement of apparatus, the JSN-SR04 doesn't go beyond 76% or somewhere around 340L water. The algorithm applied is as follows:

  digitalWrite(trigger, LOW);
  delayMicroseconds(1);
  digitalWrite(trigger, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigger, LOW);
  delayMicroseconds(1);
  time = pulseIn(echo, HIGH);

  distance = (time * 340 / 25000) // Measure distance with the sensor
  empty = 86 - distance; // Calculate the unfilled distance. 86cm is max height of water column.
  percent = (empty / 86) * 100; // Convert height measured at given instance into percent.
  volume = 3.14 * 41 * 41 * empty; // Calculate volume based upon the distance measured.

The apparatus appears something like this:

What's wrong with the system? Is it the issue of minimum distance (20cm) the JSN-SR04 can measure? I read a detailed discussion concerning this issue with major inputs from the author of NewPing library. Is there any simple way to eliminate the error of measurement that I'm facing?

Thanks.

Does the distance returned by the new sensor agree with that returned by the old sensor when the water is at the same height ?

UKHeliBob:
Does the distance returned by the new sensor agree with that returned by the old sensor when the water is at the same height ?

Thank you for replying. Unfortunately, I do not have another HC-SR04 sensor. However, I can measure the distance returned by JSN-SR04 and update as soon as possible since the tank is overhead. Can you please share your hypothesis as to what might be causing the error in distance? 75-76% is the maximum percent I get no matter how close the sensor is to an object (in this case, the water column).

What value are you using for empty ?
Did you measure it with the new sensor or is it the old value ?

Hi, I'm relying upon empty = 86 - distance;, so once the sensor measures 86, the equation returns 0, so percent = (empty / 86) * 100; returns 0. The code then has an 'if' loop that ignores anything after that, similarly anything goes beyond 100% is ignored as value of 'empty' turns negative. Perhaps its crude, but please ignore that, coding isn't my domain.

Shouldn’t distance be over 100 when the tank is empty ?

UKHeliBob:
Shouldn’t distance be over 100 when the tank is empty ?

Yes, you’re right. Please take a look at the entire code, I would be thankful if you could offer some suggestions.

#include "DHT.h"
#include "U8glib.h"
#define OLED_RESET 4
#define trigger 7
#define echo 8

U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_NONE | U8G_I2C_OPT_DEV_0);  // I2C / TWI

byte AlarmPlayed = false;
byte Beepat20 = false;
byte Beepat50 = false;
byte Beepat70 = false;

float time = 0, distance = 0;
float empty;
float volume;
float percent;
float bucket;
float NegPercent;
float adj;
int buzzPin = 9;


void draw(void) {
  //  // graphic commands to redraw the complete screen should be placed here


  // TANK Container
  u8g.drawRFrame(2, 2, 40, 62, 3); // Header Frame


  // TANK Gradation
  u8g.drawLine(42, 5, 43, 5);
  u8g.drawLine(42, 15, 43, 15);
  u8g.drawLine(42, 25, 43, 25);
  u8g.drawBox(42, 32, 2, 4); // Half-point
  u8g.drawLine(42, 45, 43, 45);
  u8g.drawLine(42, 55, 43, 55);

  // WATER LEVEL Inserts
  if (percent >= 90)
  {
    u8g.drawBox(6, 6, 32, 54); // LEVEL 10
  }
  if (percent >= 80)
  {
    u8g.drawBox(6, 9, 32, 3); // LEVEL 10
  }
  if (percent >= 70)
  {
    u8g.drawBox(6, 15, 32, 3); // LEVEL 10
  }
  if (percent >= 70)
  {
    u8g.drawBox(6, 21, 32, 3); // LEVEL 10
  }
  if (percent >= 50)
  {
    u8g.drawBox(6, 27, 32, 3); // LEVEL 10
  }
  if (percent >= 40)
  {
    u8g.drawBox(6, 33, 32, 3); // LEVEL 10
  }
  if (percent >= 30)
  {
    u8g.drawBox(6, 39, 32, 3); // LEVEL 10
  }
  if (percent >= 20)
  {
    u8g.drawBox(6, 45, 32, 3); // LEVEL 10
  }
  if (percent >= 10)
  {
    u8g.drawBox(6, 51, 32, 3); // LEVEL 10
  }
  if (percent >= 5)
  {
    u8g.drawBox(6, 57, 32, 3); // Base Level 5
  }


  //Water Level Percent
  if ((percent > 0) && (percent <= 100))
  {
    u8g.setFont(u8g_font_5x8);
    u8g.setPrintPos(55, 10);
    u8g.print("WATER LEVEL");
    u8g.setFont(u8g_font_profont22);
    u8g.setPrintPos(55, 28);
    u8g.print(percent, 0);
    u8g.setFont(u8g_font_7x14);
    u8g.setPrintPos(90, 28);
    u8g.print("%");

    // Water Level Volume
    u8g.setFont(u8g_font_5x8);
    u8g.setPrintPos(55, 40);
    u8g.print("VOLUME");
    u8g.setFont(u8g_font_profont22);
    u8g.setPrintPos(55, 60);
    u8g.print((volume / 1000), 0);
    u8g.setFont(u8g_font_7x14);
    u8g.setPrintPos(95, 60);
    u8g.print("L");

    // Buckets
    if (percent < 35)
    {
      u8g.setFont(u8g_font_7x14B);
      u8g.setPrintPos(16, 18);
      u8g.print(bucket, 0);

      u8g.setFont(u8g_font_5x8);
      u8g.setPrintPos(7, 30);
      u8g.print("BUCKET");
    }
  }


  if (percent > 100)
  {
    u8g.setFont(u8g_font_5x8);
    u8g.setPrintPos(55, 10);
    u8g.print("WATER LEVEL");
    u8g.setFont(u8g_font_profont22);
    u8g.setPrintPos(55, 28);
    u8g.print("100");
    u8g.setFont(u8g_font_7x14);
    u8g.setPrintPos(90, 28);
    u8g.print("%");

    // Water Level Volume
    u8g.setFont(u8g_font_5x8);
    u8g.setPrintPos(55, 40);
    u8g.print("VOLUME");
    u8g.setFont(u8g_font_profont22);
    u8g.setPrintPos(55, 60);
    u8g.print(volume / 1000, 0);
    u8g.setFont(u8g_font_7x14);
    u8g.setPrintPos(95, 60);
    u8g.print("L");

    // Buckets
    if (percent < 35)
    {
      u8g.setFont(u8g_font_7x14B);
      u8g.setPrintPos(16, 18);
      u8g.print(bucket, 0);

      u8g.setFont(u8g_font_5x8);
      u8g.setPrintPos(7, 30);
      u8g.print("BUCKET");
    }
  }

  if (percent < 0)
  {
    u8g.setFont(u8g_font_5x8);
    u8g.setPrintPos(55, 10);
    u8g.print("WATER LEVEL");
    u8g.setFont(u8g_font_profont22);
    u8g.setPrintPos(55, 28);
    u8g.print("0");
    u8g.setFont(u8g_font_7x14);
    u8g.setPrintPos(90, 28);
    u8g.print("");

    // Water Level Volume
    u8g.setFont(u8g_font_5x8);
    u8g.setPrintPos(55, 40);
    u8g.print("VOLUME");
    u8g.setFont(u8g_font_profont22);
    u8g.setPrintPos(55, 60);
    u8g.print("0");
    u8g.setFont(u8g_font_7x14);
    u8g.setPrintPos(95, 60);
    u8g.print("");


  }



}


void setup()
{
  Serial.begin(9600);
  pinMode(buzzPin, OUTPUT);
  pinMode(trigger, OUTPUT);
  pinMode(echo, INPUT);

}

void loop()
{
  digitalWrite(trigger, LOW);
  delayMicroseconds(1);
  digitalWrite(trigger, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigger, LOW);
  delayMicroseconds(1);
  time = pulseIn(echo, HIGH);
  distance = (time * 340 / 25000);


  bucket = (volume / 1000) / 10;
  empty = 86 - distance;
  percent = (empty / 86) * 100;
  volume = 3.14 * 41 * 41 * (86 - distance);
  Serial.println ("Distance");


  //ALARM SENSOR 1

  // Beet at 20% Water Level
  if (percent >= 20)
  {
    if (!Beepat20) {
      Beepat20 = true;
      tone(buzzPin, 3000, 50);
    }
  }
  if (percent < 15)
  {
    Beepat20 = false;
  }

  // Beet at 50% Water Level
  if (percent >= 50)
  {
    if (!Beepat50) {
      Beepat50 = true;
      tone(buzzPin, 3000, 50);
    }
  }
  if (percent < 45)
  {
    Beepat50 = false;
  }

  // Beet at 70% Water Level
  if (percent >= 70)
  {
    if (!Beepat70) {
      Beepat70 = true;
      tone(buzzPin, 3000, 50);
    }
  }
  if (percent < 65)
  {
    Beepat70 = false;
  }


  // Warn at 100% Water Level
  if (percent >= 98)
  {
    if (!AlarmPlayed) {
      AlarmPlayed = true;

      tone(buzzPin, 1800, 10000); // play 550 Hz tone in background for 'onDuration'

      //digitalWrite(buzzPin, HIGH);
      //if (millis() - last_trigger >= 5000) { // 5000 milliseconds
      //last_trigger = millis(); // update last_trigger
      //digitalWrite(buzzPin, LOW);
      //}
      //delay(10000);

      //delay(10);
    }
  }
  if (percent < 50)
  {
    AlarmPlayed = false;
  }


  { // picture loop
    u8g.firstPage();
    do {
      draw();
    } while ( u8g.nextPage() );
  }
  delay(100);

}
    u8g.drawBox(6, 6, 32, 54); // LEVEL 10
    u8g.drawBox(6, 9, 32, 3); // LEVEL 10
    u8g.drawBox(6, 15, 32, 3); // LEVEL 10
    u8g.drawBox(6, 21, 32, 3); // LEVEL 10
    u8g.drawBox(6, 27, 32, 3); // LEVEL 10
    u8g.drawBox(6, 33, 32, 3); // LEVEL 10
    u8g.drawBox(6, 39, 32, 3); // LEVEL 10
    u8g.drawBox(6, 45, 32, 3); // LEVEL 10
    u8g.drawBox(6, 51, 32, 3); // LEVEL 10

So, which number as the second argument REALLY means level 10?

  if (percent > 100)
  {
    // Buckets
    if (percent < 35)
    {

Now what are the odds that percent will be greater than 100 AND less than 35 at the same time?

  bucket = (volume / 1000) / 10;
  empty = 86 - distance;
  percent = (empty / 86) * 100;
  volume = 3.14 * 41 * 41 * (86 - distance);

Why are you calculating a value for bucket, which depends on volume, and then calculating a value for volume?

  Serial.println ("Distance");

That’s certainly useless data. Wouldn’t want to confuse the situation with facts.

PaulS:

    u8g.drawBox(6, 6, 32, 54); // LEVEL 10

u8g.drawBox(6, 9, 32, 3); // LEVEL 10
    u8g.drawBox(6, 15, 32, 3); // LEVEL 10
    u8g.drawBox(6, 21, 32, 3); // LEVEL 10
    u8g.drawBox(6, 27, 32, 3); // LEVEL 10
    u8g.drawBox(6, 33, 32, 3); // LEVEL 10
    u8g.drawBox(6, 39, 32, 3); // LEVEL 10
    u8g.drawBox(6, 45, 32, 3); // LEVEL 10
    u8g.drawBox(6, 51, 32, 3); // LEVEL 10



So, which number as the second argument REALLY means level 10?



if (percent > 100)
  {
    // Buckets
    if (percent < 35)
    {



Now what are the odds that percent will be greater than 100 AND less than 35 at the same time?



bucket = (volume / 1000) / 10;
  empty = 86 - distance;
  percent = (empty / 86) * 100;
  volume = 3.14 * 41 * 41 * (86 - distance);



Why are you calculating a value for bucket, which depends on volume, and then calculating a value for volume?



Serial.println (“Distance”);



That's certainly useless data. Wouldn't want to confuse the situation with facts.

Thanks PaulS for the inputs. It was a mistake since i wrote the code for water level at 10% and replicated further, hence the comment. As for the bucket, bucket = (volume / 1000) / 10;, I’m converting cubic centimeters to Liters and then assuming a bucket would hold 10L, converting it into numbers.

The snippets of code in question do not affect the core functioning of the system, they serve a cosmetic purpose. The problem at hand is that the system is not returning ‘100%’ value of water level even when the the water column is at its peak. So perhaps either the sensor is not returning a correct measurement or there is a misinterpretation of the sensor data in the algorithm, which is more likely the case considering my skills at programming. The following is the core code that does all the interpretation. I hope it may be improved.

  empty = 86 - distance;
  percent = (empty / 86) * 100;
  volume = 3.14 * 41 * 41 * (86 - distance);

Please help me with a better code if possible. Thanks.

The problem at hand is that the system is not returning '100%' value of water level even when the the water column is at its peak.

Print out the value you get for distance. Is that reasonable?
Print out the value for empty. Is that reasonable?
Print out the value for percent. Is that reasonable?

You just might need to be happy with 99.999%.