💬 Soil Moisture Sensor

  • Admin

    This thread contains comments for the article "Soil Moisture Sensor" posted on MySensors.org.

  • There is something in the sketch I do not understand

     * Connection:
     * D6, D7: alternative powering to avoid sensor degradation
     * A0, A1: alternative resistance mesuring
    void setup() {
      // initialize the digital pins as an output.
      // Pin 6,7 is for sensor 1
      // initialize the digital pin as an output.
      // Pin 6 is sense resistor voltage supply 1
      pinMode(6, OUTPUT);    
      // initialize the digital pin as an output.
      // Pin 7 is sense resistor voltage supply 2
     pinMode(7, OUTPUT);    

    What is this for?

  • Hardware Contributor

    I think the code is written for a self made sensor and the pictures show a binary sensor...

  • Mod

    @Martin-Tellblom the pins on the sensor can corrode by electrolysis. By alternating polarity, corrosion might be prevented (I am not sure that it makes a difference in practice though).

    Some discussion on the topic is available in https://forum.mysensors.org/topic/2147/office-plant-monitoring/ (very long thread with lots of information)

    As @FotoFieber points out, the alternating polarity is for a different sensor than the one shown in the wiring guide. We should decide which version to use and stick to one version in the example. I'm not sure which one we should use though.

  • Is there any updated sketch to use with the Soil Moisture Sensor shown in the pictures?

  • Plugin Developer

    The moisture sensor should be presented as S_MOISTURE not S_HUM.

  • Dous this sketch belong to this sensor ? pin 6,7 and a1 or a0 are not connected.....

  • Yeah. It would be great if someone can tell us about the hardware and wiring used in this sketch.

  • Henrik
    Yes could you please confirm how the nano is connected to the sensors ,soil sensors please ,yes 5+ 0- pin 3 ?? or in your sketch pin 6 7 ? to were
    or a0 a1 please how?? pin 6 to pin 3 on nano ?? it just not clear ??

  • For some people it might be worthwhile to look at the Mi Flora sensor, a pretty awesome $10 bluetooth sensor that measures moisture, soilhealth, light and temperature, and lasts a year on one coin cell battery. Its protocol has been reverse engineered, and now a lot of scripts offer all kinds of integrations, including connecting it to MQTT servers or Domoticz.



  • Hero Member

    @alowhum Thanks for the info. I have one of these and I would like to somehow integrate it with my other sensors. Maybe it would be possible to build a MySensors Bluetooth GW node to connect to the Mi Flora.

  • For those who are using the analogue soil moisture sensor in combination with a nano

    Connect the output of the sensor to the A0 of the nano
    connect the gnd of the sensor to the gnd on the nano
    connect th vcc of the sensor to 3v3 on the nano

    the sketch that works for me:
    // Enable debug prints to serial monitor
    #define MY_DEBUG

    // Enable and select radio type attached
    #define MY_RADIO_NRF24
    #define CHILD_ID 0

    #include <MySensors.h>
    // Here we are setting up some water thresholds that we will
    // use later. Note that you will need to change these to match
    // your soil type and environment. It doesn't do much for me because I'm using domoticz
    int thresholdUp = 400;
    int thresholdDown = 075;
    MyMessage msg(CHILD_ID, V_LEVEL);
    unsigned long SLEEP_TIME = 30000;

    // We are setting up the pin A0 on the redboard to be our sensor
    // pin input:
    int sensorPin = A0;

    void presentation()
    present(CHILD_ID, S_MOISTURE);

    void loop()
    int sensorValue;
    sensorValue = analogRead(sensorPin);

    //send back the values
    // delay until next measurement (msec)

  • My two cents. A soil moisture sensor that requires no extra hardware (not counting electric wire). Highly inspired by everything I've read in this thread. Comments welcome.
    Excuse the long post.

     Name:		MYS_MoistureSensor.ino
     Created:	5/25/2017 1:04:35 PM
     Author:	Rob
     Soil moisture measuring by using stainless steel rods (or any other conductor).
     Probably the simplest project ever, since only a MySensors-node and some wire is needed to set things up.
     The sketch alternates current during measuring to prevent corrosion of the rods due to electrolyses.
     Odd readings may occur when starting (eg. increasing soil moisture for no apparent reason), please just allow the electrodes to settle down in the soil.
     No extra hardware needed. 
     I use an Arduino Mini 3V3, powered on two AA cells. It is suggested you set the fuses for a lower Brown Out Detection (BOD). But anything goes.
     Just tie D4 and A0 together to one rod, and D5 and A1 to another rod. This is sensor one.
     For the second sensor tie D6 and A2 together to one rod, and D7 and A3 to another rod.
     Connect a pushbutton between GND and D3 if you need a button that makes the node report immediately (can be omitted)
     Measurement are taken every minute and send to the gateway if different from the previous reading.
     In case of no changes, the node reports itself every four hours.
     The output is between 0 (dry) and 100 (wet).
     Can also be used as a depth moisture sensor with three sensor zones; in that case use one (common) long rod and three smaller sensors along
     the height of the rod and configure the sketch accordingly.
    	 sensors[0] = { 4, A0, 5, A1, -1, false };
    	 sensors[1] = { 4, A0, 6, A2, -1, false };
    	 sensors[2] = { 4, A0, 7, A3, -1, false };
    #include "Header.h"
    // Enable debug Serial.prints to serial monitor
    //#define MY_DEBUG 
    #if defined MY_DEBUG
    #define Sprintln(a) (Serial.println(a))
    #define Sprint(a) (Serial.print(a))
    #define Sprintln(a)
    #define Sprint(a)
    // Enable and select radio type attached
    #define MY_RADIO_RFM69
    #define MY_RFM69_FREQUENCY RF69_868MHZ
    #define MY_IS_RFM69HW
    // Use PA_LOW for RF24+PA (Power Amplifier)
    //#define MY_RF24_PA_LEVEL RF24_PA_LOW
    //#define MY_RF24_PA_LEVEL RF24_PA_MAX
    #define MY_NODE_ID 4
    #include <MySensors.h>
    #define ACK 0        // = false
    #define CHILD_ID 1
    #define REPORTNOWSWITCH_PIN 3    // Arduino Digital I/O pin for button/reed switch (must be an interrupt pin!)
    #define SENSOR1_ROD1_DIGITAL 4
    #define SENSOR1_ROD1_ANALOG A0
    #define SENSOR1_ROD2_DIGITAL 5
    #define SENSOR1_ROD2_ANALOG A1
    #define SENSOR2_ROD1_DIGITAL 6
    #define SENSOR2_ROD1_ANALOG A2
    #define SENSOR2_ROD2_DIGITAL 7
    #define SENSOR2_ROD2_ANALOG A3
    #define SLEEP_IN_MS 60000		// every minute a new measurement
    #define EVERY_15_MINUTES (3600000/4/SLEEP_IN_MS)
    #define EVERY_4_HOURS (3600000*4/SLEEP_IN_MS) 
    #define NUM_READS (int)10		// Number of sensor reads for filtering
    int countLoops;
    int8_t interruptedBy = -1;
    int oldBatLevel;
    float oldTemperature;
    int output_value;
    /// Included in Header.h:
    //typedef struct {
    //	int digital_input_a;
    //	int analog_input_a;
    //	int digital_input_b;
    //	int analog_input_b;
    //	int level;
    //	bool connected;
    //} sensorWiring;
    sensorWiring sensors[NUM_MOISTURE_SENSORS];
    MyMessage msgMoistureSensor(CHILD_ID, V_LEVEL);
    MyMessage msgChipTemp(CHILD_ID_TEMPERATURE, V_TEMP);
    void before()
    	// All buttons as input-pullup as per ATMEGA recommendation to use less power (and more safety) 
    	// (http://electronics.stackexchange.com/questions/43460/how-should-unused-i-o-pins-be-configured-on-atmega328p-for-lowest-power-consumpt)
    	for (int i = 1; i <= 8; i++)
    		pinMode(i, INPUT_PULLUP);
    	// Now explicity set pins as needed
    	// Setup report-now switch, activate internal pull-up
    	// Initialize sensor variables
    	// Connect Digital pin 4 to Analog input A0 and a metal rod
    	// Connect Digital pin 5 to Analog input A1 and another metal rod.
    	// Connect Digital pin 6 to Analog input A2 and a metal rod
    	// Connect Digital pin 7 to Analog input A3 and another metal rod.
    	for  (int i = 0; i<NUM_MOISTURE_SENSORS; i++)
    		sensors[i].connected = testSensorConnections(sensors[i]);
    void setup()
    void presentation() {
    	sendSketchInfo("Moisture Sensor", "1.1", ACK);
    	for (int i = 0; i < NUM_MOISTURE_SENSORS; i++)
    		if (sensors[i].connected) present(CHILD_ID+i, S_MOISTURE, ACK);
    void loop()
    	bool reportNow = (interruptedBy == digitalPinToInterrupt(REPORTNOWSWITCH_PIN));
    	if (reportNow)
    		// Little trick for debouncing the switch
    		attachInterrupt(digitalPinToInterrupt(REPORTNOWSWITCH_PIN), debounce, RISING);
    		Sprintln(F("Report now switch pressed"));
    		countLoops = 0;
    	for (int i = 0; i < NUM_MOISTURE_SENSORS; i++)
    		if (sensors[i].connected)
    			output_value = measure(sensors[i]);
    			if ((sensors[i].level != output_value) || reportNow)
    				sensors[i].level = output_value;
    				send(msgMoistureSensor.setSensor(CHILD_ID+i).set(output_value), ACK);
    	// Every fifteen minutes; poll temperature
    	if (countLoops%EVERY_15_MINUTES==0 || reportNow) 
    		float newTemp = readTemp();
    		if (oldTemperature != newTemp || reportNow)
    			send(msgChipTemp.set(newTemp, 1), ACK);
    			oldTemperature = newTemp;
    		int batLevel = getBatteryLevel();
    		if ((oldBatLevel != batLevel) || reportNow) // ...but only when changed, or when button is pressed; 
    			sendBatteryLevel(batLevel, ACK);
    			oldBatLevel = batLevel;
    	// So you know I'm alive
    	if (countLoops == EVERY_4_HOURS)
    		countLoops = 0;
    	interruptedBy = sleep(digitalPinToInterrupt(REPORTNOWSWITCH_PIN), FALLING, SLEEP_IN_MS);
    // Connect Digital pin 'digital_input_a' to Analog input 'analog_input_a' and a metal rod,
    // do the same for b
    long measure(sensorWiring sensor)
    	long total = 0;
    	int reading_a = 0;
    	int reading_b = 0;
    	for (int i = 0; i<NUM_READS; i++) {
    		// Left to right
    		reading_a = measureOneDirection(sensor.digital_input_a, sensor.digital_input_b, sensor.analog_input_a);
    		// Right to left
    		reading_b = measureOneDirection(sensor.digital_input_b, sensor.digital_input_a, sensor.analog_input_b);
    		total += reading_a + reading_b;
    	return map(total / (2 * NUM_READS), 1023, 0, 0, 100);
    long measureOneDirection(int digital_input_1, int digital_input_2, int analog_input_1)
    	pinMode(digital_input_2, OUTPUT);
    	digitalWrite(digital_input_2, LOW);
    	pinMode(digital_input_1, INPUT_PULLUP);
    	long reading = analogRead(analog_input_1);
    	pinMode(digital_input_1, INPUT);     // High impedance                 
    	pinMode(digital_input_2, INPUT);     // High impedance                 
    	Sprint(F("measureOneDirection - Reading "));
    	return reading;
    // test the connections of both rods of a sensor
    boolean testSensorConnections(sensorWiring moistureSensor)
    	return (testSensorConnection(moistureSensor.digital_input_a, moistureSensor.analog_input_a) && testSensorConnection(moistureSensor.digital_input_b, moistureSensor.analog_input_b));
    //  test if digital pin is connected to correct analog pin
    boolean testSensorConnection(int digital_input, int analog_input)
    	pinMode(digital_input, OUTPUT);
    	digitalWrite(digital_input, HIGH);                        
    	long reading_1 = analogRead(analog_input);   
    	digitalWrite(digital_input, LOW);                      
    	long reading_2 = analogRead(analog_input);   
    	pinMode(digital_input, INPUT);     // High impedance                 
    	Sprint(F("testSensorConnection - Reading1 "));
    	Sprint(F("testSensorConnection - Reading2 "));
    	bool correct = ((reading_1 == 1023) && (reading_2 == 0));
    	return correct;
    float readTemp() 
    #if defined (xxMY_RADIO_RFM69) && !defined(MY_RFM69_NEW_DRIVER)
    	return _radio.readTemperature(-3);
    	// Read 1.1V reference against MUX3  
    	return (readMUX(_BV(REFS1) | _BV(REFS0) | _BV(MUX3)) - 125) * 0.1075f;
    long readMUX(uint8_t aControl) 
    	long result;
    	ADMUX = aControl;
    	delay(20); // Wait for Vref to settle
    	// start the conversion
    	ADCSRA |= _BV(ADSC) | _BV(ADIE);
    	set_sleep_mode(SLEEP_MODE_ADC);    // sleep during sample
    	// reading should be done, but better make sure
    	// maybe the timer interrupt fired 
    	while (bit_is_set(ADCSRA, ADSC));
    	// Reading register "ADCW" takes care of how to read ADCL and ADCH.
    	result = ADCW;
    	return result;
    // Battery measure
    int getBatteryLevel()
    	int results = (readVcc() - 2000) / 10;
    	if (results > 100)
    		results = 100;
    	if (results < 0)
    		results = 0;
    	return results;
    } // end of getBandgap
    // when ADC completed, take an interrupt 
    long readVcc() {
    	long result;
    	// Read 1.1V reference against AVcc
    	result = readMUX(_BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1));
    	result = 1126400L / result; // Back-calculate AVcc in mV (1024 steps times 1100 mV (1.1V) = 1126400L)
    	return result;
    // Utter nonsense, but needed for attaching an interrupt to...
    void debounce() {

  • The greatest problem with these sensors is electrolysis and subsequent oxidation, due to the DC current flowing through the sensor in a humid environment.
    There are some solutions: Most of the circuits that supposedly feed the sensor with A are bogus as it is apulsed DC at best..
    One could try a capacitive sensor...... in theory very good but in practice plagued by issues.

    What I have done is to remove constant current from the sensor by feeding it from a transistor that I can switch on and off. I take a measurement every 2-6 hrs and switch the current off in between. Makes a huge difference

  • Next logic step is flipping polarity like here: http://gardenbot.org/howTo/soilMoisture/

  • @rollercontainer That is definitely a good solution too, but I think gardenbot approaches it a bit too complicated from the software side when he points out that you get two readings with different values that 'need to be smoothed'

    I'd say do this:
    Both pins LOW when you are not taking a reading. That restperiod can be hours.
    When you are ready to take a reading:
    make one pin HIGH, take a reading and discard that one
    Flip the voltage, take another reading (to balance the time) and use that one
    Both pins LOW again

  • @Ed1500 This is exactly what I've done with the sketch I posted earlier in this thread.
    Use two inputs per sensor. Flip polarity for every reading and then rest in a high impedance state so as not to corrode the measuring rods (just some copper wire in my case). This has the added benefit of using the least power.
    Has been working like a charm for a couple of months now. Still planning to make a couple extra for the garden.

  • @RobKuipers sensible, good sketch. Truthfully, with just the very short reading alone (I do say a milisecond or less per 4 hours), the sensor hardly corrodes. I have two galvanized nails that I have in the soil for 4 seasons. Yes, not silky smooth anymore but really no trace of electrolysis, even one made from a clotheshanger that still is doing well

  • Did this sketch / wiring / sensor work for anyone? I read that more than I had questions about the pin 6 and 7 but those pins aren't connected in the wiring diagram. If anyone have any ideas on what to change to get it working it would be great to know!

  • The sketch does not correspond to the sensor shown in the images. It is much better to use the 2-pin sensors and drive them directly using digital outputs. Search for FC28 (better) or YL-69 in ebay or amazon. I use a voltage divider with a 10k resistor

    I've been using the alternating polarity approach on about 20 sensors, with perfect results and no signs of corrosion after around 6 months. I run some tests to determine the effect of measuring time and finally came up with 5ms with no averaging. The batteries last for months; I'm not sure how many since I haven't yet had to replace any (status led removed from 3.3v arduino mini pro board).

    As a reference, I tested a rain sensor (same principle) with no alternating current and as soon as a drop of water touched the tracks, small bubbles were produced with indicated that electrolysis was taking place. The effect could be seen on the tracks after just a couple of minutes.

    I'm attaching my sketch below. It reports battery level in addition to moisture. It uses the development branch of the mysensors library in order to use the new version of the rfm69 drivers with RSSI ATC - which btw works more than perfect.

    I'm using Domoticz which includes a predefined device for moisture. This device uses the centibar scale, so I calibrated my sensors in % moisture and then convert to cb.

    #define MY_RADIO_RFM69
    #define MY_RFM69_NEW_DRIVER   // ATC on RFM69 works only with the new driver (not compatible with old=default driver)
    #define MY_IS_RFM69HW
    #define MY_RFM69_FREQUENCY RFM69_868MHZ
    #define MY_RFM69_ATC_TARGET_RSSI_DBM (-70)
    #define MY_RFM69_NETWORKID  100
    #define MY_PARENT_NODE_ID 0
    #define MY_DEBUG 
    #include <MySensors.h>
    #include <SPI.h>
    #include <Vcc.h>
    #include <Streaming.h>
    #include <math.h>
    #define VERSION "1.1"
    /* Measurement probe connected to pins shown below
    I avoided using pins 2 and 3 because they are reserved for IRQ (potential future use) - 2 is also used by the RFM69 module.
    Although this may not actually have a noticeable effect, I also avoided 5 and 6 because they support PWM and hence are a bit slower.
    #define PIN_ALIM1 4                                   // Connect to input of resistor
    #define PIN_ALIM2 7                                   // Connect to input of measuring probe
    #define PIN_LECTURA A0
    #define AGUA_DIR 780.0
    #define AGUA_INV 160.0
    #define AIRE_DIR 0.0
    #define AIRE_INV 1023.0
    #define TIEMPO_LECTURA 5
    #define SLEEP_TIME_1h 3132000 // 1 h = 1*60*60000 = 3600000 ms -13% = 3132000 ms(my arduinos show a delay of 8s/min = 13%)
    #define SLEEP_TIME_2h 6264000 // 2 h = 2*60*60000 = 7200000 ms -13% = 6264000 ms
    #define SLEEP_TIME_3h 9396000 // 3 h = 3*60*60000 = 10800000 ms -13% = 9396000 ms
    // Battery calibration (Li-ion)
    const float VccMin   = 3.0;                         // Minimum expected Vcc level, in Volts.
    const float VccMax   = 4.2;                         // Maximum expected Vcc level, in Volts.
    const float VccCorrection = 3.82/3.74;              // Measured Vcc by multimeter divided by reported Vcc
    #define CHILD_MOIST_ID 1
    MyMessage msgmoist(CHILD_MOIST_ID, V_LEVEL);
    Vcc vcc(VccCorrection);
    float oldresultcb=0;
    int oldbat=0, count=0;
    void presentation(){
      sendSketchInfo("Sensor de humedad", VERSION);
      present(CHILD_MOIST_ID, S_MOISTURE, "Humedad suelo");
      pinMode(PIN_LECTURA, INPUT);
      pinMode(PIN_ALIM1, OUTPUT);
      pinMode(PIN_ALIM2, OUTPUT);
    void loop()
      unsigned int value1, value2;
      float result1, result2, resultp, resultcb;
    //Measurement of moisture
      digitalWrite(PIN_ALIM1, HIGH);
      digitalWrite(PIN_ALIM2, LOW);
      result1=constrain(value1/(AGUA_DIR-AIRE_DIR)*100.0, 1, 100);
      digitalWrite(PIN_ALIM1, LOW);
      digitalWrite(PIN_ALIM2, HIGH);
      digitalWrite(PIN_ALIM1, LOW);
      digitalWrite(PIN_ALIM2, LOW);
    /*Conversion from % moisture to cb taken from http://lieth.ucdavis.edu/Research/tens/98/SmtPub.htm
    Another option https://www.researchgate.net/figure/260321179_fig1_Fig-1-Relation-curve-between-water-tension-cb-and-soil-moisture-percentage
    The scale used in Domoticz is explained here http://www.irrometer.com/basics.html and can be checked in file domoticz/main/RFXNames.cpp
      0-10 Saturated Soil. Occurs for a day or two after irrigation 
      10-20 Soil is adequately wet (except coarse sands which are drying out at this range) 
      20-60 Usual range to irrigate or water (most soils except heavy clay soils). 
      60-100 Usual range to irrigate heavy clay soils 
      100-200 Soil is becoming dangerously dry
      resultcb=constrain(square((-2.96699+351.395/resultp)),0,200);                           //Equation fit using stat software
    //Send the data
      if ((oldresultcb!=resultcb) || (count==4)) send(msgmoist.set((unsigned int)resultcb));
    //Measure battery voltage here since it has been under change recently (more reliable)
      float v = vcc.Read_Volts();  
      int p = vcc.Read_Perc(VccMin, VccMax);
      if ((p!=oldbat) || (count==4)) sendBatteryLevel(p);
    //Save the last values and reset the counter
      if (count==4) count=0;
    #ifdef MY_DEBUG
      Serial << "Value1=" << value1 << " " << result1 << endl << "Value2=" << value2 << " " << result2 << endl << "Result = " << resultp << "% (" << resultcb << "cb)" << endl;
      Serial << "VCC = " << v << " Volts" << endl << "VCC% = " << p << " %" << endl;
      sleep(SLEEP_TIME_2h, true);  

    And this is how it looks in Domoticz:

    alt text

    I hope this helps.


Suggested Topics