Whether you are managing a hydroponics system, monitoring a fish pond, testing drinking water, or running a chemistry experiment, measuring pH accurately is fundamental to water quality monitoring. An Arduino pH sensor water quality interface allows you to read pH values continuously, log them over time, and trigger automated dosing pumps to maintain optimal pH ranges. This comprehensive guide covers analog pH sensor modules, calibration procedures, accurate code implementation, and a complete automatic pH control system for hydroponics.
Table of Contents
- Understanding pH and How pH Electrodes Work
- pH Sensor Modules for Arduino
- Wiring the pH Sensor to Arduino
- Calibration: Two-Point and Three-Point Methods
- Complete Arduino Code for pH Reading
- Temperature Compensation
- Hydroponics pH Automation System
- Frequently Asked Questions
Understanding pH and How pH Electrodes Work
pH is the measure of hydrogen ion concentration in a solution, expressed on a logarithmic scale from 0 (extremely acidic) to 14 (extremely alkaline), with 7 being neutral. Each full pH unit represents a 10× change in hydrogen ion concentration. Pure water at 25°C has a pH of exactly 7.00. Blood maintains pH 7.35–7.45. Tomatoes grow best at pH 5.5–6.5. Aquarium fish thrive between pH 6.5–8.0.
A pH electrode (glass electrode) generates a small voltage proportional to the hydrogen ion activity in the solution — approximately 59.16 mV per pH unit at 25°C (the Nernst equation). At pH 7, the electrode generates approximately 0 mV (the isopotential point). At pH 4, it generates about +177 mV. At pH 10, it generates about –177 mV.
This millivolt-level signal must be amplified and buffered to a voltage range readable by the Arduino’s 10-bit ADC (0–5V). pH sensor modules include a signal conditioning amplifier that maps the pH 0–14 range to approximately 0–3.5V output.
The glass membrane of a pH electrode must be kept moist and must be calibrated regularly because its response changes with:
- Temperature (Nernst slope changes with temperature)
- Electrode aging (the glass membrane deteriorates over 1–2 years)
- Reference junction contamination (from the sample solution)
- Junction potential changes with solution composition
pH Sensor Modules for Arduino
Several ready-to-use pH sensor module types are available for Arduino integration:
Analog pH Sensor Module (BNC connector): The most common type sold for Arduino. Includes a BNC connector for the pH probe, an amplifier circuit based on the LM324 op-amp, and outputs a 0–3.5V analog signal. Connect to any Arduino analog pin. Requires calibration with pH 4 and pH 7 buffer solutions. Price range: ₹400–₹800 for module + probe bundle.
DFRobot Gravity Analog pH Sensor V2: An improved commercial-grade module with better signal conditioning, temperature compensation input, and a 3-pin Gravity connector. More accurate and stable than generic clones. Available as a kit with probe, buffer solutions, and calibration screwdriver.
Atlas Scientific EZO-pH (I2C/UART): Professional-grade, lab-calibrated module with I2C or UART interface and excellent long-term stability. Used in commercial aquaculture, research, and industrial applications. More expensive (around $80–$150 USD) but far more accurate and drift-free.
For most hobbyist hydroponics, aquaponics, and monitoring projects, the analog BNC module provides a good balance of cost and accuracy. With proper calibration and temperature compensation, ±0.1–0.2 pH accuracy is achievable.
Wiring the pH Sensor to Arduino
Analog pH Module Wiring
pH Module VCC → Arduino 5V
pH Module GND → Arduino GND
pH Module Po → Arduino A0 (analog output)
pH Module Do → Arduino Pin 7 (digital threshold — optional)
PH probe BNC connector → Module BNC socket
Critical wiring notes:
- Use shielded cable for the connection between the module and Arduino if the cable is longer than 30 cm
- Keep the pH module away from switching power supplies, motors, and PWM signals — these introduce electrical noise that degrades ADC readings
- The pH probe itself uses a coaxial BNC cable; never extend the probe cable with regular wire (it must remain coaxial/shielded)
- If sharing power with other components, add a 100µF electrolytic capacitor across the 5V rail at the pH module to filter power supply noise
Temperature Sensor Wiring (for compensation)
DS18B20 VCC → Arduino 3.3V
DS18B20 GND → Arduino GND
DS18B20 DATA → Arduino Pin 2 (with 4.7kΩ pull-up to 3.3V)
Calibration: Two-Point and Three-Point Methods
Calibration is essential for accurate pH readings. The analog pH module has a zero-point trimmer (adjusts the output at pH 7) and a slope trimmer (adjusts the mV/pH ratio). You will need pH 4.01 and pH 6.86 or 7.00 buffer solutions (available as sachets or bottles).
Two-Point Calibration Procedure
- Rinse the pH probe with distilled water and pat dry (do not wipe — wiping creates static charge)
- Immerse the probe in pH 7.00 buffer solution. Wait 60 seconds for the reading to stabilise
- Adjust the zero-point trimmer on the module until the Arduino reads exactly 7.00 (or until the analog voltage is exactly 2.5V for most modules)
- Rinse with distilled water. Immerse probe in pH 4.01 buffer. Wait 60 seconds
- Note the voltage reading. If the slope is correct, the voltage should be approximately 3.0V for pH 4 (on a module where pH 7 = 2.5V)
- Adjust the slope trimmer if the reading deviates from the expected value
- Record your calibration voltage-to-pH mapping for use in the code
Software Calibration (More Flexible)
Instead of adjusting hardware trimmers, use software calibration constants stored in EEPROM:
// Calibration values (measure these for YOUR specific probe + module)
// At pH 4.01, the module outputs (e.g.) 3.02V → ADC reading ~618
// At pH 7.00, the module outputs (e.g.) 2.50V → ADC reading ~512
const float voltage_pH4 = 3.02; // measured at pH 4.01 buffer
const float voltage_pH7 = 2.50; // measured at pH 7.00 buffer
float voltageToPH(float voltage) {
// Linear interpolation between two calibration points
float slope = (7.00 - 4.01) / (voltage_pH7 - voltage_pH4);
float pH = 7.00 + slope * (voltage - voltage_pH7);
return pH;
}
Complete Arduino Code for pH Reading
#define PH_PIN A0
#define SAMPLES 20 // Number of ADC samples to average
#define SAMPLE_DELAY 30 // ms between samples
// Calibration constants — update after your calibration
const float CAL_VOLTAGE_PH4 = 3.02;
const float CAL_VOLTAGE_PH7 = 2.50;
// Read averaged voltage from pH sensor
float readPHVoltage() {
long sum = 0;
for (int i = 0; i < SAMPLES; i++) {
sum += analogRead(PH_PIN);
delay(SAMPLE_DELAY);
}
float avgADC = (float)sum / SAMPLES;
return avgADC * (5.0 / 1023.0); // Convert to voltage
}
// Convert voltage to pH using calibration
float voltageToPH(float voltage) {
float slope = (7.00 - 4.01) / (CAL_VOLTAGE_PH7 - CAL_VOLTAGE_PH4);
return 7.00 + slope * (voltage - CAL_VOLTAGE_PH7);
}
void setup() {
Serial.begin(9600);
// Use internal AREF for better stability (optional)
// analogReference(INTERNAL); // 1.1V internal ref for Uno (change divider constants)
Serial.println("Arduino pH Water Quality Monitor");
Serial.println("Voltage(V) | pH Value");
}
void loop() {
float voltage = readPHVoltage();
float pH = voltageToPH(voltage);
// Clamp to valid pH range
pH = constrain(pH, 0.0, 14.0);
Serial.print(voltage, 3);
Serial.print("V | pH ");
Serial.println(pH, 2);
// Interpret pH value
if (pH < 6.0) {
Serial.println(" → Acidic — consider adding pH Up (KOH)");
} else if (pH > 8.0) {
Serial.println(" → Alkaline — consider adding pH Down (H3PO4)");
} else {
Serial.println(" → Acceptable range");
}
delay(2000);
}
Noise Reduction: Median Filtering
For noisier environments, replace the simple average with a median filter:
int compare(const void* a, const void* b) {
return (*(int*)a - *(int*)b);
}
float readPHVoltageMedian() {
const int N = 11;
int readings[N];
for (int i = 0; i < N; i++) {
readings[i] = analogRead(PH_PIN);
delay(20);
}
qsort(readings, N, sizeof(int), compare);
return readings[N / 2] * (5.0 / 1023.0); // median value
}
Temperature Compensation
The Nernst slope (mV/pH) is temperature-dependent: 59.16 mV/pH at 25°C, but only 54.20 mV/pH at 0°C and 64.12 mV/pH at 60°C. If your solution temperature varies significantly, you must compensate:
#include <OneWire.h>
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 2
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
float readTemperature() {
sensors.requestTemperatures();
return sensors.getTempCByIndex(0);
}
// Temperature-compensated pH from voltage
float voltageToPH_Compensated(float voltage, float tempC) {
// Nernst factor adjusts with temperature
float nernst = 0.19840 * (273.15 + tempC); // mV/pH at given temp
// Slope calibration: ratio of actual to standard slope
float slopeCalibrated = (CAL_VOLTAGE_PH7 - CAL_VOLTAGE_PH4) /
(4.01 - 7.00); // mV per pH unit (measured)
float standardSlope = 59.16e-3; // V/pH at 25°C
float tempFactor = nernst / (25.0 + 273.15) / 1000.0 / standardSlope;
// Corrected pH calculation
float slope = (7.00 - 4.01) / (CAL_VOLTAGE_PH7 - CAL_VOLTAGE_PH4);
return 7.00 + (slope * tempFactor) * (voltage - CAL_VOLTAGE_PH7);
}
Hydroponics pH Automation System
A complete hydroponics pH control system reads pH, compares it to a target range, and activates dosing pumps to add pH Up (potassium hydroxide) or pH Down (phosphoric acid) as needed. Here is the control logic:
// Hydroponics pH Control System
#define PH_UP_PUMP 5 // Relay to pH Up pump (e.g., KOH solution)
#define PH_DOWN_PUMP 6 // Relay to pH Down pump (e.g., H3PO4 solution)
// Target pH range for hydroponics (adjust per crop)
const float PH_TARGET = 6.0;
const float PH_BAND = 0.2; // ±0.2 tolerance band
const float PH_LOW = PH_TARGET - PH_BAND; // 5.8
const float PH_HIGH = PH_TARGET + PH_BAND; // 6.2
// Dose settings
const unsigned long DOSE_DURATION_MS = 2000; // 2 seconds per dose
const unsigned long MIX_WAIT_MS = 60000; // 1 min for solution to mix
void setup() {
Serial.begin(9600);
pinMode(PH_UP_PUMP, OUTPUT);
pinMode(PH_DOWN_PUMP, OUTPUT);
digitalWrite(PH_UP_PUMP, LOW); // Pumps off
digitalWrite(PH_DOWN_PUMP, LOW);
}
void dose(int pumpPin, const char* label) {
Serial.print("Dosing: ");
Serial.println(label);
digitalWrite(pumpPin, HIGH);
delay(DOSE_DURATION_MS);
digitalWrite(pumpPin, LOW);
Serial.println("Dose complete. Waiting for mixing...");
delay(MIX_WAIT_MS); // Wait for solution to mix before next reading
}
void loop() {
float voltage = readPHVoltageMedian();
float tempC = readTemperature();
float pH = voltageToPH_Compensated(voltage, tempC);
pH = constrain(pH, 0.0, 14.0);
Serial.print("pH: ");
Serial.print(pH, 2);
Serial.print(" Temp: ");
Serial.print(tempC, 1);
Serial.println("°C");
if (pH < PH_LOW) {
dose(PH_UP_PUMP, "pH UP (raise pH)");
} else if (pH > PH_HIGH) {
dose(PH_DOWN_PUMP, "pH DOWN (lower pH)");
} else {
Serial.println("pH in target range — no action needed");
delay(30000); // Check every 30 seconds when in range
}
}
Frequently Asked Questions
How often should I calibrate the pH probe?
For reliable measurements, calibrate at least once per week in active hydroponics systems, or whenever the readings seem inconsistent. Glass membrane pH probes drift over time — especially after exposure to high-concentration acids, bases, or organic solutions. Always calibrate after storing the probe dry (though probes should ideally be stored in KCl storage solution, not dry).
Why do my pH readings fluctuate by 0.3–0.5 pH units?
The most common cause is electrical noise on the analog signal. Try: (1) averaging more samples (increase to 40–50), (2) using a median filter, (3) adding a 100nF capacitor from the pH module’s output pin to GND, (4) keeping the module far from PWM sources, motors, and relays, (5) using a separate regulated 5V supply for the pH module only.
Can I use the pH sensor to measure soil pH?
Yes, but you need to make a soil slurry: mix 1 part soil with 5 parts distilled water, stir thoroughly, let settle for 5 minutes, then measure the liquid. Direct insertion of a pH probe into soil will damage the glass membrane and give inaccurate readings. Dedicated solid-contact ion-selective electrodes are better for direct soil pH measurement.
What is the lifespan of a pH probe?
A typical glass pH electrode has a lifespan of 12–24 months with regular use and proper maintenance. Proper care includes: storing in KCl solution (not distilled water), rinsing before and after use, never touching the glass bulb, and avoiding contact with abrasive materials. A probe nearing end-of-life shows slow response time, unstable readings, and inability to calibrate correctly.
Can I measure the pH of highly pure or deionised water?
Standard glass pH electrodes require some ionic conductivity to function correctly. Deionised or very pure water (conductivity <10 µS/cm) gives unstable and inaccurate readings. This is a known limitation of all potentiometric pH sensors. For such measurements, use a flow-through cell or add a small amount of KCl to increase conductivity slightly.
Building an Arduino pH sensor water quality monitor opens the door to precision-controlled hydroponics, aquaculture health monitoring, water treatment verification, and environmental sensing. With proper calibration and temperature compensation, you can achieve accuracy that rivals commercial pH meters at a small fraction of the cost.
Add comment