Introductions
Water is the essence of life, the fundamental resource that sustains all living creatures on Earth. Since the beginning of time, water has been the cradle of existence, shaping ecosystems and nurturing civilizations. The quality of drinking water is paramount—it must contain essential minerals in proper amounts, maintain safe levels of dissolved solids, and remain free from harmful contaminants to support health and well-being.
There is an important distinction between pure water and distilled water. Pure water, typically used for drinking, retains beneficial minerals and ions that contribute to its taste and nutritional value. In contrast, distilled water undergoes a process that removes all impurities, resulting in the purest form of water—devoid of minerals and other dissolved substances, making it unsuitable for regular consumption.
Interestingly, while water in its purest form is tasteless, the presence of dissolved solids—known as Total Dissolved Solids (TDS)—gives it distinct characteristics. Purified drinking water often has a subtle bitter note due to added minerals, while natural sources like borewell water may taste slightly sweet because of its mineral content. Distilled water, however, lacks any discernible taste since all dissolved substances have been removed. This variation in taste underscores the critical role TDS plays in determining water quality and its suitability for drinking.
The Total Dissolved Solids (TDS) content in water can be accurately measured using specialized sensors. These devices detect water conductivity and process the signal into a digital TDS reading. The fundamental principle involves a two-electrode system powered by a voltage source, where dissolved ions create measurable current flow between the electrodes.
Modern TDS sensors significantly improve upon this basic method by incorporating advanced features. These integrated modules automatically regulate the electric field strength and include built-in temperature compensation for greater accuracy. Many models now feature auto-zeroing capabilities and digital signal processing, eliminating the need for manual calibration in most applications. While traditional methods required calibration using buffer solutions, most contemporary sensors arrive factory-calibrated and ready for immediate use, though verification with reference solutions remains recommended for precision-critical applications.
The evolution from basic electrode systems to smart digital sensors has greatly simplified water quality testing while improving reliability and repeatability of measurements.
The TDS sensor operates across a wide voltage range of 3.3V to 5.5V, producing a 0-2.3V analog output signal, ensuring full compatibility with both 5V and 3.3V control systems. Its innovative AC excitation source design prevents electrode polarization, significantly enhancing probe longevity while maintaining stable output signal quality.
The waterproof probe construction allows for continuous immersion during long-term water quality monitoring. For accurate measurements in non-standard conditions, note that when testing water significantly above room temperature (25°C), an external temperature sensor must be integrated to provide proper temperature compensation.
Requirements for this lab
- Arduino IDE | PlatformIO | Visual Studio Code
- ARDUINO BOARDS & OTHER COMPATIBLE BOARDS
- TDS Sensor Module Board with Probe
- OLED Display Module
- DS18B20 Temperature Sensor (Optional)
- Resistors (See below required values)
- Others (See below diagram for other requirements)
i2C Address Finder
This I2C address finder tool quickly identifies your OLED display’s communication address – simply upload the provided code to your Arduino board and open the Serial Monitor (found under Tools → Serial Monitor) to view the detected address. The scanner works seamlessly with common OLED displays including 128×64 and 128×32 pixel models, requiring only basic I2C connections (typically SDA to pin A4 and SCL to pin A5 on standard Arduino boards) for reliable detection. This straightforward solution eliminates guesswork when setting up your display interface, providing instant confirmation of your device’s correct I2C address through a simple upload-and-check process.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
#include <Wire.h> // I2C library, required for MLX90614 #include <SparkFunMLX90614.h> IRTherm therm; // Create an IRTherm object to interact with throughout void setup() { Serial.begin(9600); // Initialize Serial to log output therm.begin(); // Initialize the MLX90614 if (therm.readID()) // Read from the ID registers { // If the read succeeded, print the ID: Serial.println("ID: 0x" + String(therm.getIDH(), HEX) + String(therm.getIDL(), HEX)); } } void loop() { } |
Test Code TDS (Arduino Code)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
// Include required libraries for I2C communication, graphics, and OLED display #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> // OLED display configuration (128x64 pixels with hardware reset pin) #define OLED_RESET 1 Adafruit_SSD1306 display(128, 64, &Wire, OLED_RESET); // TDS sensor configuration #define TdsSensorPin A1 // Analog input pin for TDS sensor #define VREF 5.0 // Reference voltage for ADC (5V for Arduino) #define SCOUNT 30 // Number of samples for median filtering int analogBuffer[SCOUNT]; // Circular buffer for raw ADC readings int analogBufferTemp[SCOUNT]; // Temporary buffer for processing int analogBufferIndex = 0, copyIndex = 0; float averageVoltage = 0, tdsValue = 0, temperature = 25; // Default temp 25°C void setup() { Serial.begin(115200); // Initialize serial communication pinMode(TdsSensorPin, INPUT); // Configure TDS sensor pin as input Wire.begin(); // Initialize I2C communication display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // Initialize OLED with I2C address 0x3C display.clearDisplay(); // Clear display buffer } void loop() { // Sample TDS sensor every 40ms (25Hz sampling rate) static unsigned long analogSampleTimepoint = millis(); if(millis()-analogSampleTimepoint > 40U) { analogSampleTimepoint = millis(); analogBuffer[analogBufferIndex] = analogRead(TdsSensorPin); // Store reading analogBufferIndex = (analogBufferIndex + 1) % SCOUNT; // Circular buffer } // Process and display readings every 800ms static unsigned long printTimepoint = millis(); if(millis()-printTimepoint > 800U) { printTimepoint = millis(); // Copy readings to temp buffer for processing for(copyIndex=0; copyIndex<SCOUNT; copyIndex++) { analogBufferTemp[copyIndex] = analogBuffer[copyIndex]; } // Calculate median voltage (noise filtering) and convert to volts averageVoltage = getMedianNum(analogBufferTemp,SCOUNT) * (float)VREF / 1024.0; // Apply temperature compensation (formula for 25°C reference) float compensationCoefficient = 1.0 + 0.02*(temperature - 25.0); float compensationVoltage = averageVoltage / compensationCoefficient; // Convert compensated voltage to TDS value (cubic approximation) tdsValue = (133.42*pow(compensationVoltage,3) - 255.86*pow(compensationVoltage,2) + 857.39*compensationVoltage)*0.5; // Serial.print("voltage:"); // Serial.print(averageVoltage,2); // Serial.print("V"); // Serial output for debugging Serial.print("TDS Value:"); Serial.print(tdsValue,0); Serial.println("ppm"); // OLED Display Output display.clearDisplay(); display.setTextSize(2); display.setTextColor(WHITE); // Display TDS label display.setCursor(0,0); display.print("TDS Value:"); // Display TDS value display.setCursor(30,30); display.print(tdsValue,0); // Display units display.setCursor(80,28); display.print("ppm"); display.display(); } } /** * Median filter algorithm for noise reduction * @param bArray Input array of sensor readings * @param iFilterLen Number of elements in array * @return Median value of the array */ int getMedianNum(int bArray[], int iFilterLen) { int bTab[iFilterLen]; // Temporary working array // Copy input array for (byte i = 0; i<iFilterLen; i++) { bTab[i] = bArray[i]; } // Sort array using bubble sort int i, j, bTemp; for (j = 0; j < iFilterLen - 1; j++) { for (i = 0; i < iFilterLen - j - 1; i++) { if (bTab[i] > bTab[i + 1]) { bTemp = bTab[i]; bTab[i] = bTab[i + 1]; bTab[i + 1] = bTemp; } } } // Return median (middle value for odd-length, average of middle two for even) if ((iFilterLen & 1) > 0) { return bTab[(iFilterLen - 1) / 2]; } else { return (bTab[iFilterLen / 2] + bTab[iFilterLen / 2 - 1]) / 2; } } |
Test Code Version 2
This Arduino code creates a water quality monitoring system that measures Total Dissolved Solids (TDS) using a specialized sensor. The system consists of three main components: a TDS sensor that connects to analog pin A1, an I2C-connected OLED display for showing results, and the Arduino board that processes the data. At startup, the code initializes serial communication at 115200 baud for debugging and sets up the TDS sensor with important parameters including the 5V reference voltage (matching Arduino UNO’s specification) and 10-bit analog resolution. The OLED display is prepared with a 128×64 pixel resolution and checked for proper connection – if initialization fails, the system halts to prevent incorrect readings.
The core measurement process runs continuously in the main loop. Every cycle, it takes a new TDS reading while applying automatic temperature compensation (using either a default 25°C value or an actual temperature reading if a sensor is connected). The GravityTDS library handles the complex calculations, converting the raw analog signal into a meaningful parts-per-million (ppm) value that represents water purity. Results are output both to the Serial Monitor for debugging and prominently displayed on the OLED screen with large, readable numbers. The display updates every second, showing the current TDS value in ppm along with a clear label. For accuracy, the system automatically compensates for water temperature variations, as TDS readings are temperature-dependent. The code structure allows for easy integration of additional sensors, with clear markers showing where to add temperature sensor functionality if needed.
This implementation provides a reliable, user-friendly water quality monitoring solution that’s particularly useful for applications like drinking water analysis, aquarium maintenance, hydroponic systems, and industrial water treatment processes. The combination of immediate visual feedback through the OLED and detailed serial output makes it versatile for both standalone use and connected systems.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
// Include required libraries: #include <EEPROM.h> // For storing calibration data #include "GravityTDS.h" // TDS sensor library #include <Wire.h> // I2C communication #include <Adafruit_GFX.h> // Core graphics library #include <Adafruit_SSD1306.h> // OLED display library // TDS Sensor Configuration #define TdsSensorPin A1 // Analog pin for TDS sensor input GravityTDS gravityTds; // Create TDS sensor instance // OLED Display Configuration #define SCREEN_WIDTH 128 // OLED width in pixels #define SCREEN_HEIGHT 64 // OLED height in pixels #define OLED_RESET -1 // Reset pin (-1 for shared reset) Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); // Measurement Variables float temperature = 25; // Default water temperature (25°C) float tdsValue = 0; // Stores calculated TDS value void setup() { Serial.begin(115200); // Initialize serial communication // Configure TDS sensor gravityTds.setPin(TdsSensorPin); // Set measurement pin gravityTds.setAref(5.0); // Reference voltage (5V for Arduino UNO) gravityTds.setAdcRange(1024); // 10-bit ADC resolution (0-1023) gravityTds.begin(); // Initialize TDS sensor // Initialize OLED display if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println(F("SSD1306 allocation failed")); while(true); // Halt if display initialization fails } // Display startup screen display.display(); // Show initial buffer delay(2); // Brief pause display.clearDisplay(); // Clear display // Show welcome message display.setTextColor(WHITE); display.setTextSize(2); display.setCursor(0,5); display.print("TDS Sensor"); display.display(); delay(3000); // Display welcome for 3 seconds } void loop() { // Uncomment to add temperature sensor functionality: // temperature = readTemperature(); // Configure TDS measurement with temperature compensation gravityTds.setTemperature(temperature); // Set current water temperature gravityTds.update(); // Take new measurement tdsValue = gravityTds.getTdsValue(); // Get calculated TDS value // Output to Serial Monitor Serial.print(tdsValue,0); // Print TDS value (no decimal places) Serial.println("ppm"); // Print units // Update OLED Display display.clearDisplay(); // Display TDS label display.setTextSize(2); display.setCursor(20,0); display.println("TDS Value"); // Display TDS reading display.setTextSize(3); display.setCursor(30,30); display.print(tdsValue); // Show numeric value display.display(); // Refresh OLED delay(1000); // Update every second } |
Downloads
Download TDS GravityTDS Code Library
Download DallasTemperature Code Library for DS18B20
Download TDS SENSOR / Water Quality Sensor Module Datasheet / Schematics
Download CD4060BM Datasheet
Download LMV324 Datasheet
Download TPS60400DBR Datasheet
Download ME6206A30M3G Datasheet