To build a reliable turbidity sensor Arduino project, pair the DFRobot SEN0189 analog sensor with an Arduino Uno R4 Minima. While older tutorials use the classic Uno R3, the R4 Minima features a 14-bit ADC (configurable to 12-bit) that provides the granular voltage resolution required to map subtle changes in Nephelometric Turbidity Units (NTU). Expect to spend around $45 total for the microcontroller and sensor, with an additional $5 for wiring and a waterproof enclosure if deploying outdoors.

Difficulty Rating: Intermediate (Requires basic analog circuit knowledge and polynomial mapping)
Time to Complete: 45 minutes for wiring and baseline code testing

The Quick Decision: Which Turbidity Sensor Should You Buy?

Not all turbidity sensors use the same optical geometry. Hobbyist sensors typically measure light scattering (nephelometric), while industrial probes often measure light attenuation. Use this decision matrix to select the exact module for your build.

Use Case Recommended Module Approx. Cost Output / Interface Verdict
Standard Hobby / School Project
(Aquariums, hydroponics, water filter testing)
DFRobot SEN0189 $35.00 0-5V Analog DEFAULT PICK. Best documented, reliable IR LED driver, stable baseline.
Ultra-Budget / Proof of Concept
(Disposable academic demos)
Generic TSW-20M $8.00 Analog / Digital Avoid for precision. High unit-to-unit variance; requires manual per-sensor calibration.
Continuous Outdoor / Industrial
(Rivers, wastewater, 24/7 logging)
RS485 Modbus Industrial Probe $120.00+ RS485 (Modbus RTU) Required for long cable runs (>10m) and harsh environments. Needs an RS485-to-TTL adapter.

Decision Path Termination: For 95% of makers building a benchtop or indoor water quality monitor, buy the DFRobot SEN0189. The rest of this guide assumes this exact module.

Hardware Spec Sheet & Pin Mapping

The SEN0189 works by shining an infrared light into the water at a specific angle and measuring the scattered light with a phototransistor positioned at 90 degrees. As water gets cloudier, more light scatters into the receiver, dropping the output voltage. According to the USGS Water Science School, this nephelometric method is the standard for reporting NTU in environmental monitoring.

DFRobot SEN0189 Specifications

Operating Voltage5V DC (Strict requirement; 3.3V will not drive the IR LED sufficiently)
Output SignalAnalog Voltage (0 to 4.5V)
Measurement Range0 to 3000 NTU
Response Time< 500ms
Operating Temperature-10°C to 50°C

Wiring Pinout (Arduino Uno R4 Minima)

SEN0189 Wire Color Function Arduino Uno R4 Minima Pin
RedVCC5V
BlackGNDGND
Blue (or Green)Signal (Analog Out)A0

Wiring and Calibration Steps

  1. Prep the Microcontroller: Plug the Arduino Uno R4 Minima into your PC via USB-C. Ensure you have selected the correct board and port in the Arduino IDE (Tools > Board > Arduino UNO R4 Boards > Arduino UNO R4 Minima).
  2. Connect Power: Connect the sensor's Red wire to the 5V pin and Black wire to GND. Do not use the 3.3V pin. The internal IR LED requires the forward voltage provided by the 5V rail to penetrate turbid samples.
  3. Connect Signal: Route the Blue signal wire to Analog Pin A0. Use 22 AWG stranded wire if extending the cable, keeping the total run under 1 meter to avoid analog noise pickup from ambient AC mains fields.
  4. Establish a Clear Water Baseline: Submerge the sensor head in a beaker of distilled or highly filtered tap water. Ensure the optical window is fully submerged but not touching the bottom or sides of the glass.
  5. Measure Baseline Voltage: Using a multimeter, probe the signal wire. In clear water, you should read between 4.0V and 4.2V. If you read near 0V in clear water, your sensor is defective or wired backward.

Complete Arduino Code (Target: Uno R4 Minima)

This sketch leverages the Arduino analogReadResolution() function to bump the ADC from the default 10-bit (1024 steps) to 12-bit (4096 steps). This quadruples your voltage resolution, which is critical when distinguishing between 5 NTU and 15 NTU in relatively clear water. It also includes a moving average filter to smooth out noise caused by floating particulates drifting past the optical window.

// Target Board: Arduino Uno R4 Minima
// Sensor: DFRobot SEN0189 Analog Turbidity Sensor
// Library dependencies: None (Standard Arduino API)

const int TURBIDITY_PIN = A0;
const int SAMPLE_COUNT = 20;      // Number of samples for moving average
const int ADC_RESOLUTION = 12;    // 12-bit = 4095 max value
const float VREF = 5.0;           // Reference voltage

float voltageSamples[SAMPLE_COUNT];
int sampleIndex = 0;

void setup() {
  Serial.begin(115200);
  while (!Serial) {
    ; // Wait for serial port to connect (Native USB on R4)
  }
  
  // Configure ADC for higher resolution
  analogReadResolution(ADC_RESOLUTION);
  
  // Initialize sample array
  for (int i = 0; i < SAMPLE_COUNT; i++) {
    voltageSamples[i] = 0.0;
  }
  
  Serial.println("Turbidity Sensor Initialized (12-bit ADC).");
  Serial.println("----------------------------------------");
}

void loop() {
  // Read raw ADC value
  int rawAdc = analogRead(TURBIDITY_PIN);
  
  // Convert to voltage
  float currentVoltage = (rawAdc * VREF) / pow(2, ADC_RESOLUTION) - 1; 
  // Note: pow(2, 12) - 1 = 4095
  
  // Error handling: Check for out-of-bounds voltage (sensor disconnected or shorted)
  if (currentVoltage < 0.05 || currentVoltage > 4.8) {
    Serial.print("Error: Sensor voltage out of bounds (Read: ");
    Serial.print(currentVoltage);
    Serial.println("V). Check wiring or 5V rail.");
    delay(1000);
    return;
  }

  // Store in circular buffer for moving average
  voltageSamples[sampleIndex] = currentVoltage;
  sampleIndex = (sampleIndex + 1) % SAMPLE_COUNT;

  // Calculate average voltage
  float avgVoltage = 0;
  for (int i = 0; i < SAMPLE_COUNT; i++) {
    avgVoltage += voltageSamples[i];
  }
  avgVoltage /= SAMPLE_COUNT;

  // Map voltage to NTU using DFRobot's empirical polynomial formula
  // Formula derived from SEN0189 datasheet calibration curve
  float ntu = -1120.4 * sq(avgVoltage) + 5742.3 * avgVoltage - 4352.9;
  
  // Constrain NTU to physical limits of the sensor
  if (ntu < 0) ntu = 0;
  if (avgVoltage < 2.5) ntu = 3000; // Sensor is saturated (extremely muddy)

  // Output data
  Serial.print("Raw ADC: ");
  Serial.print(rawAdc);
  Serial.print(" | Avg Voltage: ");
  Serial.print(avgVoltage, 2);
  Serial.print(" V | Turbidity: ");
  Serial.print(ntu, 2);
  Serial.println(" NTU");

  delay(500); // Read twice per second
}

Debugging: Why is My Sensor Reading 0.00 NTU or Stuck at Max?

Analog optical sensors are notoriously prone to environmental interference. If your build fails, follow this diagnostic path.

The First Three Things to Check:
  1. VCC Voltage at the Sensor Head: Put your multimeter probes directly on the sensor's VCC and GND pins. If it reads 3.3V or 4.2V instead of 4.9V-5.1V, your USB cable is dropping voltage or you wired it to the 3.3V rail. The IR LED will not penetrate turbid water without a full 5V.
  2. Optical Window Fouling: Inspect the black epoxy optical window. A microscopic layer of biofilm, algae, or fingerprint oil will scatter light prematurely, causing massive NTU spikes even in distilled water. Wipe gently with a microfiber cloth and isopropyl alcohol.
  3. Analog Pin Mapping: Verify your code defines A0 and your physical wire is in A0. Plugging the signal wire into a digital pin (like D0) will result in erratic 0 or 1023/4095 readings.

Common Error Strings and Ranked Causes

Exact Serial Monitor Output Ranked Causes (Most Likely First) Fix / Measurement Threshold
Turbidity: 0.00 NTU (in visibly muddy water) 1. Powered by 3.3V instead of 5V.
2. Sensor submerged past the potting line (water inside the housing).
3. Phototransistor dead/shorted.
Measure VCC at sensor. Must be >4.8V. If water is inside the metal/plastic housing, the sensor is destroyed; replace it.
Turbidity: 3000.00 NTU (in clear distilled water) 1. Biofilm/fouling on the optical window.
2. Signal wire shorted to GND.
3. Sensor is touching the bottom/side of the beaker.
Clean window. Measure signal pin voltage with multimeter; should be ~4.1V in clear water. If it reads 0V, check for shorts.
Error: Sensor voltage out of bounds 1. Signal wire disconnected.
2. Floating analog pin picking up EMI.
Ensure Dupont connectors are fully seated. Read resistance between signal wire end and GND; should not be 0 ohms.

Extending and Simplifying the Build

Once you have the baseline NTU readings working, you can adapt the project to fit your specific deployment constraints.

How to Simplify (For Basic Automation)

If you don't actually need exact NTU values and just want to trigger a relay when a water filter fails, drop the polynomial math entirely. Map the raw 12-bit ADC value to a simple threshold. For the SEN0189, a 12-bit ADC reading dropping below 2800 (approx 3.4V) generally indicates the water has crossed from "clear" to "noticeably cloudy" (>100 NTU). Use an if (rawAdc < 2800) { digitalWrite(RELAY_PIN, HIGH); } statement to trigger a flush valve or alarm.

How to Extend (For Environmental Telemetry)

To turn this into a remote river or aquaponics monitor:

  • Swap the Brain: Replace the Uno R4 with an ESP32-C3 SuperMini ($4). You will need to adjust the code to use the ESP32's analogReadMilliVolts() function, as the ESP32 ADC is notoriously non-linear and requires millivolt mapping rather than raw step mapping.
  • Add Temperature Compensation: Water temperature changes the refractive index and affects particle suspension. Add a waterproof DS18B20 ($6) to log temperature alongside NTU. You can then apply a correction factor in your backend database.
  • Implement Auto-Cleaning: In continuous deployments, biofilm will ruin your data within 48 hours. Mount a small 5V submersible pump to blast the optical window with clean water for 5 seconds before every reading cycle.

For deeper theoretical background on how light scattering correlates to suspended solids, refer to the DFRobot SEN0189 Wiki, which provides the raw calibration graphs used to derive the polynomial formula in the code above.