Water Level Sensor · Astro Tech Blog

Water Level Sensor

The water level sensor (also called a rain sensor or water depth sensor) is a set of exposed parallel traces on a PCB that act as a variable resistor. Water bridging the traces lowers the resistance proportionally to the depth of immersion. An LM393 comparator on the module provides both analog output (AO, varying voltage) and digital output (DO, HIGH/LOW with a potentiometer threshold). The sensor is used for water tank level monitoring, rain detection, and flood alarms.

Water Level Sensor

For this interfacing you need the following components:

  • Arduino board (Uno, Nano, Mega, etc.)
  • Water level sensor module (with LM393 comparator, e.g., HW-038)
  • Breadboard and jumper wires
  • USB cable to connect Arduino to your computer
  • (Optional) Small container of water for testing

Schematic

Water Level Module     Arduino
-----------------      -------
VCC           -->      5V
GND           -->      GND
AO (Analog)   -->      A0
DO (Digital)  -->      Digital Pin 2

The module has two separate sections: the sensing probe (exposed traces) and the LM393 comparator board.

Pin Map

Module PinNameArduino Connection
VCCPower5V
GNDGroundGND
AOAnalog output (0–5V)A0
DODigital output (HIGH/LOW)Pin 2
  • AO: higher voltage = more water (lower resistance).
  • DO: LOW when water level exceeds the potentiometer threshold.
  • Onboard potentiometer sets the digital threshold.

Install necessary Library

No library is required. The sensor is read with analogRead() for the analog output and digitalRead() for the digital threshold.

Code with complete explanation

This sketch reads the water level, maps it to a percentage, and controls LEDs for dry/moist/wet status.

#define AO_PIN  A0
#define DO_PIN  2
#define LED_DRY    12  // Green — dry
#define LED_MOIST  11  // Yellow — moist
#define LED_WET    10  // Red — wet

void setup()
{
  Serial.begin(9600);
  pinMode(DO_PIN, INPUT);
  pinMode(LED_DRY, OUTPUT);
  pinMode(LED_MOIST, OUTPUT);
  pinMode(LED_WET, OUTPUT);
  Serial.println("Water Level Sensor Test");
}

void loop()
{
  int adc = analogRead(AO_PIN);    // 0–1023
  int pct = map(adc, 0, 750, 0, 100);
  pct = constrain(pct, 0, 100);

  int digitalVal = digitalRead(DO_PIN); // LOW = wet

  Serial.print("ADC: ");
  Serial.print(adc);
  Serial.print("  Level: ");
  Serial.print(pct);
  Serial.print("%  Digital: ");
  Serial.println(digitalVal == LOW ? "WET" : "DRY");

  // LED indication with hysteresis
  if (pct >= 70)
  {
    digitalWrite(LED_WET, HIGH);
    digitalWrite(LED_MOIST, LOW);
    digitalWrite(LED_DRY, LOW);
  }
  else if (pct >= 30)
  {
    digitalWrite(LED_WET, LOW);
    digitalWrite(LED_MOIST, HIGH);
    digitalWrite(LED_DRY, LOW);
  }
  else
  {
    digitalWrite(LED_WET, LOW);
    digitalWrite(LED_MOIST, LOW);
    digitalWrite(LED_DRY, HIGH);
  }

  delay(200);
}

Rain detector with alarm

#define BUZZER_PIN 3

void setup()
{
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(DO_PIN, INPUT);
}

void loop()
{
  if (digitalRead(DO_PIN) == LOW)
  {
    // Rain detected — sound alarm
    for (int i = 0; i < 5; i++)
    {
      tone(BUZZER_PIN, 2000, 100);
      delay(150);
    }

    delay(5000);  // Wait before next check
  }
  else
  {
    noTone(BUZZER_PIN);
  }

  delay(100);
}

Water tank with multiple sensors (high/low float switches)

For a discrete-level water tank monitor, use float switches (non-corrosive) instead of exposed traces:

#define LOW_FLOAT  4   // LOW when water is at minimum level
#define HIGH_FLOAT 5   // LOW when water is at maximum level
#define PUMP_RELAY 6

void setup()
{
  pinMode(LOW_FLOAT, INPUT_PULLUP);
  pinMode(HIGH_FLOAT, INPUT_PULLUP);
  pinMode(PUMP_RELAY, OUTPUT);
}

void loop()
{
  bool low  = (digitalRead(LOW_FLOAT) == LOW);   // Water at low mark
  bool high = (digitalRead(HIGH_FLOAT) == LOW);  // Water at high mark

  if (low && !high)
  {
    digitalWrite(PUMP_RELAY, HIGH);  // Pump on — tank empty
  }
  else if (high)
  {
    digitalWrite(PUMP_RELAY, LOW);   // Pump off — tank full
  }

  // If between low and high, maintain current state
}

Code breakdown

  • analogRead(AO_PIN) — returns the analog voltage from the sensor (0–1023). The ADC reading increases as water covers more traces because the water’s conductivity reduces the probe’s resistance.
  • map(value, 0, 750, 0, 100) — maps the raw ADC to a percentage. The maximum ADC with the probe fully submerged is typically 600–800 (not 1023) because the sensor’s output does not reach 5V. Calibrate this range for your specific module.
  • digitalRead(DO_PIN) — returns LOW when the water level exceeds the threshold set by the onboard potentiometer.
  • The LED indication uses three discrete levels (dry < 30%, moist 30–70%, wet ≥ 70%) with hysteresis implicitly avoided by non-overlapping thresholds.
  • The rain detector triggers a buzzer alarm when the digital pin goes LOW (wet). The 5-second delay prevents repeated triggering in light rain.
  • The float switch approach uses INPUT_PULLUP — the switch connects the pin to GND when activated (LOW = water present).

Calibration

void calibrate()
{
  // Dry sensor — record baseline
  int dryVal = analogRead(AO_PIN);

  // Fully submerged — record maximum
  int wetVal = analogRead(AO_PIN);

  // Map raw values to 0–100 %
  pct = map(adc, dryVal, wetVal, 0, 100);
  pct = constrain(pct, 0, 100);
}

Steps to perform this interfacing

  1. Connect the water level sensor module to the Arduino as shown.
  2. No library installation needed.
  3. Copy the code into the Arduino IDE.
  4. Select the correct board and port (Tools > Board and Tools > Port).
  5. Upload the sketch to the Arduino.
  6. Open the Serial Monitor (Tools > Serial Monitor, set baud rate to 9600).
  7. With the sensor dry, observe the ADC reading (typically 0–50).
  8. Dip the sensor into a container of water (do not submerge the LM393 board).
  9. Observe the ADC value increase — the green LED turns off, yellow then red turn on.
  10. Adjust the potentiometer on the module to change the digital threshold.

Caution

  • ⚠️ Corrosion and electrochemical degradation: The water level sensor’s exposed copper traces are subject to electrolysis when DC current passes through water. Within hours of continuous use, the copper traces begin to corrode, turning green and eventually dissolving. This is the single biggest limitation of this sensor type. To mitigate: power the sensor only during measurement (pulse the VCC pin via a MOSFET or digital pin, read immediately, then power off). Even with this technique, lifespan is limited to weeks to months in continuous use.
  • Pulsed operation to prevent corrosion:
#define SENSOR_PWR 8   // MOSFET gate or digital pin to power sensor VCC

void readSensor()
{
  digitalWrite(SENSOR_PWR, HIGH);   // Power on sensor
  delay(50);                         // Wait for stabilisation
  int val = analogRead(AO_PIN);     // Read
  digitalWrite(SENSOR_PWR, LOW);    // Power off immediately
}
  • Not for potable water: The exposed copper traces and solder joints contain lead and other metals that leach into water. Do not use this sensor for drinking water, hydroponic nutrient solutions, or fish tanks. The corrosion products are toxic. For potable applications, use a pressure sensor, ultrasonic distance sensor (HC-SR04), or a stainless-steel capacitive sensor.
  • Water conductivity varies: Tap water has higher conductivity (lower resistance) than distilled water. Saltwater has much higher conductivity. The analog reading for “full” varies significantly with water type. If the sensor is used in different water sources, auto-calibrate on each power-up or use a reference measurement.
  • Comparator module must stay dry: The LM393 comparator board is not waterproof. If the module is immersed past the sensing trace area, water will short-circuit the electronics and destroy the module. Mount the comparator board above the maximum water level and extend the sensor probe with wires if needed.
  • Maximum immersion depth: Most water level sensor modules are designed for a maximum immersion depth of ≈ 4–5 cm (the length of the exposed traces). For deeper tanks, use multiple sensors at different heights or a different sensing technology (ultrasonic, pressure, capacitive probe).
  • Digital threshold potentiometer: The small potentiometer on the module is fragile. Turn it gently with a plastic screwdriver. Over-tightening or using a metal screwdriver can damage the wiper. Use a dab of nail polish or hot glue to lock the threshold once set.
  • Float switch alternative: For reliable industrial-grade water level sensing, use a reed switch float sensor (magnetic float + reed switch). These have no corrosion issues, last millions of cycles, and are safe for drinking water. They provide only discrete levels (one switch = one threshold), but multiple floats can be combined for multi-level detection.