Built-in project · Arduino Uno R3

Aquarium water monitor on an Arduino Uno

Five water-quality probes on one Uno: TDS, pH, turbidity, dissolved oxygen and conductivity, each read with its maker’s own formula. The whole build, an Arduino Uno and 5 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.

Intermediate About 15 minutes to build and run. Runs in your browser. Free, and no account needed.

The parts retail for about $107. On Mokxi they're free.

The circuit runs here once the page has loaded.

What you'll learn

  • Reading several analog sensors on one board
  • Averaging many readings to reduce noise
  • Converting an ADC count to volts
  • Turning a voltage into a measurement with a formula
  • Why real probes need calibrating
  • Difficulty: Intermediate
  • Time: About 15 minutes
  • Board: Arduino Uno R3
  • Parts: 7

How it works

Five water-quality boards connect to analog pins A0 to A4: TDS, pH, turbidity, dissolved oxygen and conductivity. They all share 5V and ground. Each one outputs a voltage that depends on the water.

For each probe the sketch reads the pin 20 times and averages the readings, which smooths out noise. It then turns the voltage into a real unit using the maker's formula and prints one line to the Serial Monitor every half second.

The formulas assume water at 25 C. The variable waterC is there so a temperature sensor could correct the TDS, oxygen and conductivity values. Real probes need calibration, so the numbers are typical ones.

Parts list

7 parts, plus the jumper wires. They retail for about $107; on Mokxi they're free.

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
Water quality sensor, pH, TDS, EC, DO, NTU (1) pin VCCArduino Uno R3 pin 5V
Water quality sensor, pH, TDS, EC, DO, NTU (2) pin VCCArduino Uno R3 pin 5V
Water quality sensor, pH, TDS, EC, DO, NTU (3) pin VCCArduino Uno R3 pin 5V
Water quality sensor, pH, TDS, EC, DO, NTU (4) pin VCCArduino Uno R3 pin 5V
Water quality sensor, pH, TDS, EC, DO, NTU (5) pin VCCArduino Uno R3 pin 5V
Water quality sensor, pH, TDS, EC, DO, NTU (1) pin GNDArduino Uno R3 pin GND
Water quality sensor, pH, TDS, EC, DO, NTU (2) pin GNDArduino Uno R3 pin GND
Water quality sensor, pH, TDS, EC, DO, NTU (3) pin GNDArduino Uno R3 pin GND
Water quality sensor, pH, TDS, EC, DO, NTU (4) pin GNDArduino Uno R3 pin GND
Water quality sensor, pH, TDS, EC, DO, NTU (5) pin GNDArduino Uno R3 pin GND
Water quality sensor, pH, TDS, EC, DO, NTU (1) pin OUTArduino Uno R3 pin A0
Water quality sensor, pH, TDS, EC, DO, NTU (2) pin OUTArduino Uno R3 pin A1
Water quality sensor, pH, TDS, EC, DO, NTU (3) pin OUTArduino Uno R3 pin A2
Water quality sensor, pH, TDS, EC, DO, NTU (4) pin OUTArduino Uno R3 pin A3
Water quality sensor, pH, TDS, EC, DO, NTU (5) pin OUTArduino Uno R3 pin A4

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
// Aquarium and hydroponics water monitor: five analog water-quality probes
// on one Uno, each read with its maker's own formula, printed on Serial.
//
//   A0      TDS board (DFRobot Gravity SEN0244)            A
//   A1      pH board (PH-4502C)                            Po
//   A2      turbidity board (DFRobot Gravity SEN0189)      A, switch on A
//   A3      dissolved oxygen board (DFRobot SEN0237)       A
//   A4      EC board (DFRobot Gravity DFR0300, K = 1)      A
//   Every board's VCC (+ or V+) on 5 V and GND (- or G) on GND.
//
// The water is assumed to be at 25 C, the temperature every formula below is
// written for. With a waterproof DS18B20 in the tank, put its reading in
// waterC and the TDS, DO and EC lines compensate the way the makers' own code
// does.
//
// Each probe is read 20 times and averaged, and the voltage turned into the
// water's value:
//   TDS   EC = 133.42 V^3 - 255.86 V^2 + 857.39 V, over 1 + 0.02 (T - 25),
//         and TDS is half the EC (DFRobot's GravityTDS)
//   pH    pH = 7 + (2.5 - V) / 0.18, with the board's offset trimmer set so
//         pH 7 reads 2.5 V (the PH-4502C tutorials)
//   NTU   NTU = -1120.4 V^2 + 5742.3 V - 4352.9 (DFRobot's turbidity chart)
//   DO    DO = mV x saturation(T) / (1600 + 35 (T - 25)) (DFRobot's
//         single-point calibration, 1600 mV saturated at 25 C)
//   EC    EC = mV / 820 / 200 x 1000 mS/cm, over 1 + 0.0185 (T - 25)
//         (DFRobot_EC, uncalibrated K = 1)
// A real probe needs calibrating first; the numbers above are the makers'
// typical ones.

const float VREF = 5.0;
float waterC = 25.0;

float volts(int pin) {
  long sum = 0;
  for (int i = 0; i < 20; i++) sum += analogRead(pin);
  return sum / 20.0 * VREF / 1024.0;
}

void setup() {
  Serial.begin(115200);
  Serial.println("Mokxi Arduino Uno: water monitor");
}

void loop() {
  float v = volts(A0);
  float ec = 133.42 * v * v * v - 255.86 * v * v + 857.39 * v;
  float tds = ec / (1.0 + 0.02 * (waterC - 25.0)) * 0.5;

  v = volts(A1);
  float ph = 7.0 + (2.5 - v) / 0.18;

  v = volts(A2);
  float ntu = v < 2.5 ? 3000.0 : -1120.4 * v * v + 5742.3 * v - 4352.9;
  if (ntu < 0) ntu = 0;

  // Oxygen saturation of water at 25 C is 8.25 mg/L (DFRobot's DO_Table).
  float mv = volts(A3) * 1000.0;
  float oxygen = mv * 8.25 / (1600.0 + 35.0 * (waterC - 25.0));

  mv = volts(A4) * 1000.0;
  float cond = mv / 820.0 / 200.0 * 1000.0 / (1.0 + 0.0185 * (waterC - 25.0));

  Serial.print("TDS: ");
  Serial.print(tds, 0);
  Serial.print(" ppm  pH: ");
  Serial.print(ph, 2);
  Serial.print("  NTU: ");
  Serial.print(ntu, 0);
  Serial.print("  DO: ");
  Serial.print(oxygen, 2);
  Serial.print(" mg/L  EC: ");
  Serial.print(cond, 2);
  Serial.println(" mS/cm");
  delay(500);
}

Code walkthrough

The key functions of the sketch, and what each one does.

volts sketch.ino

Reads one analog pin 20 times, adds the results, divides by 20, then scales by VREF over 1024 to turn the count into volts. Every probe uses this, so noise is smoothed the same way for all five.

float volts(int pin) {
  long sum = 0;
  for (int i = 0; i < 20; i++) sum += analogRead(pin);
  return sum / 20.0 * VREF / 1024.0;
}

loop sketch.ino

Reads each probe in turn and applies its formula. TDS comes from a cubic curve, pH from the distance to 2.5 V, turbidity from a quadratic that is clamped at zero, oxygen and conductivity from millivolts. It then prints all five values on one line and waits 500 ms.

void loop() {
  float v = volts(A0);
  float ec = 133.42 * v * v * v - 255.86 * v * v + 857.39 * v;
  float tds = ec / (1.0 + 0.02 * (waterC - 25.0)) * 0.5;

  v = volts(A1);
  float ph = 7.0 + (2.5 - v) / 0.18;

  v = volts(A2);
  float ntu = v < 2.5 ? 3000.0 : -1120.4 * v * v + 5742.3 * v - 4352.9;
  if (ntu < 0) ntu = 0;

  // Oxygen saturation of water at 25 C is 8.25 mg/L (DFRobot's DO_Table).
  float mv = volts(A3) * 1000.0;
  float oxygen = mv * 8.25 / (1600.0 + 35.0 * (waterC - 25.0));

  mv = volts(A4) * 1000.0;
  float cond = mv / 820.0 / 200.0 * 1000.0 / (1.0 + 0.0185 * (waterC - 25.0));

  Serial.print("TDS: ");
  Serial.print(tds, 0);
  Serial.print(" ppm  pH: ");
  Serial.print(ph, 2);
  Serial.print("  NTU: ");
  Serial.print(ntu, 0);
  Serial.print("  DO: ");
  Serial.print(oxygen, 2);
  Serial.print(" mg/L  EC: ");
  // ... 4 more lines in the editor

Troubleshooting

The usual mistakes with this circuit, and the fix for each.

  • A reading is stuck or reads nonsense This project
    Check each board's OUT wire goes to the right pin: A0 TDS, A1 pH, A2 turbidity, A3 oxygen, A4 conductivity.
  • The values do not match a lab meter This project
    The formulas are uncalibrated typical ones at 25 C. Calibrate each probe, and set waterC if the water is a different temperature.
  • The readings are unstable or drift. Water quality sensor
    Let the probe settle in the sample. Keep it away from bubbles and the container wall. Rinse it between samples, and keep the probe clean.
  • The value does not match the real number. Water quality sensor
    The output is a voltage on a specific curve. Calibrate with known solutions (for pH, buffers 4, 7 and 10) and convert in code. Temperature matters too.
  • Upload fails or nothing runs after wiring pins 0 and 1 to a module. Arduino Uno R3
    Pins 0 and 1 are the USB serial port. Move the module to other pins, or unplug it while uploading. Also check the right board is selected.
  • Pins misbehave or the board resets when you power motors or servos from it. Arduino Uno R3
    Each I/O pin gives about 20 mA, 40 mA max. Power motors and servos from their own supply and join its ground to the Uno's GND.

Change it and keep it

Open it in the editor, change the circuit or the code, and keep your version in a free account.