Arduino Development
Senior 6 · ICT
Arduino Development
Lesson 5 of 5
25 min
Many schools store water in a tank on a tower or roof, filled by a pump from a ground tank or a borehole. When nobody remembers to switch the pump on, the taps run dry during lunch; when nobody remembers to switch it off, the tank overflows and water and electricity are wasted. This lesson solves that problem with everything from this unit: an ultrasonic sensor to measure the water level, a relay to run the pump, LEDs and a buzzer to show the state, and a line of data every two seconds that an internet module could send to a phone. You will build it in a simulator, test it systematically, and improve it.
Definition
Hysteresis (in control): using two different thresholds for switching on and switching off, so that a system does not switch rapidly back and forth when the measurement is near one threshold. Example: pump on below 25%, off above 90%.
Fail-safe: designed so that when a part fails, the system goes into the least harmful state. Example: if the level sensor stops answering, the pump is switched off.
Test case: a chosen input with the output it should produce, used to check a system.
| Question | Answer for the water tank |
|---|---|
| What must the system achieve? | Keep water in the tank without overflowing, and warn people when it is low or when something is wrong. |
| Inputs | Distance from the sensor at the top of the tank to the water surface (HC-SR04). |
| Outputs | Pump relay; green, amber and red LEDs; buzzer; a data line for the IoT layer. |
| Rules | Pump on below 25%, off above 90%; alarm below 10%; pump off and alarm if the sensor fails. |
| Limits | Simulated first; a real version must use a low-voltage pump relay and a waterproof sensor housing. |
The sensor is above the water. When the tank is full the surface is below it; when empty, . The level as a percentage is
Worked example 1: three readings
constrain to keep between 0 and 100.| Part | Arduino pin | Notes |
|---|---|---|
| HC-SR04 Trig, Echo | 9, 10 | VCC to 5V, GND to GND (as in lesson 3) |
| Relay module IN (pump) | 6 | In the simulator, use an LED with a 220 Ω resistor instead |
| Green, amber, red LEDs | 2, 3, 4 | Each with a 220 Ω resistor to GND |
| Passive buzzer | 7 | Other leg to GND |
Compiled for the Uno with arduino-cli without errors (5366 bytes, 16% of the program memory).
// School water tank: measure the level, run the pump with hysteresis, show status, report data
const int TRIG_PIN = 9;
const int ECHO_PIN = 10;
const int PUMP_PIN = 6; // relay module IN (an LED stands in for it in the simulator)
const int GREEN_PIN = 2; // level 60 % or more
const int AMBER_PIN = 3; // 25 % to 59 %
const int RED_PIN = 4; // below 25 %, or sensor fault
const int BUZZER_PIN = 7; // passive buzzer: alarm
const float FULL_CM = 10.0; // sensor-to-water distance when the tank is full
const float EMPTY_CM = 130.0; // sensor-to-water distance when the tank is empty
const int PUMP_ON_BELOW = 25; // start pumping below 25 %
const int PUMP_OFF_ABOVE = 90; // stop pumping above 90 %
bool pumpOn = false;
unsigned long lastReport = 0;
float readDistanceCm() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
unsigned long us = pulseIn(ECHO_PIN, HIGH, 30000UL);
if (us == 0) return -1; // no echo: sensor fault
return us * 0.0343 / 2;
}
int levelPercent(float cm) {
float pct = (EMPTY_CM - cm) / (EMPTY_CM - FULL_CM) * 100.0;
return constrain((int)(pct + 0.5), 0, 100); // round, keep within 0 to 100
}
void showLevel(int level) {
digitalWrite(GREEN_PIN, level >= 60 ? HIGH : LOW);
digitalWrite(AMBER_PIN, (level >= 25 && level < 60) ? HIGH : LOW);
digitalWrite(RED_PIN, level < 25 ? HIGH : LOW);
}
void setup() {
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
int outputs[] = {PUMP_PIN, GREEN_PIN, AMBER_PIN, RED_PIN, BUZZER_PIN};
for (int pin : outputs) pinMode(pin, OUTPUT);
Serial.begin(9600);
}
void loop() {
float cm = readDistanceCm();
if (cm < 0) { // fail safe: no reading means pump off and alarm
pumpOn = false;
digitalWrite(PUMP_PIN, LOW);
showLevel(0);
tone(BUZZER_PIN, 400, 200);
Serial.println("status=SENSOR_FAULT,pump=0");
delay(1000);
return;
}
int level = levelPercent(cm);
// hysteresis: switch on low, switch off high, otherwise keep the current state
if (!pumpOn && level < PUMP_ON_BELOW) pumpOn = true;
else if (pumpOn && level > PUMP_OFF_ABOVE) pumpOn = false;
digitalWrite(PUMP_PIN, pumpOn ? HIGH : LOW);
showLevel(level);
if (level < 10) tone(BUZZER_PIN, 1000, 100); // nearly empty
if (millis() - lastReport >= 2000) { // a data line every 2 s for the "cloud"
Serial.print("level=");
Serial.print(level);
Serial.print(",pump=");
Serial.println(pumpOn ? 1 : 0);
lastReport = millis();
}
delay(100);
}
Testing a pump by waiting for a real tank to fill would take hours. Instead, the control rules can be checked quickly: this Python program uses exactly the same formula and the same hysteresis rule, and runs them on a sequence of distances that imitates the tank emptying, filling and emptying again.
# Same logic as the sketch, run on a sequence of test distances
FULL, EMPTY, ON_BELOW, OFF_ABOVE = 10.0, 130.0, 25, 90
def level(cm):
pct = (EMPTY - cm) / (EMPTY - FULL) * 100
return max(0, min(100, int(pct + 0.5)))
pump = False
for cm in [130, 110, 103, 70, 30, 20, 21, 40, 90, 100, 103, 104]:
lv = level(cm)
if not pump and lv < ON_BELOW: pump = True
elif pump and lv > OFF_ABOVE: pump = False
print(f"{cm:5} cm level {lv:3} % pump {'ON' if pump else 'off'}")
130 cm level 0 % pump ON
110 cm level 17 % pump ON
103 cm level 23 % pump ON
70 cm level 50 % pump ON
30 cm level 83 % pump ON
20 cm level 92 % pump off
21 cm level 91 % pump off
40 cm level 75 % pump off
90 cm level 33 % pump off
100 cm level 25 % pump off
103 cm level 23 % pump ON
104 cm level 22 % pump ONRead the table as a test: at 103 cm (23%) the pump switches on; it stays on through 50% and 83%, because it only switches off above 90%; at 20 cm (92%) it switches off; and it stays off as the level falls through 75%, 33% and exactly 25%, switching on again only at 23%. In the simulator, repeat the same test by dragging the HC-SR04's distance slider through the same values and recording the pump LED.
Worked example 2: why two thresholds?
Suppose the pump used a single threshold: on below 50%, off at or above 50%. The water surface ripples by about ±1 cm, so near 50% (70 cm) the readings jump between 49% and 51%.
The fuzzy controller of unit 3 is another way to avoid abrupt switching: it changes its output smoothly instead of using thresholds.
Stuart Patterson: WOKWI, the online microcontroller and electronics simulator. Watch how a project is created, parts are added and the Serial Monitor is used; you can build the water-tank system in the same way.
Warning
Simulated is not proven. A simulator has perfect wires and sensors. A real tank has condensation on the sensor, echoes from the tank walls, power cuts and a pump that draws a large current when it starts. Before any real installation, an adult with electrical training must check the relay wiring, the pump must be low-voltage or switched by a properly rated and enclosed contactor installed by an electrician, and the system must be tested for days with someone watching it.
Tip
Ask "what if it fails?" for every part. For each sensor, wire and output, write what the system does if it stops working. If the answer is "the tank overflows" or "the pump runs dry", add a fail-safe, as the sketch does when the echo stops.
level=…,pump=… lines are the data an internet module would send to a dashboard.Check: your system passes every row of your test table, including at least one fault case, and your report explains why each threshold was chosen.
Practice
readDistanceCm() returns −1, and why that is the safe choice.Further reading
Sources and credits
This lesson adapts CC BY-SA material and is shared under CC BY-SA 4.0.