Built-in project · Arduino Uno R3

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.

The circuit runs here once the page has loaded.

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.

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 pinConnects to
Resistor, 220 Ω (3) pin 1Arduino Uno R3 pin 11
Resistor, 220 Ω (2) pin 1Arduino Uno R3 pin 10
Resistor, 220 Ω (1) pin 1Arduino Uno R3 pin 9
Relay module pin INArduino Uno R3 pin 7
Slide switch pin 2Arduino Uno R3 pin 3
Pushbutton pin 1aArduino Uno R3 pin 2
Pressure sender, 0.5-4.5 V pin VCCArduino Uno R3 pin 5V
Relay module pin VCCArduino Uno R3 pin 5V
Relay module pin COMArduino Uno R3 pin 5V
Pressure sender, 0.5-4.5 V pin GNDArduino Uno R3 pin GND
Relay module pin GNDArduino Uno R3 pin GND
Filament lamp pin 2Arduino Uno R3 pin GND
Slide switch pin 3Arduino Uno R3 pin GND
Pushbutton pin 2aArduino Uno R3 pin GND
LED, green pin CArduino Uno R3 pin GND
LED, yellow pin CArduino Uno R3 pin GND
LED, red pin CArduino Uno R3 pin GND
Pressure sender, 0.5-4.5 V pin SIGArduino Uno R3 pin A0
Filament lamp pin 1Relay module pin NO
LED, green pin AResistor, 220 Ω (1) pin 2
LED, yellow pin AResistor, 220 Ω (2) pin 2
LED, red pin AResistor, 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.

sketch.ino · Arduino Uno R3
// 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 editor

Troubleshooting

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.