When you buy a generic TDS (Total Dissolved Solids) sensor for an Arduino or ESP32 project, the listing rarely tells you what the analog pin is actually outputting or how to handle the ESP32’s notorious ADC non-linearity. The industry-standard maker choice is the DFRobot Gravity Analog TDS Sensor (SKU: SEN0161), which outputs a 0-3.3V analog voltage corresponding to 0-1000+ ppm. Below is the exact wiring, the polynomial math required to convert raw ADC reads to physical units, and the decision framework for choosing the right probe for your specific water chemistry.

How a TDS Sensor Actually Measures Water

TDS is not measured optically; it is calculated by measuring Electrical Conductivity (EC). Pure H2O is actually an excellent electrical insulator. However, when salts, minerals, and metals dissolve in water, they break into positively and negatively charged ions. These ions act as charge carriers, turning the water into a conductor. The sensor’s probe applies a voltage across its electrodes and measures the resulting current flow. Higher TDS means higher conductivity, which translates to lower electrical resistance across the probe.

To prevent electrolysis—which would cause minerals to plate onto the electrodes and permanently ruin the calibration—the probe's internal circuitry drives the electrodes with an Alternating Current (AC) rather than DC. The onboard signal conditioning circuit measures the AC voltage drop, rectifies it, and outputs a clean, steady DC analog voltage (0-3.3V) that your microcontroller’s ADC can read safely.

Wiring the SEN0161 to an ESP32

The DFRobot SEN0161 uses a standard 3-pin Gravity connector. While it can tolerate 5V on the VCC pin, you must power it with 3.3V when using an ESP32 to ensure the analog output never exceeds the ESP32’s 3.3V GPIO limit, which would fry the pin.

Sensor Pin ESP32 DevKit Pin Supply / Signal Range Notes & Constraints
VCC 3V3 3.3V to 5.0V Use 3.3V to keep AOUT within ESP32 ADC limits.
GND GND 0V Ensure a common ground with the ESP32 and any pumps.
AOUT GPIO 34 (ADC1_CH6) 0V to 3.3V (Analog) GPIO 34 is input-only and lacks an internal pull-up, making it ideal for ADC.
Bench Tip: The ESP32’s internal 12-bit ADC is notoriously non-linear above 2.5V and the onboard 3.3V regulator often outputs closer to 3.15V. For hobbyist hydroponics, the internal ADC is fine. For lab-grade accuracy, route the AOUT pin to an external 16-bit I2C ADC like the ADS1115 and use the ADS1115’s internal precision voltage reference.

The Math: Converting Raw ADC Reads to TDS (ppm)

The relationship between voltage and TDS is not perfectly linear. At higher concentrations, ion pairing occurs (positive and negative ions clump together, reducing their mobility). To compensate, DFRobot uses a 3rd-order polynomial to map the analog voltage to ppm.

Here is the exact C++ implementation for the ESP32. Note that we sample the ADC 30 times and average it to smooth out the ESP32’s inherent ADC noise.

const int TDS_PIN = 34;
const float V_REF = 3.24; // Measure your ESP32's 3V3 pin with a multimeter!

void setup() {
  Serial.begin(115200);
  analogReadResolution(12); // Set ESP32 ADC to 12-bit (0-4095)
  analogSetAttenuation(ADC_11db); // Full 0-3.3V range
}

void loop() {
  float averageVoltage = 0;
  
  // Oversample to reduce ESP32 ADC jitter
  for(int i = 0; i < 30; i++) {
    averageVoltage += analogRead(TDS_PIN);
    delay(2);
  }
  averageVoltage = averageVoltage / 30.0;
  
  // Convert raw ADC to Volts
  float voltage = (averageVoltage / 4095.0) * V_REF;
  
  // DFRobot Polynomial: TDS = (138.17*V^3 - 255.86*V^2 + 857.39*V) * 0.5
  float V_level = voltage;
  float tdsValue = (138.17 * pow(V_level, 3) - 255.86 * pow(V_level, 2) + 857.39 * V_level) * 0.5;
  
  Serial.print("Voltage: "); Serial.print(voltage); Serial.println(" V");
  Serial.print("TDS: "); Serial.print(tdsValue); Serial.println(" ppm");
  
  delay(1000);
}

Calibration Note: The V_REF variable is critical. Do not blindly assume it is 3.30V. Take a cheap digital multimeter, measure the voltage between the ESP32’s 3V3 and GND pins, and hardcode that exact number (e.g., 3.24) into your sketch. This single step eliminates 80% of baseline reading errors.

Real-World Interference: Temperature and Electrolysis

If your TDS readings drift wildly throughout the day, your sensor isn't broken; it's suffering from environmental interference. The two primary culprits are temperature fluctuations and electromagnetic noise.

1. Temperature Drift (The 2% Rule)

Electrical conductivity is highly dependent on water temperature. As water warms, ion mobility increases, causing the sensor to report a higher TDS even if the actual mineral content hasn't changed. According to the USGS water quality guidelines, conductivity typically changes by about 2% per degree Celsius. If you calibrate your sensor at 20°C and measure water at 30°C, your reading will be roughly 20% too high. The fix: You must add a waterproof DS18B20 temperature probe to your build and apply Automatic Temperature Compensation (ATC) math to normalize all readings to 25°C before running the polynomial.

2. EMI from AC Pumps

TDS probes output millivolt-level analog signals before onboard amplification. If your sensor cable is zip-tied to the power cord of an AC water pump or solenoid valve, inductive coupling will inject 50/60Hz noise into your reading. Keep the analog signal wire at least 2 inches away from any AC mains wiring or relay coils.

3. Bubble Accumulation

Dissolved gases can form micro-bubbles on the probe electrodes, acting as an insulator and causing the TDS reading to artificially drop. Gently tap the probe against the side of the reservoir during installation to dislodge trapped air.

Decision Tree: Which TDS Probe Should You Buy?

Not all water chemistry is created equal. A probe designed for drinking water will saturate and fail to read accurately in seawater. Use this decision matrix to select the correct hardware for your specific application.

Your Use Case Expected TDS Range Recommended Hardware Est. Price (2026)
Hydroponics / Drinking Water / RO Monitoring 0 - 1,000 ppm DFRobot Gravity SEN0161 $45 - $55
Brackish Water / Seawater / High Salinity 1,000 - 50,000+ ppm Atlas Scientific EC Probe (K=1.0 or K=10) $190 - $230
Basic RO Membrane Health Check (No MCU) 0 - 999 ppm Generic Inline TDS Meter (HM Digital TDS-3) $12 - $18
Industrial Wastewater / Heavy Fouling Variable Sensorex CS200TC (with self-cleaning wiper) $800+
The Default Pick: For 90% of maker projects—including automated hydroponic dosing, aquarium top-off systems, and home water filtration monitoring—buy the DFRobot SEN0161. It provides the best balance of plug-and-play 3.3V analog output, community-supported math libraries, and a replaceable titanium-alloy probe that won't corrode in standard nutrient solutions. Reserve the Atlas Scientific gear strictly for high-salinity or research-grade applications where you need I2C/UART digital output and lab-certified NIST traceability.