In the past, I have written many posts about Soil Moisture sensors. I have written about using simple capacitive moisture sensors and NPK sensors to measure moisture levels in soil. I have explained how to use these sensors and ensure that they give accurate readings.
We need better sensors for more accurate measurements, especially in important applications. A good option is the Time Domain Reflectometry (TDR) sensor. In this post, I will show you how to connect this sensor to the microcontroller. then the data is shown on the OLED Display, and the data is used to adjust the watering and fertilization.
You’ll need a TDR soil sensor that measures soil temperature, moisture, and conductivity (EC).
We used the THC-S soil sensor with an RS485 interface for calibration. r. If you use different sensor models or from other manufacturers, you might need to recalibrate and adjust the source code. Check our previous post Complete Guide for NPK Soil Sensor with Arduino Tutorial
Required Material
- Arduino board
- TDR Soil sensor (buy the THC-S model)
- MAX485 TTL to RS-485 Interface Module
- 0.96″ SSD1306 OLED Display
- Jumper wires
- Breadboard
- Raspberry PI Soil Moisture Sensor Tutorial with Python
- How To Use Raspberry Pi Pico With Moisture Sensor
- Monitoring Soil Moisture Levels with Arduino and LCD Display
- Capacitive Soil Moisture Sensor v1.2 With Arduino Tutorial
Overview Arduino and TDR Moisture sensor
This sensor is highly accurate and sensitive, developed to fast measure the moisture content of the soil. You can insert the probe into the soil surface or profile for fast testing. Additionally, the probe can be permanently buried underground and connected to a data logger for continuous and unlimited monitoring of soil moisture levels.
In crop operations, we often measure pore water EC (pwEC), which tells us about the water in the soil. This generally requires expensive sensors, but we wanted to see if cheaper sensors could do the job.
Pinout TDR Soil Sensor
Soil Parameter Measurement:
Temperature:
- Measuring Range: -40°C to 80°C
- Accuracy: ±5°C at 25°C
- Long-term Stability: ≤0.1% per year
- Response Time: ≤15 seconds
Humidity:
- Measuring Range: 0% to 100% RH
- Accuracy: 2% within 0-50%, 3% within 50-100%
- Long-term Stability: ≤1% RH per year
- Response Time: ≤4 seconds
Conductivity (EC):
- Measuring Range: 0 to 200,000 μS/cm
- Accuracy: ±3% for 0-10,000 μS/cm, ±5% for 10,000-20,000 μS/cm
- Long-term Stability: ≤1% μS/cm
- Response Time: ≤1 second
Specification:
- Power Supply: DC 4.5-30V
- Max Power Consumption: 0.5W at 24V DC
- Protection Class: IP68, suitable for long-term immersion in water
- Cable Length: 2 meters
- Operating Environment: -40°C to 80°C
- Overall Dimensions: 45 x 15 x 123mm
MAX485 RS485 Transceiver Module
RS-485 is used for serial communication of data over long distances, and it has many advantages over other serial protocols such as RS-232. It is capable of transferring data up to a distance of 1200 meters. The use of MAX485 ensures a reliable and noise-free transmission of data, which is necessary for the accurate sensing of Soil levels.
Pinout of MAX485 RS485 Transceiver
- RO: Receiver Output
- RE: Receiver Enable
- DE: Driver Enable
- DI: Driver Input
The second 4-pin header on the output side consists of four pins: VCC, B, A, and GND.
- VCC
- B: Inverted data line
- A: Non-inverted data line
- GND
The 1 x 2 screw terminal block on the output side has two pins: B and A.
- B: Inverted data line
- A: Non-inverted data line
Interfacing TDR Soil Sensor With Arduino
The wiring connections between the Arduino and the RS485 module should be as follows:
Arduino connections to RS485 module:
- 5V -> VCC
- GND -> GND
- Pin 2 -> RO
- Pin 3 -> DI
- Pin 7 -> DE
- Pin 8 -> RE
Connecting RS485 module to Soil NPK Sensor:
- Ground to Ground
- VCC pin to 5V of the Arduino
- B -> Blue wire
- A -> Yellow wire
Wiring | Cable Color | Description |
---|---|---|
Brown | Power + | (DC5-30V) |
Black | Power – | |
Yellow | RS485 A+ | |
Blue | RS485 B |
Simple Example 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 |
#include <SoftwareSerial.h> #include <Wire.h> #define RE 8 #define DE 7 const byte hum_temp_ec[8] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x03, 0x05, 0xCB}; byte sensorResponse[12] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; byte sensor_values[11]; SoftwareSerial mod(2, 3); void setup() { // put your setup code here, to run once: Serial.begin(115200); mod.begin(4800); pinMode(RE, OUTPUT); pinMode(DE, OUTPUT); digitalWrite(RE, LOW); digitalWrite(DE, LOW); delay(100); } void loop() { /************** Soil EC Reading *******************/ digitalWrite(DE, HIGH); digitalWrite(RE, HIGH); memset(sensor_values, 0, sizeof(sensor_values)); delay(100); if (mod.write(hum_temp_ec, sizeof(hum_temp_ec)) == 8) { digitalWrite(DE, LOW); digitalWrite(RE, LOW); for (byte i = 0; i < 12; i++) { //Serial.print(mod.read(),HEX); sensorResponse[i] = mod.read(); yield(); Serial.print(sensorResponse[i], HEX); Serial.print(","); } Serial.println(); } delay(250); // get sensor response data float soil_hum = 0.1 * int(sensorResponse[3] << 8 | sensorResponse[4]); float soil_temp = 0.1 * int(sensorResponse[5] << 8 | sensorResponse[6]); int soil_ec = int(sensorResponse[7] << 8 | sensorResponse[8]); /************* Calculations and sensor corrections *************/ /** * Soil VWC correction. Test and use if necessary. */ // change: quadratic aproximation of VWC from CWT sensor to Teros 12 sensor. // Just in case or for tests. The VWC of Teros and chinese sensor are very close (see reference spreadsheet). //soil_hum = -0.0134 * soil_hum * soil_hum + 1.6659 * soil_hum - 6.1095; /** * Bulk EC correction. Choose one and uncomment. */ // CHOOSE ONE: cubic aproximation of BULK EC from CWT sensor to Teros 12 sensor (more precise) //soil_ec = 0.0000014403 * soil_ec * soil_ec * soil_ec - 0.0036 * soil_ec * soil_ec + 3.7525 * soil_ec - 814.1833; // CHOOSE ONE: This equation was obtained from calibration using distilled water and a 1.1178mS/cm solution. // Change by @danielfppps >> https://github.com/kromadg/soil-sensor/issues/3#issuecomment-1383959976 soil_ec = 1.93 * soil_ec - 270.8; /** * Bulk EC temperature correction. Test and use if necessary. */ // Soil EC temp correction based on the Teros 12 manual. https://github.com/kromadg/soil-sensor/issues/1 soil_ec = soil_ec / (1.0 + 0.019 * (soil_temp - 25)); // soil_temp was left the same because the Teros and chinese sensor values are similar // quadratic aproximation // the teros bulk_permittivity was calculated from the teros temperature, teros bulk ec and teros pwec by Hilhorst 2000 model float soil_apparent_dieletric_constant = 1.3088 + 0.1439 * soil_hum + 0.0076 * soil_hum * soil_hum; float soil_bulk_permittivity = soil_apparent_dieletric_constant; /// Hamed 2015 (apparent_dieletric_constant is the real part of permittivity) float soil_pore_permittivity = 80.3 - 0.37 * (soil_temp - 20); /// same as water 80.3 and corrected for temperature // converting bulk EC to pore water EC float soil_pw_ec; if (soil_bulk_permittivity > 4.1) soil_pw_ec = ((soil_pore_permittivity * soil_ec) / (soil_bulk_permittivity - 4.1) / 1000); /// from Hilhorst 2000. else soil_pw_ec = 0; Serial.print("Humidity: "); Serial.print(soil_hum); Serial.println(" %"); Serial.print("Temperature: "); Serial.print(soil_temp); Serial.println(" °C"); Serial.print("EC: "); Serial.print(soil_ec); Serial.println(" us/cm"); Serial.print("pwEC: "); Serial.print(soil_pw_ec); Serial.println(" dS/m"); Serial.print("soil_bulk_permittivity: "); Serial.println(soil_bulk_permittivity); delay(2000); } |
Once you’ve uploaded the code check all the wiring connections, and open the Serial Monitor in the Arduino IDE. You’ll see all the measurements printed there, so you can easily view the readings.
Monitor Soil using a TDR Soil Sensor, OLED, And Arduino
The 0.96″ OLED Display is an I2C Display. Just connect its VCC pin to the 5V pin and the GND pin to the GND pin on the Arduino. Then, connect the SDA pin to the A4 pin and the SCL pin to the A5 pin on the Arduino. You can follow a circuit diagram and set up the circuit on a breadboard.
Source Code
To display the Soil Nutrient values (Humidity, Tem p& Pore water electrical conductivity on the OLED Display, you’ll need to install the OLED Library in the Arduino IDE. Here’s what you need to download:
Here is the full code. Compile the code & upload it to the Arduino Board.
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 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 |
#include <SoftwareSerial.h> #include <Wire.h> #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #define SCREEN_WIDTH 128 // OLED display width, in pixels #define SCREEN_HEIGHT 64 // OLED display height, in pixels #define OLED_RESET -1 // Reset pin # (or -1 if sharing Arduino reset pin) Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET); #define RE 8 #define DE 7 const byte hum_temp_ec[8] = {0x01, 0x03, 0x00, 0x00, 0x00, 0x03, 0x05, 0xCB}; byte sensorResponse[12] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; byte sensor_values[11]; SoftwareSerial mod(2, 3); void setup() { // put your setup code here, to run once: Serial.begin(115200); mod.begin(4800); pinMode(RE, OUTPUT); pinMode(DE, OUTPUT); digitalWrite(RE, LOW); digitalWrite(DE, LOW); // Initializes display display.begin(SSD1306_SWITCHCAPVCC, 0x3C); // initialize with the I2C addr 0x3C (128x64) delay(500); display.clearDisplay(); display.setCursor(13, 15); display.setTextSize(1); display.setTextColor(WHITE); display.println(" THC Soil Sensor"); display.setCursor(21, 35); display.setTextSize(1); display.print("Initializing..."); display.display(); delay(5000); } void loop() { // put your main code here, to run repeatedly: /**************Soil EC Reading*******************/ digitalWrite(DE, HIGH); digitalWrite(RE, HIGH); memset(sensor_values, 0, sizeof(sensor_values)); delay(100); if (mod.write(hum_temp_ec, sizeof(hum_temp_ec)) == 8) { digitalWrite(DE, LOW); digitalWrite(RE, LOW); for (byte i = 0; i < 12; i++) { //Serial.print(mod.read(),HEX); sensorResponse[i] = mod.read(); yield(); Serial.print(sensorResponse[i], HEX); Serial.print(","); } Serial.println(); } delay(250); float soil_hum = 0.1 * int(sensorResponse[3] << 8 | sensorResponse[4]); float soil_temp = 0.1 * int(sensorResponse[5] << 8 | sensorResponse[6]); int soil_ec = int(sensorResponse[7] << 8 | sensorResponse[8]); /** * Soil VWC correction. Test and use if works for you. */ // change: quadratic aproximation of VWC from CWT sensor to Teros 12 sensor. // Just in case or for tests. The VWC of Teros and chinese sensor are very close (see reference spreadsheet). //soil_hum = -0.0134 * soil_hum * soil_hum + 1.6659 * soil_hum - 6.1095; /** * Bulk EC correction. Choose one, test and uncomment if works for you. */ // CHOOSE ONE: cubic aproximation of BULK EC from CWT sensor to Teros 12 sensor (more precise) //soil_ec = 0.0000014403 * soil_ec * soil_ec * soil_ec - 0.0036 * soil_ec * soil_ec + 3.7525 * soil_ec - 814.1833; // CHOOSE ONE: This equation was obtained from calibration using distilled water and a 1.1178mS/cm solution. // Change by @danielfppps >> https://github.com/kromadg/soil-sensor/issues/3#issuecomment-1383959976 soil_ec = 1.93 * soil_ec - 270.8; /** * Bulk EC temperature correction. Test and use if works for you. */ // Soil EC temp correction based on the Teros 12 manual. https://github.com/kromadg/soil-sensor/issues/1 soil_ec = soil_ec / (1.0 + 0.019 * (soil_temp - 25)); // soil_temp foi deixada a mesma pois os valores do teros e do sensor chines sao parecidos // quadratic aproximation // the teros bulk_permittivity was calculated from the teros temperature, teros bulk ec and teros pwec by Hilhorst 2000 model float soil_apparent_dieletric_constant = 1.3088 + 0.1439 * soil_hum + 0.0076 * soil_hum * soil_hum; float soil_bulk_permittivity = soil_apparent_dieletric_constant; /// hammed 2015 (apparent_dieletric_constant is the real part of permittivity) float soil_pore_permittivity = 80.3 - 0.37 * (soil_temp - 20); /// same as water 80.3 and corrected for temperature // converting bulk EC to pore water EC float soil_pw_ec; if (soil_bulk_permittivity > 4.1) soil_pw_ec = ((soil_pore_permittivity * soil_ec) / (soil_bulk_permittivity - 4.1) / 1000); /// from Hilhorst 2000. else soil_pw_ec = 0; Serial.print("Humidity: "); Serial.print(soil_hum); Serial.println(" %"); Serial.print("Temperature: "); Serial.print(soil_temp); Serial.println(" °C"); Serial.print("EC: "); Serial.print(soil_ec); Serial.println(" us/cm"); Serial.print("pwEC: "); Serial.print(soil_pw_ec); Serial.println(" dS/m"); Serial.print("soil_bulk_permittivity: "); Serial.println(soil_bulk_permittivity); delay(2000); display.clearDisplay(); display.setTextSize(1); display.setCursor(0, 0); display.print("Humi: "); display.setTextSize(2); display.print(soil_hum); display.setTextSize(1); display.print(" %"); display.setTextSize(1); display.setCursor(0, 17); display.print("Temp: "); display.setTextSize(2); display.print(soil_temp); display.setTextSize(1); display.print(" C"); display.setTextSize(1); display.setCursor(0, 34); display.print("EC: "); display.setTextSize(2); display.print(soil_ec); display.setTextSize(1); display.print(" us/cm"); display.setTextSize(1); display.setCursor(0, 50); display.print("pwEC: "); display.setTextSize(2); display.print(soil_pw_ec); display.setTextSize(1); display.print(" dS/m"); display.display(); } |
Monitoring TDR Soil Data on OLED Display
After uploading the code to the Arduino Board, both the OLED display and the sensor will initialize. Please note that the sensor may require some time to stabilize, and initially, the readings may not be accurate.