В данной статье мы рассмотрим подключение датчика TDS к модулю ESP32 для измерения качества воды. Датчик/измеритель TDS показывает общее количество растворенных в воде (или растворе) твердых веществ, таких как соли, минералы и металлы. Таким образом, с его помощью можно производить измерение качества воды и сравнивать ее качество из различных источников. Одним из наиболее частых применений датчика TDS является его использование в аквариуме для контроля качества воды.
Ранее на нашем сайте мы рассматривали другие проекты измерителей качества воды:
- определение качества воды с помощью Arduino и датчика мутности;
- pH-метр (измеритель кислотности) на Arduino Uno.
Необходимые компоненты
- Модуль ESP32 (купить на AliExpress).
- TDS датчик (купить на AliExpress).
TDS датчик
Измеритель TDS измеряет общее количество растворенных твердых веществ, таких как соли, минералы и металлы в воде. По мере увеличения количества растворенных твердых веществ в воде увеличивается проводимость воды, и это позволяет нам рассчитать общее количество растворенных твердых веществ в частях на миллион (ppm (mg/L, мг / л)).
Хотя это хороший индикатор для мониторинга качества воды, обратите внимание, что он не измеряет содержание загрязняющих веществ в воде. Таким образом, вы не можете полагаться исключительно на этот индикатор, чтобы определить, пригодна ли вода для потребления или нет.
Измеритель TDS может быть полезен для мониторинга качества воды во многих приложениях, таких как бассейны, аквариумы, аквариумы для рыб, гидропоника, водоочистители и т.д.
В данном проекте мы будем использовать измеритель TDS от компании keystudio, который поставляется с интерфейсным модулем и электродным зондом (см. рисунок выше).
Для получения дополнительной информации о датчике TDS мы рекомендуем ознакомиться с его официальной документацией.
Технические характеристики
Приведены для TDS датчика версии 0 от keystudio.
Измеритель TDS:
- Входное напряжение: 3,3 ~ 5,5 В постоянного тока;
- Выходное напряжение: 0 ~ 2,3 В;
- Рабочий ток: 3 ~ 6 мА;
- Диапазон измерения TDS: 0 ~ 1000 ppm;
- Точность измерения TDS: ± 10% F.S. (25 ℃);
- Интерфейс модуля: XH2.54-3P;
- Интерфейс электрода: XH2.54-2P.
Пробник датчика:
- Количество игл: 2;
- Общая длина: 60 см;
- Интерфейс подключения: XH2.54-2P;
- Цвет: белый;
- Водонепроницаемый зонд.
Подключение TDS датчика к модулю ESP32
Выход TDS датчика необходимо подключать к одному из контактов модуля ESP32, способных выполнять АЦП (аналого-цифровое преобразование). Один из вариантов подключения датчика к модулю приведен в следующей таблице.
TDS датчик | Модуль ESP32 |
GND | GND |
VCC | 3.3V |
Data | GPIO 27 |
Вместо GPIO 27 можно использовать любой другой контакт модуля ESP32, поддерживающий функции АЦП.
Исходный код программы (скетча)
Пробник датчика выдает аналоговый сигнал, который может быть преобразован в TDS датчике в единицы ppm. Мы будем использовать код программы, предоставленный в документации к датчику, с небольшими изменениями.
Чтобы получить более точные результаты измерений может потребоваться калибровка вашего датчика в соответствии с известным значением TDS. Кроме того, в данном случае необходимо учитывать нелинейность АЦП ESP32, когда дело доходит до низких и высоких значений.
Однако эти настройки могут и не понадобиться, если вас интересуют не конкретные значения, а качественное значение TDS. Код нашей программы будет следующим.
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 |
// Original source code: https://wiki.keyestudio.com/KS0429_keyestudio_TDS_Meter_V1.0#Test_Code // Project details: https://RandomNerdTutorials.com/esp32-tds-water-quality-sensor/ #define TdsSensorPin 27 #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 = 25; // current temperature for compensation // median filtering algorithm 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; } 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]; // read the analog value more stable by the median filtering algorithm, and convert to voltage value averageVoltage = getMedianNum(analogBufferTemp,SCOUNT) * (float)VREF / 4096.0; //temperature compensation formula: fFinalResult(25^C) = fFinalResult(current)/(1.0+0.02*(fTP-25.0)); float compensationCoefficient = 1.0+0.02*(temperature-25.0); //temperature compensation float compensationVoltage=averageVoltage/compensationCoefficient; //convert voltage value to tds value tdsValue=(133.42*compensationVoltage*compensationVoltage*compensationVoltage - 255.86*compensationVoltage*compensationVoltage + 857.39*compensationVoltage)*0.5; //Serial.print("voltage:"); //Serial.print(averageVoltage,2); //Serial.print("V "); Serial.print("TDS Value:"); Serial.print(tdsValue,0); Serial.println("ppm"); } } } |
Объяснение работы программы
Дадим осмысленное название контакту модуля ESP32, к которому подключен датчик.
1 |
#define TdsSensorPin 27 |
Затем зададим значение опорного напряжения для АЦП модуля. В нашем случае для модуля ESP32 это напряжение будет равно 3.3V, а для платы Arduino, к примеру, оно будет составлять уже 5V.
1 |
#define VREF 3.3 // analog reference voltage(Volt) of the ADC |
Для получения более стабильных результатов измерений мы будем использовать алгоритм фильтрации медианных значений. Переменная SCOUNT в данном случае будет задавать количество отсчетов, по которым мы будем усреднять результат.
1 |
#define SCOUNT 30 // sum of sample point |
Затем объявим необходимые нам переменные и массивы.
1 2 3 4 |
int analogBuffer[SCOUNT]; // store the analog value in the array, read from ADC int analogBufferTemp[SCOUNT]; int analogBufferIndex = 0; int copyIndex = 0; |
Объявим и запишем в переменные averageVoltage и tsdValue значение 0.
1 2 |
float averageVoltage = 0; float tdsValue = 0; |
В переменной temperature мы будем хранить текущее значение температуры. Абсолютное значение температуры будет влиять на показания, поэтому существует алгоритм, компенсирующий колебания температуры. В нашем случае эталонная температура для датчика составляет 25ºC, но вы можете изменить ее в зависимости от вашей среды. Для получения более точных результатов вы можете добавить в проект датчик температуры и получать фактическую температуру на момент считывания показаний TDS датчика.
1 |
float temperature = 25; // current temperature for compensation |
В следующей функции мы будем получать стабильное TDS значение на основе усреднения ряда измерений.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// median filtering algorithm 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; } |
В функции setup() инициализируем последовательную связь со скоростью 115200 бод.
1 |
Serial.begin(115200); |
Установим режим работы контакта, к которому подключен TDS датчик, на ввод данных.
1 |
pinMode(TdsSensorPin,INPUT); |
В функции loop() мы будем считывать новые TDS значения каждые 40 мс и затем сохранять их в буфер.
1 2 3 4 5 6 7 8 9 |
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; } } |
Каждые 800 миллисекунд мы будем получать последние показания с датчика и среднее значние напряжения, используя алгоритм фильтрации, запрограммированный нами ранее.
1 2 3 4 5 6 7 8 |
static unsigned long printTimepoint = millis(); if(millis()-printTimepoint > 800U){ printTimepoint = millis(); for(copyIndex=0; copyIndex<SCOUNT; copyIndex++){ analogBufferTemp[copyIndex] = analogBuffer[copyIndex]; // read the analog value more stable by the median filtering algorithm, and convert to voltage value averageVoltage = getMedianNum(analogBufferTemp,SCOUNT) * (float)VREF / 4096.0; |
Затем мы будем рассчитывать коэффициент температурной компенсации и с его помощью рассчитывать TDS значение.
1 2 3 4 5 6 7 |
//temperature compensation formula: fFinalResult(25^C) = fFinalResult(current)/(1.0+0.02*(fTP-25.0)); float compensationCoefficient = 1.0+0.02*(temperature-25.0); //temperature compensation float compensationVoltage=averageVoltage/compensationCoefficient; //convert voltage value to tds value tdsValue=(133.42*compensationVoltage*compensationVoltage*compensationVoltage - 255.86*compensationVoltage*compensationVoltage + 857.39*compensationVoltage)*0.5; |
И, наконец, будем печатать на экране компьютера TDS значение в единицах ppm.
1 2 3 |
Serial.print("TDS Value:"); Serial.print(tdsValue,0); Serial.println("ppm"); |
Демонстрация работы проекта
После сборки аппаратной части проекта загрузите с помощью Arduino IDE код программы в свой модуль ESP32. Не забудьте выбрать правильный тип платы (модуля) в Tools > Board и правильный COM порт в Tools > Port. После этого откройте окно монитора последовательной связи на скорости 115200 бод и нажмите на модуле ESP32 кнопку RST чтобы код программы начал работать.
Датчик покажет значение 0 если его зонд не погружен в воду. Поместите зонд в раствор, чтобы проверить его TDS и работу проекта. Вы можете попробовать использовать водопроводную воду и добавить немного соли, чтобы посмотреть, увеличатся ли значения.
Автор проекта измерил значение TDS для водопроводной воды в своем доме и получил значение около 100 ppm, что является хорошим показателем для питьевой воды. Также он протестировал работу датчика, погрузив его в чай, и в этом случае значение TDS увеличилось примерно до 230 ppm, что кажется разумным значением.
Наконец, он также измерил значение TDS бутилированной воды и получил значение 0 ppm. Он не уверен, что это значение правильное, потому что вода рекламируется как минеральная, поэтому минералы, растворенные в воде, должны учитывать значение TDS. Он считает что это значение можно объяснить нелинейностью входов АЦП модуля ESP32 при малых значениях напряжения.