+233 20 936 2757 Same-day dispatch in Accra for orders before 14:00 · Free delivery over GH₵1000
Nauvitel Electronics
Tutorial

Build a water tank level monitor with ESP32 and HC-SR04

Measure how full a tank is with a waterproof ultrasonic sensor, show it on an OLED and get ready to send it online.

Overhead polytanks are everywhere in Ghana, and knowing how much water is left saves a lot of climbing. In this project an ESP32 reads a waterproof HC-SR04 ultrasonic sensor mounted in the tank lid, converts the distance into a percentage and shows it on a small OLED screen.

How it works

The sensor sends a short ultrasonic pulse down towards the water and times the echo. Sound travels at about 343 m/s (0.0343 cm per microsecond), so the distance is echo time × 0.0343 ÷ 2. Subtract that from the tank height and you have the water level.

Wiring

  • HC-SR04 VCC → 5V (the ESP32 DevKit's VIN/5V pin when powered over USB) and GND → GND.
  • Trig → GPIO 5.
  • Echo → GPIO 18 through a voltage divider (1 kΩ from Echo to GPIO 18, 2 kΩ from GPIO 18 to GND). When powered from 5 V the echo pulse is 5 V, and ESP32 pins are 3.3 V only.
  • OLED SDA → GPIO 21, SCL → GPIO 22, VCC → 3.3V, GND → GND.

Mount the probe facing straight down, at least 25 cm above the highest water level — the HC-SR04 cannot measure closer than that.

The code

Install Adafruit SSD1306 and Adafruit GFX from the Arduino Library Manager, set TANK_HEIGHT_CM and SENSOR_GAP_CM for your tank, and upload:

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

const int TRIG = 5, ECHO = 18;
const float TANK_HEIGHT_CM = 120;  // inside height of the tank
const float SENSOR_GAP_CM  = 30;   // distance from sensor to the "full" line
Adafruit_SSD1306 oled(128, 64, &Wire, -1);

float readDistanceCm() {
  digitalWrite(TRIG, LOW);  delayMicroseconds(5);
  digitalWrite(TRIG, HIGH); delayMicroseconds(20);
  digitalWrite(TRIG, LOW);
  unsigned long us = pulseIn(ECHO, HIGH, 30000);   // 30 ms timeout
  return us ? us * 0.0343 / 2.0 : -1;
}

void setup() {
  Serial.begin(115200);
  pinMode(TRIG, OUTPUT); pinMode(ECHO, INPUT);
  oled.begin(SSD1306_SWITCHCAPVCC, 0x3C);
}

void loop() {
  float d = readDistanceCm();
  if (d > 0) {
    float water = TANK_HEIGHT_CM + SENSOR_GAP_CM - d;
    int pct = constrain((int)(water / TANK_HEIGHT_CM * 100), 0, 100);
    Serial.printf("distance %.1f cm, level %d%%\n", d, pct);
    oled.clearDisplay(); oled.setTextColor(SSD1306_WHITE);
    oled.setTextSize(1); oled.setCursor(0, 0); oled.print("Tank level");
    oled.setTextSize(3); oled.setCursor(0, 24); oled.printf("%d%%", pct);
    oled.display();
  }
  delay(2000);
}

Tips for a reliable reading

  • Take 5 readings and use the median — ripples and condensation cause the odd wild value.
  • Keep the electronics outside the tank in a sealed box; only the probe goes in the lid.
  • For a remote tank, add a SIM800L module (see our SMS alerts tutorial) or use the ESP32's Wi-Fi to send the level to a dashboard over MQTT.
Keep reading

More tutorials

All articles

New parts, restocks and project ideas

Join our list — one useful email a month, no spam.