The Tank Monitor Calibration Lab

SS 2 Arduino Uno Simulation first Piece-Wise Calibration Curves
Simulation lab
Board: ♾️ Arduino Uno
Open in Wokwi ↗
Simulator preview
ARDUINO UNO

Press the pushbutton to test the interactive circuit in real time.

>_ Serial monitor
Ready — press the button.
📦 Components ▴
  • Arduino Uno microcontroller board
  • LED indicator (green)
  • 220 ohm current-limiting resistor
  • Tactile pushbutton switch
  • Jumper wires (male-male)

🔧 Build Your Piece-Wise Calibration Lab

1 Measure water surface distance using an HC-SR04 ultrasonic echo transducer in centimeters.
2 Model non-linear tank geometry: conical funnel base (0–30cm) and cylindrical main body (30–100cm).
3 Construct a Piece-Wise Linear Interpolation (PWLI) lookup table of (depth, volume) coordinates in C++.
4 Execute interpolation to output true volumetric capacity in Liters with sub-percent accuracy!
🧩 Block plan / Starter code ▴
struct CalPoint { float depth; float volume; };

const CalPoint table[] = {
  {0.0, 0.0},
  {30.0, 120.0},    // Conical base transition
  {70.0, 600.0},    // Mid cylinder
  {100.0, 1200.0}   // Full capacity
};
const int NUM_POINTS = 4;

float interpolateVolume(float measuredDepth) {
  if (measuredDepth <= table[0].depth) return table[0].volume;
  if (measuredDepth >= table[NUM_POINTS-1].depth) return table[NUM_POINTS-1].volume;

  for (int i = 0; i < NUM_POINTS - 1; i++) {
    if (measuredDepth >= table[i].depth && measuredDepth <= table[i+1].depth) {
      float slope = (table[i+1].volume - table[i].volume) / (table[i+1].depth - table[i].depth);
      return table[i].volume + slope * (measuredDepth - table[i].depth);
    }
  }
  return 0.0;
}

void setup() {
  Serial.begin(9600);
  float testDepths[] = {15.0, 30.0, 50.0, 85.0};
  for (float d : testDepths) {
    Serial.print("Depth: "); Serial.print(d);
    Serial.print(" cm -> True Volume: ");
    Serial.print(interpolateVolume(d)); Serial.println(" Liters");
  }
}
void loop() {}

Piece-wise linear interpolation models irregular tank geometry accurately without heavy polynomial calculations.

⚗️ Test your model

Test Condition / ActionExpected Result
Depth = 15cm (In Conical Base)Interpolates on segment 1: outputs 60.0 Liters (slope = 4.0 L/cm)
Depth = 30cm (Transition Point)Exact match with calibration anchor: outputs 120.0 Liters
Depth = 50cm (In Cylinder)Interpolates on segment 2: outputs 360.0 Liters (slope = 12.0 L/cm)
⚑ Try a challenge

Add acoustic temperature compensation: calculate actual speed of sound using ambient temperature (v = 331.3 + 0.606 * T) before distance calculation!

❓ Quick check
Why does calculating the water volume of a real cylindrical plastic tank (like a GeePee tank) using a simple formula Volume = Height * Width produce large errors?

📖 Our Story & Concepts

Real community water tanks and petroleum vessels are rarely simple rectangular cubes. Most industrial tanks feature a conical tapered bottom for sediment drainage and rounded spherical caps. Consequently, depth and volume do not share a linear relationship.

An ultrasonic sensor measures distance, not volume. While computing 3D calculus equations on embedded microcontrollers is computationally expensive, Piece-Wise Linear Interpolation (PWLI) offers an elegant solution: the curve is divided into linear segments between empirical calibration points, delivering rapid and accurate volumetric readouts.

</> Full Annotated Code Walkthrough

// Complete Ultrasonic Non-Linear Volume Station
const int TRIG_PIN = 9;
const int ECHO_PIN = 10;
const float TANK_HEIGHT = 120.0; // cm

struct Point { float d; float v; };
const Point lut[] = {
  {0.0, 0.0}, {30.0, 120.0}, {70.0, 600.0}, {100.0, 1200.0}
};

float getDepth() {
  digitalWrite(TRIG_PIN, LOW); delayMicroseconds(2);
  digitalWrite(TRIG_PIN, HIGH); delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);
  long dur = pulseIn(ECHO_PIN, HIGH);
  float distance = (dur * 0.0343) / 2.0;
  return constrain(TANK_HEIGHT - distance, 0.0, 100.0);
}

float calcVolume(float depth) {
  for (int i = 0; i < 3; i++) {
    if (depth >= lut[i].d && depth <= lut[i+1].d) {
      float m = (lut[i+1].v - lut[i].v) / (lut[i+1].d - lut[i].d);
      return lut[i].v + m * (depth - lut[i].d);
    }
  }
  return 0;
}

void setup() {
  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);
  Serial.begin(115200);
}

void loop() {
  float waterDepth = getDepth();
  float volumeLiters = calcVolume(waterDepth);
  Serial.print("Depth(cm):"); Serial.print(waterDepth);
  Serial.print(",Volume(L):"); Serial.println(volumeLiters);
  delay(1000);
}

Piece-wise linear interpolation models irregular tank geometry accurately without heavy polynomial calculations.

▷ Expected Results & Troubleshooting

Test Condition / ActionExpected Result
Depth = 15cm (In Conical Base)Interpolates on segment 1: outputs 60.0 Liters (slope = 4.0 L/cm)
Depth = 30cm (Transition Point)Exact match with calibration anchor: outputs 120.0 Liters
Depth = 50cm (In Cylinder)Interpolates on segment 2: outputs 360.0 Liters (slope = 12.0 L/cm)

⚑ Extension Challenges & Blueprints

⚑ Extension Challenges
  • Level 1: Calibrate a horizontal cylindrical fuel tank using a 10-point lookup table.
  • Level 2: Display water percentage, remaining liters, and days of water reserve on an LCD.
  • Level 3: Implement an acoustic echo outlier rejection filter to eliminate splashing artifacts.

❓ Check Your Understanding

Question 1: Why does calculating the water volume of a real cylindrical plastic tank (like a GeePee tank) using a simple formula Volume = Height * Width produce large errors?

Question 2: What is 'Piece-Wise Linear Interpolation' in embedded sensor engineering?

Question 3: Given two calibration points: at depth 20cm volume is 100L, and at depth 40cm volume is 300L, what is the interpolated volume at depth 30cm?

Question 4: In the ultrasonic formula Distance = (Duration * Speed_of_Sound) / 2, why do we divide by 2?

Feedback appears after you check your answer.