Pressure tank monitor on an Arduino Uno
A pressure transducer and a pump relay run a 40/60 or 30/50 psi cut-in and cut-out, count pump cycles and run time, and flag short cycling, low and over-pressure and a lost prime. A teaching model with low-voltage stand-ins, never for a real pump or pressure switch. The whole build, an Arduino Uno and 11 more parts, runs here in your browser on the firmware below; open it in the editor to change the wiring or the code and run it again.
Teaching? Assign it to your class in one click.
Advanced About 40 minutes to build and run. Runs in your browser. Free, and no account needed.
The parts retail for about $47. On Mokxi they're free.
What you'll learn
- Sensors
- Motors
- Reading a button
- Choosing and reading a resistor
- How an LED lights and why it needs a resistor
- Difficulty: Advanced
- Time: About 40 minutes
- Board: Arduino Uno R3
- Parts: 13
How it works
A pressure transducer and a pump relay run a 40/60 or 30/50 psi cut-in and cut-out, count pump cycles and run time, and flag short cycling, low and over-pressure and a lost prime. A teaching model with low-voltage stand-ins, never for a real pump or pressure switch.
Arduino Uno R3 runs the sketch below. The circuit around it: Pressure sender, 0.5-4.5 V, Relay module, Filament lamp, Slide switch, Pushbutton, Resistor, 220 Ω. pinMode sets PIN_RESET as an input with its pull-up on. digitalWrite drives PIN_PUMP HIGH. digitalRead reads whether PIN_RANGE is HIGH or LOW. analogRead measures the voltage on A0 as a number. Serial.begin opens the serial port at 115200 baud.
Parts list
13 parts, plus the jumper wires. They retail for about $47; on Mokxi they're free.
- 1 × Arduino Uno R3In Starter Kit, Claw Machine Kit, Vending Machine Kit.
- 1 × Full-size breadboard
- 1 × Pressure sender, 0.5-4.5 V
- 1 × Relay moduleIn Halloween Prop Kit.
- 1 × Filament lamp
- 1 × Slide switchIn Logic Lab Kit, Spaceship Panel Kit.
- 1 × PushbuttonIn Starter Kit, Christmas Lights Kit, Logic Lab Kit.
- 3 × Resistor, 220 ΩIn Starter Kit, Claw Machine Kit, Halloween Prop Kit.
- 1 × LED, greenIn Starter Kit, Halloween Prop Kit, Logic Lab Kit.
- 1 × LED, yellowIn Starter Kit, Halloween Prop Kit, Logic Lab Kit.
- 1 × LED, redIn Starter Kit, Halloween Prop Kit, Logic Lab Kit.
Wiring table
Every connection, pin by pin, read from the circuit itself. Each row joins the pin on the left to the pin on the right, by a jumper wire or a breadboard strip.
| Part and pin | Connects to |
|---|---|
| Resistor, 220 Ω (3) pin 1 | Arduino Uno R3 pin 11 |
| Resistor, 220 Ω (2) pin 1 | Arduino Uno R3 pin 10 |
| Resistor, 220 Ω (1) pin 1 | Arduino Uno R3 pin 9 |
| Relay module pin IN | Arduino Uno R3 pin 7 |
| Slide switch pin 2 | Arduino Uno R3 pin 3 |
| Pushbutton pin 1a | Arduino Uno R3 pin 2 |
| Pressure sender, 0.5-4.5 V pin VCC | Arduino Uno R3 pin 5V |
| Relay module pin VCC | Arduino Uno R3 pin 5V |
| Relay module pin COM | Arduino Uno R3 pin 5V |
| Pressure sender, 0.5-4.5 V pin GND | Arduino Uno R3 pin GND |
| Relay module pin GND | Arduino Uno R3 pin GND |
| Filament lamp pin 2 | Arduino Uno R3 pin GND |
| Slide switch pin 3 | Arduino Uno R3 pin GND |
| Pushbutton pin 2a | Arduino Uno R3 pin GND |
| LED, green pin C | Arduino Uno R3 pin GND |
| LED, yellow pin C | Arduino Uno R3 pin GND |
| LED, red pin C | Arduino Uno R3 pin GND |
| Pressure sender, 0.5-4.5 V pin SIG | Arduino Uno R3 pin A0 |
| Filament lamp pin 1 | Relay module pin NO |
| LED, green pin A | Resistor, 220 Ω (1) pin 2 |
| LED, yellow pin A | Resistor, 220 Ω (2) pin 2 |
| LED, red pin A | Resistor, 220 Ω (3) pin 2 |
The code
The firmware exactly as the editor opens it. Change a line there and press Run: it compiles in the browser.
// Well pressure tank monitor: a pressure transducer on the tank, a relay for
// the pump, and a counter that watches how often and how long the pump runs.
// The sketch does the job of the pressure switch (cut-in and cut-out) and
// then goes further: it counts cycles and run time, and it raises warnings
// that a plain switch never does.
//
// TEACHING MODEL ONLY. A real well pump runs on 120 or 240 V, and its
// pressure switch sits in that line-voltage circuit, with a relief valve on
// the tank as the last defence. Never connect this to a real pump, a real
// pressure switch or house wiring. Here the "pump" is a small lamp on a relay
// module at 5 V, and the water is you dragging the transducer's pressure.
//
// A0 the transducer's SIG (red to 5 V, black to GND)
// pin 2 RESET button to GND (the chip's pull-up holds it HIGH)
// pin 3 range switch: open is 40/60 psi, grounded is 30/50 psi
// pin 7 relay module IN (pump); lamp from the relay's NO contact
// pin 9 PUMP LED (green), 220 ohms to GND
// pin 10 WARN LED (yellow), 220 ohms to GND
// pin 11 ALARM LED (red), 220 ohms to GND
//
// The transducer puts out 0.5 V at no pressure and 4.5 V at full scale: 10%
// and 90% of its supply, as in the water pressure gauge, so the sketch works
// in fractions of the 5 V supply and a supply a little off cancels out. A
// reading under 3% or over 97% of the supply means a broken wire or a
// shorted lead, not a pressure.
//
// Pump rules:
// cut-in the pump starts at or below it (40 psi, or 30 on the 30/50 range)
// cut-out the pump stops at or above it (60 psi, or 50)
// over-pressure cut-out plus 10 psi: pump off, alarm, latched until RESET
// with the pressure back under cut-out
// no-rise the pump has run 60 s and the pressure rose less than 2 psi: a
// lost prime, a dry well or a broken pipe. Pump off, latched until
// RESET
// low pump on for 30 s and still under cut-in: the pump is not
// keeping up (a big leak or demand, a tired pump)
// short 3 cycles in a row with the pump on under 30 s, or starts under
// 60 s apart: a waterlogged tank (lost air charge), a bad
// pressure switch or a leak. Check the tank's air with the water
// off and the tank drained.
//
// Times are scaled: 1 second of pump time is 100 ms here, so a 40 second run
// takes 4 seconds to watch. Set MS_PER_S to 1000 for real time.
const int PIN_RESET = 2;
const int PIN_RANGE = 3;
const int PIN_PUMP = 7;
const int PIN_RUN_LED = 9;
const int PIN_WARN_LED = 10;
const int PIN_ALARM_LED = 11;
const float FULL_SCALE_PSI = 100.0; // the model printed on the body
const unsigned long MS_PER_S = 100; // scaled time: 100 ms is one second
const float OVER_MARGIN_PSI = 10.0; // above cut-out by this: alarm
const unsigned long SHORT_BETWEEN_S = 60; // starts closer than this are short
const unsigned long SHORT_RUN_S = 30; // runs shorter than this are short
const int SHORT_FLAG_AFTER = 3; // short cycles in a row to flag
const unsigned long LOW_AFTER_S = 30; // running this long under cut-in: low
const unsigned long NO_RISE_WINDOW_S = 60; // running this long ...
const float NO_RISE_MIN_PSI = 2.0; // ... must raise the pressure this much
float cutIn = 40.0;
float cutOut = 60.0;
float psi = 0;
bool pump = false;
bool sensorFault = false;
bool overLatched = false;
bool noRiseLatched = false;
bool lowWarn = false;
bool shortFlag = false;
int shortStreak = 0;
unsigned long cycles = 0;
unsigned long lastRunS = 0;
unsigned long totalRunS = 0;
unsigned long startMs = 0;
unsigned long lastStartMs = 0;
unsigned long windowMs = 0;
float windowPsi = 0;
unsigned long starts[4]; // the last four start times, oldest first
int startCount = 0;
bool intervalShort = false; // this cycle began under 60 s after the last start
bool lastRange = HIGH;
unsigned long lastSample = 0;
unsigned long lastReport = 0;
bool lastButton = HIGH;
unsigned long buttonChangedAt = 0;
bool readPsi() {
long sum = 0;
for (int i = 0; i < 16; i++) sum += analogRead(A0);
float fraction = sum / 16.0 / 1023.0; // of the supply
if (fraction < 0.03 || fraction > 0.97) return false;
psi = (fraction - 0.1) / 0.8 * FULL_SCALE_PSI;
if (psi < 0) psi = 0;
return true;
}
void pumpOn() {
unsigned long now = millis();
pump = true;
digitalWrite(PIN_PUMP, HIGH);
intervalShort = cycles > 0 && (now - lastStartMs) < SHORT_BETWEEN_S * MS_PER_S;
lastStartMs = now;
startMs = now;
windowMs = now;
windowPsi = psi;
cycles++;
if (startCount == 4) {
for (int i = 0; i < 3; i++) starts[i] = starts[i + 1];
startCount = 3;
}
starts[startCount++] = now;
Serial.print("PUMP ON at ");
Serial.print(psi, 1);
Serial.print(" psi, cycle ");
Serial.println(cycles);
}
// Stops the pump. A finished cycle is judged for short cycling; a pump cut by
// a guard is not counted as a cycle.
void pumpOff(bool finishedCycle) {
if (!pump) return;
unsigned long now = millis();
pump = false;
digitalWrite(PIN_PUMP, LOW);
lowWarn = false;
lastRunS = (now - startMs) / MS_PER_S;
totalRunS += lastRunS;
Serial.print("PUMP OFF at ");
Serial.print(psi, 1);
Serial.print(" psi after ");
Serial.print(lastRunS);
Serial.println("s");
if (!finishedCycle) return;
if (lastRunS < SHORT_RUN_S || intervalShort) {
shortStreak++;
if (shortStreak >= SHORT_FLAG_AFTER && !shortFlag) {
shortFlag = true;
Serial.println("SHORT CYCLING: check the tank air charge (waterlogged), pressure switch, leaks");
}
} else {
shortStreak = 0;
if (shortFlag) Serial.println("Short cycling cleared");
shortFlag = false;
}
}
int cyclesPerHour() {
if (startCount < 2) return 0;
unsigned long span = (starts[startCount - 1] - starts[0]) / MS_PER_S;
if (span == 0) return 0;
return (int)(3600UL * (startCount - 1) / span);
}
void checkRange() {
bool now = digitalRead(PIN_RANGE);
if (now != lastRange) {
lastRange = now;
cutIn = now ? 40.0 : 30.0;
cutOut = now ? 60.0 : 50.0;
Serial.print("RANGE ");
Serial.println(now ? "40/60 psi" : "30/50 psi");
}
}
void checkReset() {
bool now = digitalRead(PIN_RESET);
if (now != lastButton && millis() - buttonChangedAt > 30) {
buttonChangedAt = millis();
lastButton = now;
if (now != LOW) return; // act on the press, not the release
if (!overLatched && !noRiseLatched) {
Serial.println("RESET: nothing is latched");
} else if (sensorFault) {
Serial.println("RESET refused: sensor fault");
} else if (overLatched && psi >= cutOut) {
Serial.print("RESET refused: ");
Serial.print(psi, 1);
Serial.println(" psi, must be under cut-out");
} else {
overLatched = false;
noRiseLatched = false;
Serial.println("RESET: latches cleared");
}
}
}
void control() {
if (!readPsi()) {
if (!sensorFault) {
sensorFault = true;
pumpOff(false);
Serial.println("SENSOR FAULT: transducer open or shorted, pump forced off");
}
return;
}
if (sensorFault) {
sensorFault = false;
Serial.println("SENSOR OK again");
}
unsigned long now = millis();
// Over-pressure: stop the pump and latch the alarm.
if (!overLatched && psi >= cutOut + OVER_MARGIN_PSI) {
overLatched = true;
pumpOff(false);
Serial.print("OVER-PRESSURE at ");
Serial.print(psi, 1);
Serial.println(" psi: pump off, latched until RESET");
}
if (pump) {
// No rise: the pump runs and the water does not come.
if (now - windowMs >= NO_RISE_WINDOW_S * MS_PER_S) {
if (psi - windowPsi < NO_RISE_MIN_PSI) {
noRiseLatched = true;
pumpOff(false);
Serial.print("NO RISE: pump ran ");
Serial.print(NO_RISE_WINDOW_S);
Serial.println("s with no pressure gain, lost prime or dry well, latched until RESET");
} else {
windowMs = now;
windowPsi = psi;
}
}
// Low: still under cut-in after a long run.
if (pump && !lowWarn && now - startMs >= LOW_AFTER_S * MS_PER_S && psi < cutIn) {
lowWarn = true;
Serial.println("LOW PRESSURE: pump is not keeping up");
}
if (lowWarn && psi >= cutIn) lowWarn = false;
}
if (overLatched || noRiseLatched) {
pumpOff(false);
return;
}
if (!pump && psi <= cutIn) pumpOn();
else if (pump && psi >= cutOut) pumpOff(true);
}
void setup() {
Serial.begin(115200);
Serial.println("Mokxi Arduino Uno: pressure tank monitor (teaching model)");
Serial.println("Never connect this to a real well pump or pressure switch");
pinMode(PIN_RESET, INPUT_PULLUP);
pinMode(PIN_RANGE, INPUT_PULLUP);
pinMode(PIN_PUMP, OUTPUT);
pinMode(PIN_RUN_LED, OUTPUT);
pinMode(PIN_WARN_LED, OUTPUT);
pinMode(PIN_ALARM_LED, OUTPUT);
digitalWrite(PIN_PUMP, LOW);
lastRange = digitalRead(PIN_RANGE);
if (!lastRange) {
cutIn = 30.0;
cutOut = 50.0;
}
Serial.print("RANGE ");
Serial.println(lastRange ? "40/60 psi" : "30/50 psi");
readPsi();
}
void loop() {
checkReset();
checkRange();
if (millis() - lastSample >= 20) {
lastSample = millis();
control();
}
bool alarm = overLatched || noRiseLatched || sensorFault;
bool warn = !alarm && (lowWarn || shortFlag);
digitalWrite(PIN_RUN_LED, pump ? HIGH : LOW);
digitalWrite(PIN_WARN_LED, warn ? HIGH : LOW);
digitalWrite(PIN_ALARM_LED, alarm ? HIGH : LOW);
if (millis() - lastReport >= 1000) {
lastReport = millis();
Serial.print("psi ");
if (sensorFault) Serial.print("---");
else Serial.print(psi, 1);
Serial.print(" pump ");
Serial.print(pump ? "ON" : "OFF");
Serial.print(" cycles ");
Serial.print(cycles);
Serial.print(" last-run ");
Serial.print(lastRunS);
Serial.print("s state ");
if (sensorFault) Serial.print("SENSOR");
else if (overLatched) Serial.print("OVER-PRESSURE");
else if (noRiseLatched) Serial.print("NO-RISE");
else if (lowWarn) Serial.print("LOW-PRESSURE");
else if (shortFlag) Serial.print("SHORT-CYCLING");
else Serial.print("OK");
Serial.print(" total-run ");
Serial.print(totalRunS);
Serial.print("s cph ");
Serial.println(cyclesPerHour());
}
}
Code walkthrough
The key functions of the sketch, and what each one does.
setup sketch.ino
Runs once when the board starts. It gets the pins and libraries ready.
void setup() {
Serial.begin(115200);
Serial.println("Mokxi Arduino Uno: pressure tank monitor (teaching model)");
Serial.println("Never connect this to a real well pump or pressure switch");
pinMode(PIN_RESET, INPUT_PULLUP);
pinMode(PIN_RANGE, INPUT_PULLUP);
pinMode(PIN_PUMP, OUTPUT);
pinMode(PIN_RUN_LED, OUTPUT);
pinMode(PIN_WARN_LED, OUTPUT);
pinMode(PIN_ALARM_LED, OUTPUT);
digitalWrite(PIN_PUMP, LOW);
lastRange = digitalRead(PIN_RANGE);
if (!lastRange) {
cutIn = 30.0;
cutOut = 50.0;
}
Serial.print("RANGE ");
Serial.println(lastRange ? "40/60 psi" : "30/50 psi");
readPsi();
}loop sketch.ino
Runs over and over for as long as the board has power. This is where the project does its work.
void loop() {
checkReset();
checkRange();
if (millis() - lastSample >= 20) {
lastSample = millis();
control();
}
bool alarm = overLatched || noRiseLatched || sensorFault;
bool warn = !alarm && (lowWarn || shortFlag);
digitalWrite(PIN_RUN_LED, pump ? HIGH : LOW);
digitalWrite(PIN_WARN_LED, warn ? HIGH : LOW);
digitalWrite(PIN_ALARM_LED, alarm ? HIGH : LOW);
if (millis() - lastReport >= 1000) {
lastReport = millis();
Serial.print("psi ");
if (sensorFault) Serial.print("---");
else Serial.print(psi, 1);
Serial.print(" pump ");
Serial.print(pump ? "ON" : "OFF");
Serial.print(" cycles ");
Serial.print(cycles);
Serial.print(" last-run ");
Serial.print(lastRunS);
Serial.print("s state ");
if (sensorFault) Serial.print("SENSOR");
else if (overLatched) Serial.print("OVER-PRESSURE");
// ... 10 more lines in the editorTroubleshooting
The usual mistakes with this circuit, and the fix for each.
- The reading is off at zero pressure. Pressure sender
The output is 0.5 V at no pressure, not 0 V. Subtract that offset, then scale 0.5 V to 4.5 V over the full range of the model. - The ADC saturates, or the board is damaged. Pressure sender
Power it from 5 V. On a 3.3 V board, the 4.5 V output is too high: use a divider. Check the three wires (supply, ground, signal), as colors vary by brand. - The relay does not click, or the board resets when it switches. Relay module
A relay coil needs more current than a pin can give. Use a module with a transistor driver, and power VCC from 5 V. Share ground between the module and the Arduino. - The relay works backwards: on when you expect off. Relay module
Many modules are active LOW: the input must go LOW to turn the relay on. Set the pin HIGH at startup so the load does not switch on during boot. - The lamp is dim or does not light when driven from a board pin. Filament lamp
A lamp needs far more current than a pin gives (a pin is limited to about 20 mA). Switch it with a transistor or MOSFET from its own supply that matches the lamp's voltage rating. - The lamp's brightness lags or it burns out the switch when it first turns on. Filament lamp
A cold filament draws several times its running current at first. Size the switch for that surge, not just the steady current. - The input reads random values in one position. Slide switch
A switch only connects or disconnects. Add a pull-up or pull-down resistor, or use INPUT_PULLUP, so the pin has a defined level when the switch is open. - The load never turns on when the switch is flipped. Slide switch
Use the center (common) leg and one outer leg. The unused outer leg goes nowhere. Check that the switch is in series with the load.
Change it and keep it
Open it in the editor, change the circuit or the code, and keep your version in a free account.