IOT

How to design an Aquarium Water Quality Monitor with TDS Sensor & ESP32?

In this post, we’ll look at how to use a TDS sensor, a temperature sensor, an ESP32 WIFI module, and a TFT LCD display to monitor aquarium water quality. An aquarium is a container for keeping freshwater or marine aquatic species, or a facility where a collection of aquatic organisms is shown or researched.

In a Smart Aquarium, water quality monitoring, automatic water change, and feeding are all necessary. Water quality monitoring is the most demanding task, as it needs the use of sensors and technological equipment. Water quality measurements include pH, TDS, Turbidity, Dissolved Oxygen, Temperature, and Electrical Conductivity. Water Temperature and TDS are the most important characteristics for aquariums and aquatic life, including fish. 

Hardware  Required

  • ESP32 + LCD Display
  • Gravity Analog TDS Sensor
  • DS18B20 Waterproof Temperature Sensor
  • Resistor
  • Connecting Wires
  • Breadboard

Proposed System

Monitoring the water quality, as well as automatic water change and feeding, are all required in a Smart Aquarium. The most difficult duty is water quality monitoring, which necessitates the use of sensors and technological equipment. Water pH, TDS, Turbidity, Dissolved Oxygen, Temperature, and Electrical Conductivity are some of the water quality metrics. The most crucial parameters for aquariums and aquatic life, including fish, are Water Temperature and TDS. The ideal temperature range for an aquarium is 76° to 80°F (25° to 27°C). The TDS levels in the water should be between 400 and 450 ppm for most freshwater fish.

We’ll use a TDS sensor and an ESP32 to create our own aquarium water quality monitor system in this project. On the TFT Color LCD Display, we will show the real-time value of Water TDS and Temperature. TDS Sensor and DS18B20 Waterproof Temperature Sensor information is previously accessible in earlier blogs. The finest part of the project is the utilization of a TFT LCD Display with an ESP32 built in it. Makerfabs created the customized Display.

What is TDS & How does it affect Fish Life?

The TDS (Total Dissolved Solids) measurement represents the total amount of dissolved solids in water. Salts, minerals, and conductive metal ions are examples of these solids. The conductivity of water is another name for this number. Because the larger the concentration of such particles or ions in water, the better it conducts electricity.

TDS meters measure conductivity in micro siemens or parts per million. Parts per million, or the quantity of solid particles per million water mixture particles, is the latter. The number of 40 ppm indicates that out of a million particles, 40 are dissolved ions, while the rest (= 999 960) are water molecules.

How Does TDS Affect Fish & other Aquatic life?

The fish require a stable environment with the same TDS and PH levels as their natural habitat in aquariums or tanks. Varying fish require different TDS levels in their water. TDS levels of 400 to 450 ppm in the water are suggested for most freshwater fish.

If the concentrations are too high, fish will die and an algae bloom will occur in large quantities. Fish growth will be hampered by low TDS levels in the water. To put it another way, if the TDS rises over average, you may need to assist the little fish.

Gravity TDS Sensor

The Gravity Analog TDS Sensor is an Arduino-compatible TDS sensor/meter kit for determining water TDS levels. It can be used to test the quality of residential water, hydroponics, and other types of water. This product accepts 3.3 5.5V wide voltage input and outputs 0 2.3V analog voltage, making it suitable for use with 5V or 3.3V control systems or boards.

TDS Sensor

The probe’s excitation source is an AC signal, which can effectively prevent polarisation and extend the probe’s life while also increasing the output signal’s stability. The TDS probe is waterproof and may be submerged in water for extended periods of time.

The sensor has a TDS measurement range of 0 to 1000ppm and a 10 percent FS (25 °C) accuracy. The probe cannot be used in water that is warmer than 55 degrees Celsius.

DS18B20 Waterproof Temperature Sensor

The DS18B20 Sensor is a pre-wired and waterproofed variant that can be used to measure anything far away or in rainy situations. The sensor can detect temperatures ranging from -55 to 125 degrees Celsius (-67 degrees Fahrenheit to +257 degrees Fahrenheit). The cable is PVC jacketed. These 1-wire digital temperature sensors are fairly accurate, with a range of 0.5°C. They’re compatible with any microcontroller that has a single digital pin.

DS18B20 Temperature Sensor

Two libraries are required for the sensor: Dallas Temperature Sensor Library and One-Wire Library. When using the sensor, it additionally requires a 4.7k resistor to act as a pullup from the DATA to the VCC line.

Interfacing TDS & Temperature Sensor with ESP32

Let’s connect the Gravity TDS Sensor and the DS18B20 Temperature Sensor to the ESP32 Board now. Because the temperature parameter is necessary during TDS adjustment, we are employing a temperature sensor. The TDS readings alter dramatically when the temperature rises and falls.

The project’s simple connection schematic is shown below.

Connect the VCC and GND pins of the TDS and Temperature Sensor to the ESP32 3.3V and GND pins, respectively. Connect the TDS Sensor’s output analog pin to the ESP32 IO35 pin. Connect the DS18B20 output to the ESP32 IO25 Pin in the same way. A 4.7K pull resistor is required, as well as a parasitic power connection between the DS18B20 output pin and 3.3V VCC.

TDS Temperature Sensor

Assemble the components on a breadboard for testing purposes.

Source Code/Program

Here is a simple test code to see if the TDS Sensor and Temperature Sensor operate with the ESP32. The DS18B20 temperature sensor requires a One-wire Library as well as Dallas Library.

These libraries should be downloaded and placed in the Arduino IDE library folder. You can now upload the code below to the ESP32 board.

#include <OneWire.h>
#include <DallasTemperature.h>

const int oneWireBus = 25; // GPIO where the DS18B20 is connected to

#define TdsSensorPin 35
#define VREF 3.3 // 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;
int copyIndex = 0;
float averageVoltage = 0;
float tdsValue = 0;
float temperature = 0;


OneWire oneWire(oneWireBus); // Setup a oneWire instance to communicate with any OneWire devices

DallasTemperature sensors(&oneWire); // Pass our oneWire reference to Dallas Temperature sensor

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

void loop()
{
sensors.requestTemperatures();
float temperature = sensors.getTempCByIndex(0);

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(“TDS Value:”);
Serial.print(tdsValue, 0);
Serial.println(“ppm”);

Serial.print(“Temperature:”);
Serial.print(temperature);
Serial.println(“ºC”);
}
}
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;
}

Open the Serial Monitor after you’ve uploaded the code. Dip the TDS and Temperature Sensor probes in a sample of water as well.

Keep an eye on the Serial Monitor; it will begin to report temperature and TDS readings.

Aquarium Water Quality Monitor with TDS Sensor & ESP32

Let’s complete the project by adding a display and creating a display-based portable Water TDS meter. One of the greatest displays available, including ESP32 and ILI9341 3.5″ TFT Touch Display.

The display can be quite handy for keeping an eye on fish and other aquatic life in an aquarium. The project’s connection diagram is again straightforward, as shown.

TDS Sensor TFT LCD Display ESP32

The connection for the GPIO Pins stills remains the same.

Source Code/Program

Let’s utilize an ESP32 with an LCD screen, as well as a TDS and temperature sensor. The temperature and TDS level will be displayed in gauge style on the LCD screen.

  • The code below uses two files, one of which is an .ino file and the other a .h file.
  • Copy and paste the following code into your Arduino IDE, then save it as an .ino file.
#include <OneWire.h>
#include <DallasTemperature.h>
#include “alert.h” // Out of range alert icon
#include <TFT_eSPI.h> // Hardware-specific library
#include <SPI.h>

const int oneWireBus = 25; // GPIO where the DS18B20 is connected to

#define TdsSensorPin 35
#define VREF 3.3 // analog reference voltage(Volt) of the ADC
#define SCOUNT 30 // sum of sample point

#define RED2RED 0
#define GREEN2GREEN 1
#define BLUE2BLUE 2
#define BLUE2RED 3
#define GREEN2RED 4
#define RED2GREEN 5


#define TFT_GREY 0x2104 // Dark grey 16 bit colour

TFT_eSPI tft = TFT_eSPI(); // Invoke custom library with default width and height

uint32_t runTime = -99999; // time for next update

int analogBuffer[SCOUNT]; // store the analog value in the array, read from ADC
int analogBufferTemp[SCOUNT];
int analogBufferIndex = 0;
int copyIndex = 0;
float averageVoltage = 0;
float tdsValue = 0;
float temperature = 0;

int reading = 0; // Value to be displayed
int d = 0; // Variable used for the sinewave test waveform
boolean range_error = 0;
int8_t ramp = 1;

OneWire oneWire(oneWireBus); // Setup a oneWire instance to communicate with any OneWire devices

DallasTemperature sensors(&oneWire); // Pass our oneWire reference to Dallas Temperature sensor

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

tft.begin();
tft.setRotation(1);
tft.fillScreen(TFT_BLACK);

tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.drawString(“Aquarium Monitoring”, 120, 10, 4);
//tft.drawString(“Weather”, 350, 10, 2);

tft.drawString(“TDS Value”, 80, 270, 2);
tft.drawString(“Temperature”, 320, 270, 2);
}

void loop()
{
tdstemp_read();

if (millis() – runTime >= 0L)
{ // Execute every TBD ms
runTime = millis();

// Test with a slowly changing value from a Sine function
d += 4;
if (d >= 360)
d = 0;

// Set the the position, gap between meters, and inner radius of the meters
int xpos = 0, ypos = 5, gap = 4, radius = 52;

xpos = 60, ypos = 90, gap = 50, radius = 80;

//reading = 800 + 150 * sineWave(d + 90);
xpos = gap + ringMeter(tdsValue, 0, 1000, xpos, ypos, radius, “ppm”, BLUE2RED); // Draw analogue meter

//reading = 15 + 15 * sineWave(d + 150);
xpos = gap + ringMeter(temperature, -20, 50, xpos, ypos, radius, “C”, GREEN2GREEN); // Draw analogue meter
}
}

// #########################################################################
// Draw the meter on the screen, returns x coord of righthand side
// #########################################################################
int ringMeter(int value, int vmin, int vmax, int x, int y, int r, const char *units, byte scheme)
{
// Minimum value of r is about 52 before value text intrudes on ring
// drawing the text first is an option

x += r;
y += r; // Calculate coords of centre of ring

int w = r / 3; // Width of outer ring is 1/4 of radius

int angle = 150; // Half the sweep angle of meter (300 degrees)

int v = map(value, vmin, vmax, -angle, angle); // Map the value to an angle v

byte seg = 3; // Segments are 3 degrees wide = 100 segments for 300 degrees
byte inc = 6; // Draw segments every 3 degrees, increase to 6 for segmented ring

// Variable to save “value” text colour from scheme and set default
int colour = TFT_BLUE;

// Draw colour blocks every inc degrees
for (int i = -angle + inc / 2; i < angle – inc / 2; i += inc)
{
// Calculate pair of coordinates for segment start
float sx = cos((i – 90) * 0.0174532925);
float sy = sin((i – 90) * 0.0174532925);
uint16_t x0 = sx * (r – w) + x;
uint16_t y0 = sy * (r – w) + y;
uint16_t x1 = sx * r + x;
uint16_t y1 = sy * r + y;

// Calculate pair of coordinates for segment end
float sx2 = cos((i + seg – 90) * 0.0174532925);
float sy2 = sin((i + seg – 90) * 0.0174532925);
int x2 = sx2 * (r – w) + x;
int y2 = sy2 * (r – w) + y;
int x3 = sx2 * r + x;
int y3 = sy2 * r + y;

if (i < v)
{ // Fill in coloured segments with 2 triangles
switch (scheme)
{
case 0:
colour = TFT_RED;
break; // Fixed colour
case 1:
colour = TFT_GREEN;
break; // Fixed colour
case 2:
colour = TFT_BLUE;
break; // Fixed colour
case 3:
colour = rainbow(map(i, -angle, angle, 0, 127));
break; // Full spectrum blue to red
case 4:
colour = rainbow(map(i, -angle, angle, 70, 127));
break; // Green to red (high temperature etc)
case 5:
colour = rainbow(map(i, -angle, angle, 127, 63));
break; // Red to green (low battery etc)
default:
colour = TFT_BLUE;
break; // Fixed colour
}
tft.fillTriangle(x0, y0, x1, y1, x2, y2, colour);
tft.fillTriangle(x1, y1, x2, y2, x3, y3, colour);
//text_colour = colour; // Save the last colour drawn
}
else // Fill in blank segments
{
tft.fillTriangle(x0, y0, x1, y1, x2, y2, TFT_GREY);
tft.fillTriangle(x1, y1, x2, y2, x3, y3, TFT_GREY);
}
}
// Convert value to a string
char buf[10];
byte len = 3;
if (value > 999)
len = 5;
dtostrf(value, len, 0, buf);
buf[len] = ‘ ‘;
buf[len + 1] = 0; // Add blanking space and terminator, helps to centre text too!
// Set the text colour to default
tft.setTextSize(1);

if (value < vmin || value > vmax)
{
drawAlert(x, y + 90, 50, 1);
}
else
{
drawAlert(x, y + 90, 50, 0);
}

tft.setTextColor(TFT_WHITE, TFT_BLACK);
// Uncomment next line to set the text colour to the last segment value!
tft.setTextColor(colour, TFT_BLACK);
tft.setTextDatum(MC_DATUM);
// Print value, if the meter is large then use big font 8, othewise use 4
if (r > 84)
{
tft.setTextPadding(55 * 3); // Allow for 3 digits each 55 pixels wide
tft.drawString(buf, x, y, 8); // Value in middle
}
else
{
tft.setTextPadding(3 * 14); // Allow for 3 digits each 14 pixels wide
tft.drawString(buf, x, y, 4); // Value in middle
}
tft.setTextSize(1);
tft.setTextPadding(0);
// Print units, if the meter is large then use big font 4, othewise use 2
tft.setTextColor(TFT_WHITE, TFT_BLACK);
if (r > 84)
tft.drawString(units, x, y + 60, 4); // Units display
else
tft.drawString(units, x, y + 15, 2); // Units display

// Calculate and return right hand side x coordinate
return x + r;
}

void drawAlert(int x, int y, int side, boolean draw)
{
if (draw && !range_error)
{
drawIcon(alert, x – alertWidth / 2, y – alertHeight / 2, alertWidth, alertHeight);
range_error = 1;
}
else if (!draw)
{
tft.fillRect(x – alertWidth / 2, y – alertHeight / 2, alertWidth, alertHeight, TFT_BLACK);
range_error = 0;
}
}

// #########################################################################
// Return a 16 bit rainbow colour
// #########################################################################
unsigned int rainbow(byte value)
{
// Value is expected to be in range 0-127
// The value is converted to a spectrum colour from 0 = blue through to 127 = red

byte red = 0; // Red is the top 5 bits of a 16 bit colour value
byte green = 0; // Green is the middle 6 bits
byte blue = 0; // Blue is the bottom 5 bits

byte quadrant = value / 32;

if (quadrant == 0)
{
blue = 31;
green = 2 * (value % 32);
red = 0;
}
if (quadrant == 1)
{
blue = 31 – (value % 32);
green = 63;
red = 0;
}
if (quadrant == 2)
{
blue = 0;
green = 63;
red = value % 32;
}
if (quadrant == 3)
{
blue = 0;
green = 63 – 2 * (value % 32);
red = 31;
}
return (red << 11) + (green << 5) + blue;
}

// #########################################################################
// Return a value in range -1 to +1 for a given phase angle in degrees
// #########################################################################
float sineWave(int phase)
{
return sin(phase * 0.0174532925);
}

//====================================================================================
// This is the function to draw the icon stored as an array in program memory (FLASH)
//====================================================================================

// To speed up rendering we use a 64 pixel buffer
#define BUFF_SIZE 64

// Draw array “icon” of defined width and height at coordinate x,y
// Maximum icon size is 255×255 pixels to avoid integer overflow

void drawIcon(const unsigned short *icon, int16_t x, int16_t y, int8_t width, int8_t height)
{

uint16_t pix_buffer[BUFF_SIZE]; // Pixel buffer (16 bits per pixel)

tft.startWrite();

// Set up a window the right size to stream pixels into
tft.setAddrWindow(x, y, width, height);

// Work out the number whole buffers to send
uint16_t nb = ((uint16_t)height * width) / BUFF_SIZE;

// Fill and send “nb” buffers to TFT
for (int i = 0; i < nb; i++)
{
for (int j = 0; j < BUFF_SIZE; j++)
{
pix_buffer[j] = pgm_read_word(&icon[i * BUFF_SIZE + j]);
}
tft.pushColors(pix_buffer, BUFF_SIZE);
}

// Work out number of pixels not yet sent
uint16_t np = ((uint16_t)height * width) % BUFF_SIZE;

// Send any partial buffer left over
if (np)
{
for (int i = 0; i < np; i++)
pix_buffer[i] = pgm_read_word(&icon[nb * BUFF_SIZE + i]);
tft.pushColors(pix_buffer, np);
}

tft.endWrite();
}

void tdstemp_read()
{
sensors.requestTemperatures();
temperature = sensors.getTempCByIndex(0);

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(“TDS Value:”);
Serial.print(tdsValue, 0);
Serial.println(“ppm”);

Serial.print(“Temperature:”);
Serial.print(temperature);
Serial.println(“ºC”);
}
}

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;
}

In the Arduino IDE, create a new tab. Copy the code below and save it as an Alert.h file.

// We need this header file to use FLASH as storage with PROGMEM directive:
#include <pgmspace.h>
// Icon width and height
const uint16_t alertWidth = 32;
const uint16_t alertHeight = 32;
// The icon file can be created with the “UTFT ImageConverter 565” bitmap to .c file creation utility, more can be pasted in here
const unsigned short alert[1024] PROGMEM={
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0840,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 0, 32 pixels
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x1080,0xAC66,0xEDE8,0xFE69,0xC4C6,0x2901,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 1, 64 pixels
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0xBCC6,0xFE68,0xFE68,0xFE6A,0xFE68,0xEDE8,0x18A1,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 2, 96 pixels
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x8344,0xFE48,0xFE8C,0xFFDD,0xFFFF,0xFEF0,0xFE48,0xB466,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 3, 128 pixels
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x1880,0xEDC7,0xFE48,0xFF99,0xFFBC,0xFF9B,0xFFBD,0xFE6A,0xFE48,0x5A23,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 4, 160 pixels
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x9BE5,0xFE28,0xFED0,0xFFBC,0xFF7A,0xFF9A,0xFF9B,0xFF35,0xFE28,0xBCA6,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 5, 192 pixels
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x3962,0xFE28,0xFE28,0xFF9A,0xFF79,0xFF9A,0xFF9B,0xFF9A,0xFFBD,0xFE6B,0xFE28,0x72E3,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 6, 224 pixels
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0xB465,0xFE28,0xFEF2,0xFF7A,0xFF79,0xFF7A,0xFF9A,0xFF7A,0xFF7A,0xFF78,0xFE28,0xDD67,0x0860,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 7, 256 pixels
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x5A22,0xFE07,0xFE29,0xFF9B,0xFF37,0xFF58,0xFF79,0xFF79,0xFF79,0xFF58,0xFF9B,0xFEAE,0xFE07,0x93A4,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 8, 288 pixels
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0xC4A5,0xFE07,0xFF15,0xFF37,0xFF36,0xAD11,0x2965,0x2965,0xCDF4,0xFF37,0xFF37,0xFF79,0xFE07,0xFE07,0x2901,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 9, 320 pixels
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x7B03,0xFDE7,0xFE4B,0xFF79,0xFEF4,0xFF15,0xB552,0x2945,0x2945,0xDE55,0xFF16,0xFF15,0xFF58,0xFED1,0xFDE7,0xAC25,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 10, 352 pixels
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0840,0xDD26,0xFDE7,0xFF57,0xFED3,0xFED2,0xFEF4,0xBD93,0x2124,0x2124,0xDE75,0xFF14,0xFED3,0xFED3,0xFF7A,0xFE08,0xFDE7,0x49A2,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 11, 384 pixels
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x9BA4,0xFDC6,0xFE6E,0xFF36,0xFE90,0xFEB1,0xFED3,0xC592,0x2124,0x2124,0xE675,0xFED3,0xFEB2,0xFEB1,0xFEF3,0xFEF3,0xFDC6,0xBC45,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 12, 416 pixels
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x3141,0xF5C6,0xF5C7,0xFF58,0xFE90,0xFE6F,0xFE8F,0xFEB1,0xCDB2,0x2104,0x2104,0xF6B4,0xFEB1,0xFE90,0xFE8F,0xFE90,0xFF58,0xFE0A,0xF5C6,0x72A3,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 13, 448 pixels
0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0xABE4,0xF5A6,0xFEB1,0xFED3,0xFE4E,0xFE6E,0xFE6F,0xFE90,0xD5F2,0x18E3,0x18E3,0xFED4,0xFE90,0xFE6F,0xFE6F,0xFE6E,0xFE91,0xFF36,0xF5A6,0xCCA5,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, // row 14, 480 pixels
0x0000,0x0000,0x0000,0x0000,0x0000,0x5202,0xF5A6,0xF5C7,0xFF58,0xFE4D,0xFE4D,0xFE4D,0xFE4E,0xFE6F,0xDE11,0x18C3,0x18C3,0xFED3,0xFE6F,0xFE6E,0xFE4E,0xFE4D,0xFE4D,0xFF16,0xFE2C,0xF5A6,0x9363,0x0000,0x0000,0x0000,0x0000,0x0000, // row 15, 512 pixels
0x0000,0x0000,0x0000,0x0000,0x0000,0xBC44,0xF585,0xFED3,0xFE6F,0xFE2C,0xFE2C,0xFE2D,0xFE4D,0xFE4E,0xE630,0x10A2,0x2104,0xFED1,0xFE4E,0xFE4D,0xFE4D,0xFE2D,0xFE2C,0xFE4D,0xFF37,0xF586,0xF585,0x28E1,0x0000,0x0000,0x0000,0x0000, // row 16, 544 pixels
0x0000,0x0000,0x0000,0x0000,0x7282,0xF565,0xF5EA,0xFF16,0xFE0B,0xFE0B,0xFE0B,0xFE2C,0xFE2C,0xFE4D,0xF670,0x1082,0x2924,0xFEB0,0xFE2D,0xFE2C,0xFE2C,0xFE2C,0xFE0B,0xFE0B,0xFEB2,0xFE6F,0xF565,0xA383,0x0000,0x0000,0x0000,0x0000, // row 17, 576 pixels
0x0000,0x0000,0x0000,0x0840,0xD4C4,0xF565,0xFEF5,0xFE0C,0xFDE9,0xFDEA,0xFE0A,0xFE0B,0xFE0B,0xFE2C,0xFE8F,0x0861,0x2964,0xFE8F,0xFE2C,0xFE0B,0xFE0B,0xFE0B,0xFE0A,0xFDEA,0xFE0B,0xFF37,0xF586,0xF565,0x4181,0x0000,0x0000,0x0000, // row 18, 608 pixels
0x0000,0x0000,0x0000,0x9343,0xF545,0xF60C,0xFED3,0xFDC8,0xFDC8,0xFDC9,0xFDE9,0xFDEA,0xFDEA,0xFE0B,0xFE8E,0x0861,0x3184,0xFE6D,0xFE0B,0xFE0A,0xFDEA,0xFDEA,0xFDE9,0xFDC9,0xFDC9,0xFE4E,0xFEB2,0xF545,0xB3E3,0x0000,0x0000,0x0000, // row 19, 640 pixels
0x0000,0x0000,0x28E0,0xF544,0xF545,0xFF17,0xFDC8,0xFDA7,0xFDA7,0xFDC8,0xFDC8,0xFDC9,0xFDC9,0xFDE9,0xFE6C,0x10A2,0x39C4,0xFE4C,0xFDEA,0xFDE9,0xFDC9,0xFDC9,0xFDC8,0xFDC8,0xFDA7,0xFDA8,0xFF16,0xF588,0xF544,0x6222,0x0000,0x0000, // row 20, 672 pixels
0x0000,0x0000,0xA383,0xF524,0xF64E,0xFE4E,0xFD86,0xFD86,0xFD87,0xFDA7,0xFDA7,0xFDA8,0xFDC8,0xFDC8,0xFE2A,0xA469,0xB4EA,0xFE2A,0xFDC9,0xFDC8,0xFDC8,0xFDA8,0xFDA7,0xFDA7,0xFD87,0xFD86,0xFDEA,0xFED3,0xF524,0xC443,0x0000,0x0000, // row 21, 704 pixels
0x0000,0x51C1,0xF504,0xF546,0xFF16,0xF565,0xFD65,0xFD65,0xFD86,0xFD86,0xFD86,0xFDA7,0xFDA7,0xFDA7,0xFDE8,0xFE6A,0xFE4A,0xFDE8,0xFDA7,0xFDA7,0xFDA7,0xFDA7,0xFD86,0xFD86,0xFD86,0xFD65,0xFD65,0xFEB2,0xF5CA,0xF504,0x8AE2,0x0000, // row 22, 736 pixels
0x0000,0xB3A2,0xED03,0xFE92,0xFDC9,0xF543,0xF544,0xFD44,0xFD65,0xFD65,0xFD65,0xFD86,0xFD86,0xFD86,0xFDA7,0xFDC7,0xFDC7,0xFDA7,0xFD86,0xFD86,0xFD86,0xFD86,0xFD65,0xFD65,0xFD65,0xFD44,0xF544,0xFD86,0xFEF5,0xED03,0xE4C3,0x1880, // row 23, 768 pixels
0x7241,0xECE3,0xF567,0xFED3,0xF523,0xF523,0xF523,0xF543,0xF544,0xF544,0xFD65,0xFD65,0xFD65,0xFD65,0xD4E6,0x39C5,0x39A5,0xD4E6,0xFD86,0xFD65,0xFD65,0xFD65,0xFD65,0xF544,0xF544,0xF543,0xF523,0xF523,0xFE2E,0xF5EC,0xECE3,0x9B42, // row 24, 800 pixels
0xD443,0xECE3,0xFED4,0xF565,0xF502,0xF502,0xF522,0xF523,0xF523,0xF543,0xF544,0xF544,0xF544,0xFD65,0x8B64,0x18C3,0x18C3,0x8344,0xFD85,0xFD44,0xF544,0xF544,0xF544,0xF543,0xF523,0xF523,0xF522,0xF502,0xF523,0xFEF5,0xED04,0xECE3, // row 25, 832 pixels
0xECC3,0xF5AB,0xFE6F,0xF501,0xF4E1,0xF501,0xF502,0xF502,0xF522,0xF522,0xF523,0xF523,0xF523,0xFD84,0xC504,0x20E1,0x18E1,0xC4E4,0xFD84,0xF543,0xF523,0xF523,0xF523,0xF522,0xF522,0xF502,0xF502,0xF501,0xF501,0xFDC9,0xF62F,0xECC3, // row 26, 864 pixels
0xECC2,0xFE92,0xF523,0xF4E0,0xF4E0,0xF4E1,0xF4E1,0xF501,0xF501,0xF502,0xF502,0xF522,0xF522,0xF543,0xFDE3,0xFEA5,0xF6A4,0xFE04,0xF543,0xF522,0xF522,0xF522,0xF502,0xF502,0xF501,0xF501,0xF4E1,0xF4E1,0xF4E0,0xF4E1,0xFED4,0xECC2, // row 27, 896 pixels
0xECA2,0xF5EC,0xF4E0,0xF4C0,0xF4E0,0xF4E0,0xF4E0,0xF4E1,0xF4E1,0xF501,0xF501,0xF501,0xF502,0xF502,0xF542,0xFDA2,0xFDA2,0xF542,0xF502,0xF502,0xF502,0xF501,0xF501,0xF501,0xF4E1,0xF4E1,0xF4E0,0xF4E0,0xF4E0,0xF4C0,0xF5A9,0xECA2, // row 28, 928 pixels
0xECA2,0xECA2,0xECC2,0xF4C1,0xF4C1,0xF4C1,0xF4C1,0xF4C1,0xF4C1,0xF4C1,0xF4C1,0xF4E1,0xF4E2,0xF4E2,0xF4E2,0xF4E2,0xF4E2,0xF4E2,0xF4E2,0xF4E2,0xF4E2,0xF4E1,0xF4C1,0xF4C1,0xF4C1,0xF4C1,0xF4C1,0xF4C1,0xF4C1,0xECC2,0xECC3,0xECA2, // row 29, 960 pixels
0x8AC1,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0xEC82,0x9B01, // row 30, 992 pixels
0x0000,0x1880,0x51A0,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x8AA1,0x61E0,0x28E0,0x0000}; // row 31, 1024 pixels

Select the ESP32 Wrover Module from the tool menu and proceed to the next step.

Connect the ESP32 Board with Type-C USB Cable and then upload the code.

Monitoring Aquarium TDS & Temperature

Press the rest button on the ESP32 Board after uploading the code. You can now use a lovely widget to monitor the TDS and temperature of your aquarium.

Aquarium Water Quality Monitor

You can use salt to test the sensor by pouring it into the liquid. The TDS value will quickly rise as soon as the salt is added. This is due to the fact that salt is an ionic substance. As a result, a salt solution can be used to increase the conductivity of any solution.

To check the value of TDS as a function of temperature, you can use hot water. The TDS value increases as the temperature rises. This is due to the fact that when the temperature rises, thus doe the electrical conductivity.

Aquarium Monitoring System

At 21°C temperature, the TDS value was 344ppm but at 49°C, the TDS value reached to 404ppm.

For an aquarium, the temperature good range is 76° to 80°F (25° to 27°C). The 400PPM~450PPM TDS in the water is good for most freshwater fish living.

Conclusion
I hope all of you understand how to design an Aquarium Water Quality Monitor with TDS Sensor & ESP32 . We MATHA Electronics will be back soon with more informative blogs.

Leave a Reply

Your email address will not be published.