Difference between revisions of "TDS (Total Dissolved Solids) Meter Sensor SKU: CQRSENTDS01"

From CQRobot-Wiki
Jump to: navigation, search
(Raspberry Pi Example and Test Code)
(Raspberry Pi Application)
Line 258: Line 258:
 
----
 
----
  
=='''Raspberry Pi Application'''==
+
=='''Used in Conjunction with the 4-CH 16-Bit ADS1115 ADC Module (SKU: CQRADC001 / ASIN: B0GF12JQ8R)'''==
 +
[[image:CQRADC001-7.jpg|thumb|1000px| center]]
  
[[File:CQRSENTDS01-108.jpg|600px]]
+
*'''Sample Program'''
  
'''Download and Run the Test Examples'''
+
[[File:CQRADC001-80.jpg|700px]]
  
[[Media: TDS Meter Sensor-Raspberry Pi-New.zip]]
+
*Create a folder and a “.c” file, then write the code.
  
*1. Connect the module to the Raspberry Pi 4B, and put the test code on the Raspberry Pi system in the form of a folder. Dial the ADD DIP switch to the 0X48 end.
+
[[Media: ADSTDS.zip]]
  
*2. The Raspberry Pi I2C communication function is used in this experiment. It is necessary to turn on the I2C function, enter sudo raspi-config in the Raspberry Pi system, and then perform the following operations.
+
*You can directly add to the compressed file.
  
*3. '''Note''': When wiring, connect to A1 according to the tutorial.
+
[[File:CQRADC001-811.jpg|700px]]
  
[[File:CQRSENTDS01-102.jpg|500px]]
+
gcc -o ADSTDS ADSTDS.c -lwiringPi
  
[[File:CQRSENTDS01-103.jpg|500px]]
+
./ADSTDS
  
[[File:CQRSENTDS01-104.jpg|500px]]
+
Go to the folder created in the terminal, use GCC cross-compilation and run the file.
  
[[File:CQRSENTDS01-105.jpg|500px]]
+
*'''Example Code'''
  
[[File:CQRSENTDS01-106.jpg|500px]]
 
----
 
 
File: '''ADS1115_Read Voltage.py'''
 
<pre>
 
import sys
 
sys.path.append('../')
 
import time
 
from CQRobot_ADS1115 import ADS1115
 
ADS1115_REG_CONFIG_PGA_6_144V        = 0x00 # 6.144V range = Gain 2/3
 
ADS1115_REG_CONFIG_PGA_4_096V        = 0x02 # 4.096V range = Gain 1
 
ADS1115_REG_CONFIG_PGA_2_048V        = 0x04 # 2.048V range = Gain 2 (default)
 
ADS1115_REG_CONFIG_PGA_1_024V        = 0x06 # 1.024V range = Gain 4
 
ADS1115_REG_CONFIG_PGA_0_512V        = 0x08 # 0.512V range = Gain 8
 
ADS1115_REG_CONFIG_PGA_0_256V        = 0x0A # 0.256V range = Gain 16
 
ads1115 = ADS1115()
 
#Set the IIC address
 
ads1115.setAddr_ADS1115(0x48)
 
#Sets the gain and input voltage range.
 
ads1115.setGain(ADS1115_REG_CONFIG_PGA_6_144V)
 
 
VREF = 5.0
 
analogBuffer = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
 
analogBufferTemp = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
 
analogBufferIndex = 0
 
copyIndex = 0
 
averageVoltage = 0
 
tdsValue = 0
 
temperature = 25
 
 
def getMedianNum(iFilterLen):
 
global analogBufferTemp
 
bTemp = 0.0
 
for j in range(iFilterLen-1):
 
for i in range(iFilterLen-j-1):
 
if analogBufferTemp[i] > analogBufferTemp[i+1]:
 
bTemp = analogBufferTemp[i]
 
analogBufferTemp[i] = analogBufferTemp[i+1]
 
analogBufferTemp[i+1] = bTemp
 
if iFilterLen & 1 > 0:
 
bTemp = analogBufferTemp[(iFilterLen - 1)/2]
 
else:
 
bTemp = (analogBufferTemp[iFilterLen // 2] + analogBufferTemp[iFilterLen // 2 - 1]) / 2
 
return float(bTemp)
 
 
analogSampleTimepoint = time.time()
 
printTimepoint = time.time()
 
while True :
 
if time.time() - analogSampleTimepoint > 0.04:
 
#print(" test.......... ")
 
analogSampleTimepoint = time.time()
 
analogBuffer[analogBufferIndex] = ads1115.readVoltage(1)['r']
 
analogBufferIndex = analogBufferIndex + 1
 
if analogBufferIndex == 30:
 
analogBufferIndex = 0
 
 
if time.time()-printTimepoint > 0.8:
 
#print(" test ")
 
printTimepoint = time.time()
 
for copyIndex in range(30):
 
analogBufferTemp[copyIndex] = ads1115.readVoltage(1)['r']
 
print(" A1:%dmV "%getMedianNum(30))
 
averageVoltage = getMedianNum(30) * (VREF / 1024.0)
 
compensationCoefficient = 1.0 + 0.02 * (temperature - 25.0)
 
compensationVolatge = averageVoltage / compensationCoefficient
 
tdsValue = (133.42 * compensationVolatge * compensationVolatge * compensationVolatge - 255.86 * compensationVolatge * compensationVolatge + 857.39 * compensationVolatge) * 0.5
 
print(" A1:%dppm "%tdsValue)
 
 
#Get the Digital Value of Analog of selected channel
 
#adc1 = ads1115.readVoltage(1)
 
#time.sleep(0.2)
 
#print(" A1:%dmV "%(adc1['r']))
 
</pre>
 
----
 
 
File: '''CQRobot_ADS1115.py'''
 
 
<pre>
 
<pre>
import smbus
+
#include <stdio.h>
import time
+
#include <stdlib.h>
 +
#include <stdint.h>
 +
#include <unistd.h>
 +
#include <wiringPi.h>
 +
#include <wiringPiI2C.h>
 +
#include <math.h>
 +
#include <time.h>
  
# Get I2C bus
+
#define VREF 5.0f         
bus = smbus.SMBus(1)
+
#define SCOUNT 30          // Sampling points
 +
#define TEMPERATURE 25.0f  // Water temperature (Celsius)
  
# I2C address of the device
+
int analogBuffer[SCOUNT];       
ADS1115_IIC_ADDRESS0 = 0x48
+
int analogBufferTemp[SCOUNT];
ADS1115_IIC_ADDRESS1 = 0x49
+
int analogBufferIndex = 0;
 +
float averageVoltage = 0, tdsValue = 0;
  
# ADS1115 Register Map
+
int ads1115_fd;
ADS1115_REG_POINTER_CONVERT = 0x00 # Conversion register
+
// Function declarations
ADS1115_REG_POINTER_CONFIG = 0x01 # Configuration register
+
int getMedianNum(int bArray[], int iFilterLen);
ADS1115_REG_POINTER_LOWTHRESH = 0x02 # Lo_thresh register
+
int readADS1115();
ADS1115_REG_POINTER_HITHRESH = 0x03 # Hi_thresh register
+
unsigned long my_millis(); 
  
# ADS1115 Configuration Register
+
unsigned long my_millis() {
ADS1115_REG_CONFIG_OS_NOEFFECT = 0x00 # No effect
+
    struct timespec ts;
ADS1115_REG_CONFIG_OS_SINGLE = 0x80 # Begin a single conversion
+
    clock_gettime(CLOCK_MONOTONIC, &ts);
ADS1115_REG_CONFIG_MUX_DIFF_0_1 = 0x00 # Differential P = AIN0, N = AIN1 (default)
+
    return (ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
ADS1115_REG_CONFIG_MUX_DIFF_0_3 = 0x10 # Differential P = AIN0, N = AIN3
+
}
ADS1115_REG_CONFIG_MUX_DIFF_1_3 = 0x20 # Differential P = AIN1, N = AIN3
 
ADS1115_REG_CONFIG_MUX_DIFF_2_3 = 0x30 # Differential P = AIN2, N = AIN3
 
ADS1115_REG_CONFIG_MUX_SINGLE_0 = 0x40 # Single-ended P = AIN0, N = GND
 
ADS1115_REG_CONFIG_MUX_SINGLE_1 = 0x50 # Single-ended P = AIN1, N = GND
 
ADS1115_REG_CONFIG_MUX_SINGLE_2 = 0x60 # Single-ended P = AIN2, N = GND
 
ADS1115_REG_CONFIG_MUX_SINGLE_3 = 0x70 # Single-ended P = AIN3, N = GND
 
ADS1115_REG_CONFIG_PGA_6_144V = 0x00 # +/-6.144V range = Gain 2/3
 
ADS1115_REG_CONFIG_PGA_4_096V = 0x02 # +/-4.096V range = Gain 1
 
ADS1115_REG_CONFIG_PGA_2_048V = 0x04 # +/-2.048V range = Gain 2 (default)
 
ADS1115_REG_CONFIG_PGA_1_024V = 0x06 # +/-1.024V range = Gain 4
 
ADS1115_REG_CONFIG_PGA_0_512V = 0x08 # +/-0.512V range = Gain 8
 
ADS1115_REG_CONFIG_PGA_0_256V = 0x0A # +/-0.256V range = Gain 16
 
ADS1115_REG_CONFIG_MODE_CONTIN = 0x00 # Continuous conversion mode
 
ADS1115_REG_CONFIG_MODE_SINGLE = 0x01 # Power-down single-shot mode (default)
 
ADS1115_REG_CONFIG_DR_8SPS = 0x00 # 8 samples per second
 
ADS1115_REG_CONFIG_DR_16SPS = 0x20 # 16 samples per second
 
ADS1115_REG_CONFIG_DR_32SPS = 0x40 # 32 samples per second
 
ADS1115_REG_CONFIG_DR_64SPS = 0x60 # 64 samples per second
 
ADS1115_REG_CONFIG_DR_128SPS = 0x80 # 128 samples per second (default)
 
ADS1115_REG_CONFIG_DR_250SPS = 0xA0 # 250 samples per second
 
ADS1115_REG_CONFIG_DR_475SPS = 0xC0 # 475 samples per second
 
ADS1115_REG_CONFIG_DR_860SPS = 0xE0 # 860 samples per second
 
ADS1115_REG_CONFIG_CMODE_TRAD = 0x00 # Traditional comparator with hysteresis (default)
 
ADS1115_REG_CONFIG_CMODE_WINDOW = 0x10 # Window comparator
 
ADS1115_REG_CONFIG_CPOL_ACTVLOW = 0x00 # ALERT/RDY pin is low when active (default)
 
ADS1115_REG_CONFIG_CPOL_ACTVHI = 0x08 # ALERT/RDY pin is high when active
 
ADS1115_REG_CONFIG_CLAT_NONLAT = 0x00 # Non-latching comparator (default)
 
ADS1115_REG_CONFIG_CLAT_LATCH = 0x04 # Latching comparator
 
ADS1115_REG_CONFIG_CQUE_1CONV = 0x00 # Assert ALERT/RDY after one conversions
 
ADS1115_REG_CONFIG_CQUE_2CONV = 0x01 # Assert ALERT/RDY after two conversions
 
ADS1115_REG_CONFIG_CQUE_4CONV = 0x02 # Assert ALERT/RDY after four conversions
 
ADS1115_REG_CONFIG_CQUE_NONE = 0x03 # Disable the comparator and put ALERT/RDY in high state (default)
 
  
mygain=0x02
+
int getMedianNum(int bArray[], int iFilterLen) {
coefficient=0.125
+
    int bTab[iFilterLen];
addr_G=ADS1115_IIC_ADDRESS0
+
    for (int i = 0; i < iFilterLen; i++) {
class ADS1115():
+
        bTab[i] = bArray[i];
def setGain(self,gain):
+
    }
global mygain
+
    int i, j, bTemp;
global coefficient
+
    for (j = 0; j < iFilterLen - 1; j++) {
mygain=gain
+
        for (i = 0; i < iFilterLen - j - 1; i++) {
if mygain == ADS1115_REG_CONFIG_PGA_6_144V:
+
            if (bTab[i] > bTab[i + 1]) {
coefficient = 0.1875
+
                bTemp = bTab[i];
elif mygain == ADS1115_REG_CONFIG_PGA_4_096V:
+
                bTab[i] = bTab[i + 1];
coefficient = 0.125
+
                bTab[i + 1] = bTemp;
elif mygain == ADS1115_REG_CONFIG_PGA_2_048V:
+
            }
coefficient = 0.0625
+
        }
elif mygain == ADS1115_REG_CONFIG_PGA_1_024V:
+
    }
coefficient = 0.03125
+
   
elif mygain == ADS1115_REG_CONFIG_PGA_0_512V:
+
    if ((iFilterLen & 1) > 0) {
coefficient = 0.015625
+
        bTemp = bTab[(iFilterLen - 1) / 2];
elif  mygain == ADS1115_REG_CONFIG_PGA_0_256V:
+
    } else {
coefficient = 0.0078125
+
        bTemp = (bTab[iFilterLen / 2] + bTab[iFilterLen / 2 - 1]) / 2;
else:
+
    }
coefficient = 0.125
+
    return bTemp;
def setAddr_ADS1115(self,addr):
+
}
global addr_G
 
addr_G=addr
 
def setChannel(self,channel):
 
global mygain
 
"""Select the Channel user want to use from 0-3
 
For Single-ended Output
 
0 : AINP = AIN0 and AINN = GND
 
1 : AINP = AIN1 and AINN = GND
 
2 : AINP = AIN2 and AINN = GND
 
3 : AINP = AIN3 and AINN = GND
 
For Differential Output
 
0 : AINP = AIN0 and AINN = AIN1
 
1 : AINP = AIN0 and AINN = AIN3
 
2 : AINP = AIN1 and AINN = AIN3
 
3 : AINP = AIN2 and AINN = AIN3"""
 
self.channel = channel
 
while self.channel > 3 :
 
self.channel = 0
 
 
return self.channel
 
 
def setSingle(self):
 
global addr_G
 
if self.channel == 0:
 
CONFIG_REG = [ADS1115_REG_CONFIG_OS_SINGLE | ADS1115_REG_CONFIG_MUX_SINGLE_0 | mygain | ADS1115_REG_CONFIG_MODE_CONTIN, ADS1115_REG_CONFIG_DR_128SPS | ADS1115_REG_CONFIG_CQUE_NONE]
 
elif self.channel == 1:
 
CONFIG_REG = [ADS1115_REG_CONFIG_OS_SINGLE | ADS1115_REG_CONFIG_MUX_SINGLE_1 | mygain | ADS1115_REG_CONFIG_MODE_CONTIN, ADS1115_REG_CONFIG_DR_128SPS | ADS1115_REG_CONFIG_CQUE_NONE]
 
elif self.channel == 2:
 
CONFIG_REG = [ADS1115_REG_CONFIG_OS_SINGLE | ADS1115_REG_CONFIG_MUX_SINGLE_2 | mygain | ADS1115_REG_CONFIG_MODE_CONTIN, ADS1115_REG_CONFIG_DR_128SPS | ADS1115_REG_CONFIG_CQUE_NONE]
 
elif self.channel == 3:
 
CONFIG_REG = [ADS1115_REG_CONFIG_OS_SINGLE | ADS1115_REG_CONFIG_MUX_SINGLE_3 | mygain | ADS1115_REG_CONFIG_MODE_CONTIN, ADS1115_REG_CONFIG_DR_128SPS | ADS1115_REG_CONFIG_CQUE_NONE]
 
 
bus.write_i2c_block_data(addr_G, ADS1115_REG_POINTER_CONFIG, CONFIG_REG)
 
 
def setDifferential(self):
 
global addr_G
 
if self.channel == 0:
 
CONFIG_REG = [ADS1115_REG_CONFIG_OS_SINGLE | ADS1115_REG_CONFIG_MUX_DIFF_0_1 | mygain | ADS1115_REG_CONFIG_MODE_CONTIN, ADS1115_REG_CONFIG_DR_128SPS | ADS1115_REG_CONFIG_CQUE_NONE]
 
elif self.channel == 1:
 
CONFIG_REG = [ADS1115_REG_CONFIG_OS_SINGLE | ADS1115_REG_CONFIG_MUX_DIFF_0_3 | mygain | ADS1115_REG_CONFIG_MODE_CONTIN, ADS1115_REG_CONFIG_DR_128SPS | ADS1115_REG_CONFIG_CQUE_NONE]
 
elif self.channel == 2:
 
CONFIG_REG = [ADS1115_REG_CONFIG_OS_SINGLE | ADS1115_REG_CONFIG_MUX_DIFF_1_3 | mygain | ADS1115_REG_CONFIG_MODE_CONTIN, ADS1115_REG_CONFIG_DR_128SPS | ADS1115_REG_CONFIG_CQUE_NONE]
 
elif self.channel == 3:
 
CONFIG_REG = [ADS1115_REG_CONFIG_OS_SINGLE | ADS1115_REG_CONFIG_MUX_DIFF_2_3 | mygain | ADS1115_REG_CONFIG_MODE_CONTIN, ADS1115_REG_CONFIG_DR_128SPS | ADS1115_REG_CONFIG_CQUE_NONE]
 
 
bus.write_i2c_block_data(addr_G, ADS1115_REG_POINTER_CONFIG, CONFIG_REG)
 
 
def readValue(self):
 
"""Read data back from ADS1115_REG_POINTER_CONVERT(0x00), 2 bytes
 
raw_adc MSB, raw_adc LSB"""
 
global coefficient
 
global addr_G
 
data = bus.read_i2c_block_data(addr_G, ADS1115_REG_POINTER_CONVERT, 2)
 
 
# Convert the data
 
raw_adc = data[0] * 256 + data[1]
 
 
if raw_adc > 32767:
 
raw_adc -= 65535
 
raw_adc = int(float(raw_adc)*coefficient)
 
return {'r' : raw_adc}
 
  
def readVoltage(self,channel):
+
int readADS1115() {
self.setChannel(channel)
+
    // Configure ADS1115: Channel 0
self.setSingle()
+
    uint16_t config = 0xC283; 
time.sleep(0.1)
+
    wiringPiI2CWriteReg16(ads1115_fd, 0x01, (config >> 8) | (config << 8));
return self.readValue()
+
    usleep(8000); 
 
+
   
def ComparatorVoltage(self,channel):
+
    uint16_t result = wiringPiI2CReadReg16(ads1115_fd, 0x00);
self.setChannel(channel)
+
    int16_t adc = ((result >> 8) & 0xFF) | ((result & 0xFF) << 8);
self.setDifferential()
+
   
time.sleep(0.1)
+
return self.readValue()
+
    static int debug_count = 0;
</pre>
+
    if (debug_count++ % 20 == 0) {
----
+
        printf("[Raw ADC: %d] ", adc);
 
+
    }
'''Run the code'''
+
   
 
+
    int adc_abs = abs(adc);
Note: Make sure that the I2C address from its default (0x48);
+
    // Convert to analog value (0-1023)
 
+
    int arduino_value = (adc_abs * 1024) / 30000;
Note: Enable "Interfacing Options - P5 I2C" by typing the below command in the terminal:
+
    // Limit to 0-1023 range
 
+
    if (arduino_value > 1023) arduino_value = 1023;
<pre>
+
    if (arduino_value < 0) arduino_value = 0;
sudo raspi-config
+
    return arduino_value;
</pre>
+
}
 
+
int main() {
Run the code:
+
    printf("=== Raspberry Pi TDS Measurement ===\n");
<pre>
+
    printf("TDS = 133.42*V^3 - 255.86*V^2 + 857.39*V * 0.5\n\n");
cd TS1728
+
   
ls
+
    // Initialize wiringPi
sudo python3 CQRobot_ADS1115.py
+
    if (wiringPiSetup() < 0) {
cd ADS1115_ReadVoltage
+
        printf("wiringPi initialization failed\n");
ls
+
        return 1;
sudo python3 ADS1115_ReadVoltage.py
+
    }
 +
   
 +
    // Initialize ADS1115
 +
    ads1115_fd = wiringPiI2CSetup(0x48);
 +
    if (ads1115_fd < 0) {
 +
        printf("ADS1115 initialization failed (address 0x48)\n");
 +
        printf("Please check: sudo i2cdetect -y 1\n");
 +
        return 1;
 +
    }
 +
    printf("✓ Hardware initialization completed\n");
 +
    printf("Starting measurement...\n");
 +
    printf("----------------------------------------\n");
 +
   
 +
    // Timepoint variables (using custom my_millis)
 +
    unsigned long analogSampleTimepoint = my_millis();
 +
    unsigned long printTimepoint = my_millis();
 +
   
 +
    while (1) {
 +
        unsigned long currentMillis = my_millis();   
 +
        if (currentMillis - analogSampleTimepoint > 40U) {
 +
            analogSampleTimepoint = currentMillis;
 +
           
 +
            int arduinoValue = readADS1115();
 +
           
 +
            analogBuffer[analogBufferIndex] = arduinoValue;
 +
            analogBufferIndex++;
 +
            if (analogBufferIndex == SCOUNT) {
 +
                analogBufferIndex = 0;
 +
            }
 +
        }
 +
       
 +
        if (currentMillis - printTimepoint > 800U) {
 +
            printTimepoint = currentMillis;
 +
         
 +
            for (int copyIndex = 0; copyIndex < SCOUNT; copyIndex++) {
 +
                analogBufferTemp[copyIndex] = analogBuffer[copyIndex];
 +
            }
 +
 
 +
            int medianValue = getMedianNum(analogBufferTemp, SCOUNT);
 +
            averageVoltage = medianValue * VREF / 1024.0;
 +
            float compensationCoefficient = 1.0 + 0.02 * (TEMPERATURE - 25.0);
 +
            float compensationVoltage = averageVoltage / compensationCoefficient;
 +
            tdsValue = (133.42 * compensationVoltage * compensationVoltage * compensationVoltage
 +
                      - 255.86 * compensationVoltage * compensationVoltage
 +
                      + 857.39 * compensationVoltage) * 0.5;
 +
            printf("TDS Value: %.0fppm", tdsValue);
 +
         
 +
            printf(" (ADC:%d, Volt:%.3fV)\n", medianValue, averageVoltage);
 +
           
 +
            fflush(stdout);
 +
        }
 +
       
 +
        usleep(1000); 
 +
    }
 +
   
 +
    return 0;
 +
}
 
</pre>
 
</pre>
  
[[File:TDS Meter Sensor-Raspberry Pi-Run the code.jpg]]
+
*Test Results:
----
 
  
'''Run the Video'''
+
[[File:CQRADC001-821.jpg|700px]]
  
[[Media: TDS Meter Sensor-Raspberry Pi-Run the video.mp4]]
+
The original ADC value represents the raw 16-bit analog-to-digital conversion data from the ADS1115. The ADC value is the stabilized output after median filtering. The voltage value is the calculated corresponding voltage. The TDS value indicates the dissolved solids content in the water.  
  
 +
In this configuration, the I2C address is 0x48, and the analog port is AIN0.
 
----
 
----

Revision as of 06:41, 6 March 2026

100px TDS Meter Sensor

Description

TDS (Total Dissolved Solids), Chinese name Total Dissolved Solids, also known as Total Dissolved Solids, indicates how many milligrams of dissolved solids are dissolved in 1 liter of water. Generally speaking, the higher the TDS value, the more dissolved substances contained in the water and the less clean the water. Therefore, the value of TDS can be used as one of the basis for reflecting the cleanliness of water. The commonly used TDS testing equipment is a TDS pen. Although it is cheap and easy to use, it cannot transmit data to the control system for long-term online monitoring and water quality analysis. Using a special instrument, although it can transmit data and has high accuracy, it is very expensive.

CQRobot has launched an arduino-compatible TDS sensor, which can be used to measure the TDS value of water after connecting to the arduino controller. This product is specially designed for arduino, plug and play, easy to use. 3.3V~5.5V wide voltage power supply, 0 to 2.3V analog signal output, making this product compatible with 5V and 3.3V control systems, it can be easily connected to existing control systems. With CQRobot ADS1115 16-bit AD conversion module (Model: CQRADS1115), it can accurately collect and convert analog signals to meet the compatibility requirements of the Raspberry Pi motherboard.

The excitation source for measurement uses AC signals, which can effectively prevent probe polarization, extend the life of the probe, and increase the stability of the output signal. The TDS probe is a waterproof probe that can be immersed in water for long-term measurement. This product can be applied to water quality testing in areas such as domestic water and hydroponics. With this sensor, you can easily DIY a TDS detector, easily detect the cleanliness of the water, and check your water quality.

TDS Meter Sensor-7B.jpg

Size Display

TDS Meter Sensor-7C1.jpg

Specifications

Signal Transmitter Board Specifications

  • Input Voltage: 3.3V to 5.5V
  • Output Voltage: 0 to 2.3V
  • Working Current: 3mA to 6mA
  • TDS Measurement Range: 0 to 1000ppm
  • TDS Measurement Accuracy: Plus/Minus 10% F.S. (25 Degree Celsius)
  • Module Size: 43mm * 32.2mm
  • Module Interface: JST 2.0mm 3-Pin
  • Electrode Interface: JST 2.54mm 2-Pin

TDS Probe Specifications

  • Number of Needle: 2
  • Total Length: 60cm
  • Connection Interface: JTS 2.54mm 2-Pin
  • Color: Black
  • Other: Waterproof Probe

Ocean Interface Cable Specifications

  • Cable specifications: 22AWG
  • Material: Silicone
  • Withstand Voltage: Less Than 50V
  • Withstand Current: Less Than 1000MA
  • Length: 21cm
  • Line Sequence: Black-Negative Power Supply, Red-Positive Power Supply, Green-Signal Terminal.

Connections and Examples

Attention

  • The probe can not be used in water above 55 degrees centigrade.
  • The probe can not be left too close to the edge of the container, otherwise it will affect the reading.
  • The head and the cable of the probe are waterproof, but the connector and the signal transmitter board are not waterproof. Please be careful.
  • When using the TDS instrument sensor, the sensor probe is immersed in liquid, and the purchaser needs to Separate the sensor Probe Protective Cover to avoid incorrect data reading and malfunction.
TDS Meter Sensor-7D.jpg
  • The TDS probe cannot be used in water above 55 degrees Celsius;
  • The TDS probe should not be placed too close to the edge of the container, otherwise it will affect the sensor reading parameters;
  • The TDS probe head and lead are made of waterproof material and can be immersed in water, but the cable interface and signal transfer board are not waterproof, please use them carefully.

Arduino Connections

  • The DuPont female single-head wiring we distribute cannot be directly connected to the UNO R3 control board. Need to be in Stack the sensor expansion board on the UNO R3 control board, or connect the male-to-male Dupont wire to the Dupont female single-head wiring(Bread Wire).

Raspberry Pi Test Code

  • Compatible Raspberry Pi needs to be used with ADS1115 16-bit AD conversion module. Please refer to the specific test code: http://www.cqrobot.wiki/index.php/ADS1115_16-Bit_ADC_Module_SKU:_CQRADS1115
  • Connect the module to the Raspberry Pi 4B, and put the test code in the Raspberry Pi system in the form of a folder. Dial the ADD dial switch to the 0X48 end.
  • In this experiment, the Raspberry Pi I2C communication function is used. You need to turn on the I2C function, enter sudo raspi-config in the Raspberry Pi system, and then perform the following operations.
  • This kit does not include CQRobot ADS1115 16-bit AD conversion module (Model: CQRADS1115), buyers need to purchase it separately.
TDS Meter Sensor-7E.jpg

Arduino Application

Note: The DuPont female single-ended wiring we distribute cannot be directly connected to the UNO R3 control board. When wiring, you need to stack the sensor expansion board on the UNO R3 control board, or connect the male-to-male Dupont wire (bread wire) on the Dupont wire female single-ended wire.

CQRSENTDS01-107.jpg

Download and Run the Test Examples

Media: TDS Meter Sensor-Arduino.rar

#define TdsSensorPin A1
#define VREF 5.0      // analog reference voltage(Volt) of the ADC
#define SCOUNT  30           // sum of sample point
int analogBuffer[SCOUNT];    // store the analog value in the array, read from ADC
int analogBufferTemp[SCOUNT];
int analogBufferIndex = 0, copyIndex = 0;
float averageVoltage = 0, tdsValue = 0, temperature = 25;

void setup()
{
  Serial.begin(115200);
  pinMode(TdsSensorPin, INPUT);
}

void loop()
{
  static unsigned long analogSampleTimepoint = millis();
  if (millis() - analogSampleTimepoint > 40U)  //every 40 milliseconds,read the analog value from the ADC
  {
    analogSampleTimepoint = millis();
    analogBuffer[analogBufferIndex] = analogRead(TdsSensorPin);    //read the analog value and store into the buffer
    analogBufferIndex++;
    if (analogBufferIndex == SCOUNT)
      analogBufferIndex = 0;
  }
  static unsigned long printTimepoint = millis();
  if (millis() - printTimepoint > 800U)
  {
    printTimepoint = millis();
    for (copyIndex = 0; copyIndex < SCOUNT; copyIndex++)
      analogBufferTemp[copyIndex] = analogBuffer[copyIndex];
    averageVoltage = getMedianNum(analogBufferTemp, SCOUNT) * (float)VREF / 1024.0; // read the analog value more stable by the median filtering algorithm, and convert to voltage value
    float compensationCoefficient = 1.0 + 0.02 * (temperature - 25.0); //temperature compensation formula: fFinalResult(25^C) = fFinalResult(current)/(1.0+0.02*(fTP-25.0));
    float compensationVolatge = averageVoltage / compensationCoefficient; //temperature compensation
    tdsValue = (133.42 * compensationVolatge * compensationVolatge * compensationVolatge - 255.86 * compensationVolatge * compensationVolatge + 857.39 * compensationVolatge) * 0.5; //convert voltage value to tds value
    //Serial.print("voltage:");
    //Serial.print(averageVoltage,2);
    //Serial.print("V   ");
    Serial.print("TDS----Value:");
    Serial.print(tdsValue, 0);
    Serial.println("ppm");
  }
}
int getMedianNum(int bArray[], int iFilterLen)
{
  int bTab[iFilterLen];
  for (byte i = 0; i < iFilterLen; i++)
    bTab[i] = bArray[i];
  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;
      }
    }
  }
  if ((iFilterLen & 1) > 0)
    bTemp = bTab[(iFilterLen - 1) / 2];
  else
    bTemp = (bTab[iFilterLen / 2] + bTab[iFilterLen / 2 - 1]) / 2;
  return bTemp;
}

Test Results

Connect according to the wiring diagram, upload the code on the arduino board, after power-on, open the serial monitor, set the baud rate to 115200, insert the TDS probe into the water to be measured, stir a few times, and observe the value printed on the serial monitor, This value is the TDS value of water. As shown below.

CQRSENTDS01-101.jpg


TDS Meter Sensor vs. Xiaomi Water Quality Test Pen

This is a sample of tap water from the urban pipe network.

TDS value of Xiaomi's test pen: 84 ppm

TDS value of our sensor: 92 ppm


The test result is only responsible for the sample, but you can serve as a reference.

CQRSENTDS01-X22.jpg
CQRSENTDS01-X3.jpg
CQRSENTDS01-X4.jpg
CQRSENTDS01-X5.jpg

Raspberry Pi Example and Test Code

CQRADC001-76.jpg

CQRADC001-73.jpg

Raspberry Pi 4B Pinout Diagram

CQRADC001-75.jpg

CQRADC001-77.jpg

  • Create a folder and a “.c” file, then write the code.

Media: ADS1115.zip

  • You can directly add to the compressed file.

CQRADC001-781.jpg


gcc -Wall -g -o ADS1115 ADS1115.c -lm

./ADS1115

Go to the folder created in the terminal, use GCC cross-compilation and run the file.

  • Sample Code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>
#include <math.h>

#define ADS1115_ADDR 0x48

int main() {
    int fd = open("/dev/i2c-1", O_RDWR);
    if (fd < 0) return 1;
    if (ioctl(fd, I2C_SLAVE, ADS1115_ADDR) < 0) return 1;
    
    while(1) {
        unsigned char config[3] = {0x01, 0xC2, 0x83};
        unsigned char result[2];
        
        write(fd, config, 3);
        usleep(8000);
        
        unsigned char reg[1] = {0x00};
        write(fd, reg, 1);
        read(fd, result, 2);
        
        int16_t adc = (result[0] << 8) | result[1];
        float voltage = adc * 0.125 / 1000.0;
        float ntc_r = (3.3 * 10000.0 / voltage) - 20000.0;
        float temp = 1.0 / (log(ntc_r / 10000.0) / 3950.0 + 1.0 / 298.15) - 273.15;
        
        printf("Temp: %.1fC\n", temp);
        sleep(1);
    }
    
    return 0;
}
  • Test Results:

CQRADC001-791.jpg

  • Temporarily set the I2C address to 0x48. You can modify this line of code as needed: #define ADS1115_ADDR 0x48.
  • Temporarily set the input channel to AIN0. You can replace it with the following code based on actual needs:

AIN0: 0xC2

AIN1: 0xD2

AIN2: 0xE2

AIN3: 0xF2


Used in Conjunction with the 4-CH 16-Bit ADS1115 ADC Module (SKU: CQRADC001 / ASIN: B0GF12JQ8R)

CQRADC001-7.jpg
  • Sample Program

CQRADC001-80.jpg

  • Create a folder and a “.c” file, then write the code.

Media: ADSTDS.zip

  • You can directly add to the compressed file.

CQRADC001-811.jpg

gcc -o ADSTDS ADSTDS.c -lwiringPi

./ADSTDS

Go to the folder created in the terminal, use GCC cross-compilation and run the file.

  • Example Code
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <wiringPi.h>
#include <wiringPiI2C.h>
#include <math.h>
#include <time.h>

#define VREF 5.0f           
#define SCOUNT 30           // Sampling points
#define TEMPERATURE 25.0f   // Water temperature (Celsius)

int analogBuffer[SCOUNT];        
int analogBufferTemp[SCOUNT];
int analogBufferIndex = 0;
float averageVoltage = 0, tdsValue = 0;

int ads1115_fd;
// Function declarations
int getMedianNum(int bArray[], int iFilterLen);
int readADS1115();
unsigned long my_millis();  

unsigned long my_millis() {
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return (ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
}

int getMedianNum(int bArray[], int iFilterLen) {
    int bTab[iFilterLen];
    for (int i = 0; i < iFilterLen; i++) {
        bTab[i] = bArray[i];
    }
    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;
            }
        }
    }
    
    if ((iFilterLen & 1) > 0) {
        bTemp = bTab[(iFilterLen - 1) / 2];
    } else {
        bTemp = (bTab[iFilterLen / 2] + bTab[iFilterLen / 2 - 1]) / 2;
    }
    return bTemp;
}

int readADS1115() {
    // Configure ADS1115: Channel 0
    uint16_t config = 0xC283;  
    wiringPiI2CWriteReg16(ads1115_fd, 0x01, (config >> 8) | (config << 8));
    usleep(8000);  
    
    uint16_t result = wiringPiI2CReadReg16(ads1115_fd, 0x00);
    int16_t adc = ((result >> 8) & 0xFF) | ((result & 0xFF) << 8);
    
 
    static int debug_count = 0;
    if (debug_count++ % 20 == 0) {
        printf("[Raw ADC: %d] ", adc);
    }
    
    int adc_abs = abs(adc);
    // Convert to analog value (0-1023)
    int arduino_value = (adc_abs * 1024) / 30000;
    // Limit to 0-1023 range
    if (arduino_value > 1023) arduino_value = 1023;
    if (arduino_value < 0) arduino_value = 0;
    return arduino_value;
}
int main() {
    printf("=== Raspberry Pi TDS Measurement ===\n");
    printf("TDS = 133.42*V^3 - 255.86*V^2 + 857.39*V * 0.5\n\n");
    
    // Initialize wiringPi
    if (wiringPiSetup() < 0) {
        printf("wiringPi initialization failed\n");
        return 1;
    }
    
    // Initialize ADS1115
    ads1115_fd = wiringPiI2CSetup(0x48);
    if (ads1115_fd < 0) {
        printf("ADS1115 initialization failed (address 0x48)\n");
        printf("Please check: sudo i2cdetect -y 1\n");
        return 1;
    }
    printf("✓ Hardware initialization completed\n");
    printf("Starting measurement...\n");
    printf("----------------------------------------\n");
    
    // Timepoint variables (using custom my_millis)
    unsigned long analogSampleTimepoint = my_millis();
    unsigned long printTimepoint = my_millis();
    
    while (1) {
        unsigned long currentMillis = my_millis();     
        if (currentMillis - analogSampleTimepoint > 40U) {
            analogSampleTimepoint = currentMillis;
            
            int arduinoValue = readADS1115();
            
            analogBuffer[analogBufferIndex] = arduinoValue;
            analogBufferIndex++;
            if (analogBufferIndex == SCOUNT) {
                analogBufferIndex = 0;
            }
        }
         
        if (currentMillis - printTimepoint > 800U) {
            printTimepoint = currentMillis;
           
            for (int copyIndex = 0; copyIndex < SCOUNT; copyIndex++) {
                analogBufferTemp[copyIndex] = analogBuffer[copyIndex];
            }
   
            int medianValue = getMedianNum(analogBufferTemp, SCOUNT);
            averageVoltage = medianValue * VREF / 1024.0;
            float compensationCoefficient = 1.0 + 0.02 * (TEMPERATURE - 25.0);
            float compensationVoltage = averageVoltage / compensationCoefficient;
            tdsValue = (133.42 * compensationVoltage * compensationVoltage * compensationVoltage 
                       - 255.86 * compensationVoltage * compensationVoltage 
                       + 857.39 * compensationVoltage) * 0.5;
            printf("TDS Value: %.0fppm", tdsValue);
           
            printf(" (ADC:%d, Volt:%.3fV)\n", medianValue, averageVoltage);
            
            fflush(stdout);
        }
        
        usleep(1000);  
    }
    
    return 0;
}
  • Test Results:

CQRADC001-821.jpg

The original ADC value represents the raw 16-bit analog-to-digital conversion data from the ADS1115. The ADC value is the stabilized output after median filtering. The voltage value is the calculated corresponding voltage. The TDS value indicates the dissolved solids content in the water.

In this configuration, the I2C address is 0x48, and the analog port is AIN0.