Capacitive Soil Moisture Measuring (with I2C)




Introduction: Capacitive Soil Moisture Measuring (with I2C)

About: I am a physician by trade. After a career in the pharmeceutical world I decided to take it a bit slower and do things I like. Other than my hobbies that involves grassroots medicine in S.E.&P Asia. I have buil…

A lot has been written about how to measure soil moisture and especially on what sensor to use.

With the simple resistance measuring the biggest issue is the corrosion of the sensor, not just because it is in contact with the soil but also because there is a DC current flowing which causes electrolysis of the sensors.

There are solutions for this, like feeding the sensor with an AC current, but in practice this is at best a pulsating DC. Some people encase their sensors in plaster, but I found that a bit of a drag and you get a slow reacting sensor as the moisture content of the plaster will always be behind that of the soil.

So, like many others, i have been entertaining the idea of capacitive soil measuring.
Capacitive measuring has some advantages, not only is it possible to avoid corrosion of the probe, capacitive measuring also gives a better reading of the moisture content of the soil as opposed to resistance measuring. In the latter one doesnt really measure moisture (as water is a bad conductor of current), but in fact one measures the ions that are dissolved in the moisture. Adding fertilizer for instance will decrease the resistance of the soil, eventhough no water is added.

Capacitive measuring basically measures the dielectrum that is formed by the soil and the water is the most important factor that forms that dielectrum.

As said, various people have already been tackling this issue and came up with capacitive probes. Usually these are made of double sided PCB to form the 'capacitor', sometimes even single sided PCB in which basically two tracks are etched. These have the advantage that they can contain some hardware as well and basically have a "stick in and forget" practicality. Just one thing you put in the ground and be done with it.

These are also a bit pricey if you buy them around 10-13 dollars, but ofcourse making it yourself is possible and in this case probably cheaper as well.

Essential with these 'probes' is that you electrically insulate them from the soil, otherwise you might as well measure resistance again and keep it simple. This insulation is usually done with some kind of weatherproof lacquer or paint.

I wanted to see if i could use a bit of a simpler contraption, like 2 pieces of pcb as my capacitor plates.

Step 1: Capacitive Soil Moisture Measuring: Testing

Although one can use the arduino to measure capacity directly, I find it a bit tedious and it will also cost you an extra pin or 2. Also it is a bit impractical as it needs the capacitor to be close to the arduino as not to measure stray capacity of the wires.
Therefore I wanted to use an RC oscillator in which the 'C' i.e. the soil moisture content defined the frequency of the osicllator. A frequency value can be transported easier over a long wire than a capacity value.


  • 74HCT14
  • 2M2 resistor (In the ready model I ended up using a 100k resistor)
  • Glass jar (pickles or something like that)
  • 2 pieces of alu foil, each large enough to cover a bit less than half the jar
  • Ducttape
  • wires
  • small breadboard
  • Arduino

There are several simple circuits that form an RC oscillator but I have chosen the one with the inverter/schmitt-trigger 74HCT14. The frequency of that is 1/T=1/(0.67 xRC). If you happen to use a 74HC14 the frequency is 1/T=1/(0.8xRC).

However, the actual frequency is not really important as we are looking for differences in frequency that relate to dry or moist soil. If you happen to have some NANDs rather than the 74HC14 that can be used too and most people will likely have a 555 that can be used.

As it is winter and cold outside I like to do my testing inside so I also made a quick sort of modified "Leyden jar" (Leidse Fles) that consisted of a glass jar with two pieces of alufoil attached to the outside (at opposite sides) with each a wire ducttaped to it.

I attached this jar to the input of the oscillator and fed the output to an arduino where i used a simple 'pulseIn' command to measure the period. I first used a resistor of 100k but that really didnt show any results, so i increased it to 2M2.
i then started to fill the jar with water. That gave me the following readouts:

  • Empty jar: 1uS (but that was probably the minimum reading as 'no jar' also gave me that)
  • Half full jar: 50-60uS
  • Full jar: 90-110 uS (that's around 10kHz)

All in all it looked pretty stable and in fact fairly linear as well. The system is fairly sensitive too: I found out that if i stuck my fingers in a full jar, the cycle time dropped a bit: as my fingers contain less water than pure water and they displaced the water, the dielectrum dropped. Obviously this only works with a full jar,because with a half full jar the water between the plates rises, increasing the dielectrum again.

Step 2: Capacitive Soil Moisture Measuring: Field Testing

For the field test we need the following:

  • 2 pieces of PCB ca, 12x7 cm (or other size if you have)
  • 2 Zip bags of >12x>7 cm
  • 2 alligator clamps (or solder a wire)
  • 1x HCT14
  • 1x 2M2 resistor (Eventually in my ready model I used a 100k, but much depends on yr sensor)
  • wires
  • small breadboard
  • Arduino
  • Laptop or LCD

As it was freezing, I had to wait for the ground to thaw, but then I constructed two plates simply by placing a piece of PCB with a wire attached in a ZIP-bag as one plate and a similar construction as the other plate.

I have a raised bed of 1.20x1.20m (4x4 foot 'square foot garden') filled with Mel' s Mix.
I put the plates some 30 cm apart with the copper sides facing. With that I took a rather traditional view on what a capacitor is: a dielecrum BETWEEN 2 plates, whereas the double sided pcb versions more look at the dielectrum AROUND 2 plates. In reality ofcourse the soil at the back of my plates also is part of the dielectrum.

I used the PulseIn command again to measure the pulse-length of the output of the 74HCT14 RC oscillator.

I was pleasantly surprised to see values in the range of what I found with the Leyden jar the reading was about 30uS and would rise fairly rapidly when I started watering, even if this was not directly between the plates.

The reading was more stable than I had expected, but handling the wires definitely had an influence, albeit small. This can be helped by using twisted pair wires, but as there are two plates at 30 cm distance, some length of single strand will be unavoidable. Note, I am talking about the wires from the plates to the oscillator, not the wires from the oscillator to the arduino,


So, basically I now had a fairly cheap and simple capacitive sensor but I was starting to think about the practicality.
Ideally, I would place the plates at opposite ends of the Square foot garden, so 4 feet/1m20 away from eachother.
That did work albeit that the readings were a bit lower. Placing them in a 90degree angle in a corner also worked well.
Now I still had to consider where in the final setup I had to put the oscillator. Clearly this had to be on one of the plates, but then there always would be a wire needed to connect the distal plate.
I was starting to see the advantages of the "one prod, just stick it in the soil" sensor. Time to rethink

Step 3: Capacitive Soil Moisture Measuring: Practical Solutions

As said, I was now having this working, cheap, easy capacitive sensor that would do fine in a steady setup, but I wanted something more moveable, durable.
My goal however was to keep it cheap, otherwise I might as well have bought one. Ofcourse it is possible to have a PCB made and then for say 15 euro's you get 3 PCBś so the cost per PCB is not that bad, but to have a PCB made for something that simple seemed dumb.
So I did some further testing and placed the PCB's back to back with the copper surface away from eachother and I also tried putting them next to eachother. Both setups again gave reasonable results. Maybe the one PCB solution wasnt that bad after all.

With one PCB there are a few options to form the capacitor plates A and B:

  • double sided, each with a full surface A or B
  • double sided, but each side also divided in two surfaces so A & B at both sides
  • single sided with plate A and B on one side

The first solution seemed simplest to me

Step 4: Capacitive Soil Moisture Measuring: the Chirp Has I2C: Can You Do That Too?

Yes, well the original Chirp does not have I2C (but can be hacked as such) but they do have a version with an I2C and that isn't a big problem to do. We are going to need an Attiny85 for that. As we are using the Attiny85 we could consider dropping the HC14 as the attiny can also measure capacity (and uses 3 pins for that), but while we are at it and use I2C, we might as well expand the humidity sensor with a temperature and e.g. light sensor. and keep the HC14.
The Attiny has 5 pins to its disposal (unless we want to mess with pin1), two we need for I2C so we would have 3 left, which would just be enough for a capacity tester. If we keep the HC14, we only need one pin and have two left for other measurements
In order to make the Attiny85 act like an I2C slave we will be using the TinyWireS library.

For the reading of the LDR ant the NTC we need an integer to store the readings as it could go up to 1023, however, unless you nee a lot of accuracy, you could map it into 1 byte.

Do not forget that the I2C lines need a 4k7-10k pull up line. Whether you want to add those to your sensor or add them at yr Arduino is up to you

You will find the code in the next step

Step 5: Capacitive Soil Moisture Measuring: I2C Slave Code

The code to provide the sensor with I2C looks like this:

It is important that the Attiny works on at least 8Mhz.
I cant take all the credit for the code as I just reworked one of the examples in the TinyWireS library.
I have used pulseIn to measure the pulselength. PulseIn is a command that waits. It might not be the best policy, but it works. If anybody has a suggestion on a better code, I am always interested to hear that.
With regard to the NTC, I now read the value on the analog port, map that to 1 byte and present that for further processing. Ofcourse it is also possibe to use the Steinhart-Hart formula to rework it to a temperature in degrees and put that in the register.

If u approximate the temperature with th Steinhart-Hart formula youneed one of these calculations
Rntc = Rseries/((1023/ADC) – 1)); // with a pull up resistor

Rntc = Rseries*((1023/ADC)-1);// with a pull down resistor as in this circuit

Step 6: Capacitive Soil Moisture Measuring: I2C Master Code

In order to read the the sensor, the Arduino needs the following code:

This is ofcourse only an example code that reads out the LDR, NTC and Humidity registers. The humidity is represented by two bytes that need to be combined in an integer. That can be done with one line of code: value= msbv<<8 | lsvb; For the uninitiated: this code shifts the Highest bit 8 positions (1byte) to the left, basically by adding 8 zeros at the right. It then OR's the lowest byte to that, thus forming the 16 bit (2byte) integer

Step 7: Construction

As I didnt have double sided PCB, I just glued two pieces of single side together (but mind you, one piece of single sided PCB with two plates etched onto it works too). And to make sure it wouldn't detach, I soldered a wire through both plates in the corners. Obviously you should do that an an insulated copper island. Made a round plastic baseplate that the pcb would fit in and that could carry the clear plastic dome. The dome would house the separate PCB I made for the circuit

Now of course this is not the way you have to do it. It is very convenient to etch the PCB for the circuit on one of the capacitor plates, but as my piece of scrap was a bit short I decided against it.

the only reason I made it with a plastic dome is because I have an LDR under it. without that you could just encase the entire circuit in shrink tube and have a nifty sleek design
The NTC I stuck to one of the capacitor plates.
Finally, a 4 wire cable goes into the sensor.

It is essential that the capacitor plates are completely galvanically separated from the soil. One can do that with paint, Plasti-Dip (expensive), or Heatshrink-tube.
I most likely will choose the latter, but for now I will just use a plastic zip bag till I know I am completely happy with the set-up.
Eventually I ended up going back to a 100k resistor in the oscillator

Total cost:

Attiny 45 or 85: 100ct (75 cts in 20SU)

74HC14 10ct

dip foot 10ct

resistors 10 cts
So basically in parts this will cost 1.30 USD.
Some scrap PCB will do and then some wire, a cone and some scrap plastic for a base.
The cover of laquer or shrink tube might be the most expensive part

Step 8: Capacitive Soil Humidity Measuring With I2C: Thoughts

A promising range of chips is the AD7745/46/47 range.
These are integrated capacitive to digital chips that will take two capacitor plates a input and convert it to an I2C signal.
However, at 10-12 USD these are not cheap.

Also, with its current program, the device measures continuously. Obviously that is not really a problem as one always reads-out the most recent stable measurement, but one could decide to send a signal via I²C to start the measurements.

You will find another interesting capacitive moisture measuring projct here.

2 People Made This Project!


  • Sculpt & Carve Challenge

    Sculpt & Carve Challenge
  • Meatless Challenge

    Meatless Challenge
  • Fabric Challenge

    Fabric Challenge



5 years ago


I have made these probes based on the design in Initially my plan was to read the sensors using a ATTINY84 and then use a ESP8266 module conneted to it through the serial port to trasnmit the data. Later i changed the design to use a Arduino Pro Mini and a nrf24 module. I use the Arduino capsense module to read the capacitance values of the sensors directly from the microcontroller.

Note that for extra protection I have coated the sensors with Plasti dip spray.

If I hold the sensor in my hand with my fingers tightly wrapped around, i get pretty consistent values with a variation of just 1 to 3%. But when i insert the sensor into the soil of a vegetable pot, then the readings that i get become very, very erratic - more than 20 to 30%. I even get a value of zero often.

I am really puzzled by this behavior. If there is a problem with either the sensor construction or the electronics then I should be getting erratic readings all the time. But thats not the case. If I hold the sensor with my fingers wrapped around, then I am getting consistent values.

Do you have any suggestions for me to fix this issue (where the sensors are giving very erratic values when inserted in soil)? Thank you, Gopi.


Reply 2 years ago

Hi, i have a similar problem, do you find a solution?


Reply 5 years ago

Gopi, I apologize for my late reply. Normally I try to reply within a few days, but somehow your message escaped my attention.

Some variation in readout is within expectation as soil is not a stationar medium, but 30% may be a bit much and a reading of 0 is indeed odd. Thee might be several factors causing this:

airpockets around your probe
a leaking coating
long or faulty wiring
probe design

What you could try is to use twisted wiring or a shielded cable and keep it short. Also your software can play a role, although in your case that also might have caused problems when you had yr hand around it. Having said that....I have tried several capsense programs/libraries and had dissatisfying results with it, kinda like what you describe.

The design of your probe seems OK, but you are using only a small part of the available pcb surface. I kinda like the design that Colomichi is using, but I fully understand you will not just redo your entire setup.

if for whatever reason you keep getting large variation, I suggest you average a number of readings (say 50-100) and see how that goes. I would be especially interested to know is with averaging the readings, you would be able to see a distinct difference hen adding water


Reply 5 years ago

Thanks for your message sir. In my probe, the distance between the probe and the Arduino is just a couple of centimetres. The Arduino is at the top of the probe itself. Hence, I dont think the length of the wire could be playing a part here.

I have checked and rechecked everything, but in the soil it always give very erratic readings. I have the pcb mask itself, then i have a layer of plasti dip spray. I checked all the points that could be exposed and covered them with kapton tape. But still no luck.

If i take the probe out and cover it with my hand, then it gives very stable readings - relative to the moisture content in my hand.

I am using 30 as the number of samples to the capacitiveSensor() function. I think i have tried using 100 or 200 samples before. I will try it again just to be sure.

I do see the readings change when i add water to the pot, but its still erratic. If its within an acceptable range, i could try averaging them out. But I get many 0 readings which will mess up the averages.

I will keep trying and will update if I get lucky :)



Reply 5 years ago

I had secretly hoped you had found a solution these past 2 months. Anyway, what is the reading when you keep it in your hand as you describe and what is the ballparc reading when you have it in the soil?

Also, can you try it in compact sand and see what is happening?

Both a pcb masing AND plastidip might be a bit too much. How thick is the layer you'd guess?


3 years ago on Step 8

Great instractable! Heartfelt thanks. As an enthusiast "Sunday morning gardener" I will try to implement this device. One criticism: having wires in a raised bed could lead to mess it up when dealing with the beloved vegetables. One second comment: a micro solar panel could solve power problem for the device, not to spare the battery but to avoid openings which could lead to oxidation/malfunction.


Tip 4 years ago

Sure the "Siemens" is conductivity which is 1/ohm, but if after reading the title and the description, plus seeing the RC oscillator and seeing the formula in which the outcome is in microseconds and I also express those microseconds in a frequency, plus describe the Leyden jar, I am surprised it took you to 'pulseIn' to understand what I have been saying and describing from the start: that it is capacitive


Tip 4 years ago on Step 1

"S" with capital letter means "Siemens", and it's a conductivity (1/Ω) unit, which lead me to confusion: wasn't this a capacitative sensor, not a resistive one? Until I read that you used "pulseIn", for measuring time. Use a lowercase "s" for referring to seconds. In general, SI units in capital letters are for those with scientist's names, and lowercase for the rest.

Tecwyn Twmffat
Tecwyn Twmffat

6 years ago

Nice! Are you still transmitting a frequency to the Arduino or using I2C? as I2C is only designed for shortish cable lengths.


Reply 6 years ago

Tecwyn, I know for sure I answered your question but somehow i cannot find the answer here.
Initially I used it with a 4m cable, pullup at both sides, but i have to admitt that i made the i2c just for practice. The pump is now directly triggered locally by the attiny


6 years ago


Can you explain better about this:

value= msbv<<8 | lsvb;

How can I add this to I2C master code to read moisture value?




Reply 6 years ago

Alberto what value= msbv<<8 | lsvb; does is that it combines 2 bytes into an integer.
The circuit reads 3 values: LDR, NTC and moisture. These are all 2 bytes (an integer) as their value theoretically could be between 0 and 1023.
However, the I2C command reads byte for byte. So I would have to send 3x2 = 6 bytes (2 for ldr, 2 for ntc and 2 for moisture)
As I didnt find the LDR and NTC value that important I decided to map them to 1 byte. That means that the value of the reading may not be higher than 255. So I have to make sure that my highest possible reading -1023- becomes 255 maximally. The Arduino language has the MAP command for that but that consumes a lot of memory. I might just as well just divide it by 4 as 1023/4=255 i.e 1 byte.

However, I didnt want to do that for the reading of the moisture as that is the most important value here, So I keep that as an integer (with a max value of 1023)
But as I need to store them in 1 byte registers, I can do that for the NTC and LDR value, but I have to split the moisture integer into two bytes: The 'Highbyte' and the 'Lowbyte'

The I2C thus reads 4 registers that are each one byte
reg2=Highbyte Moisture
reg3=Lowbyte Mositure

So on the arduino side, I get those 4 bytes. For the LDR and NTC I can just use those as I please, but for the Moisture reading I need to combine them again into 1 integer

It might be illustrative to take a real life example.
Suppose my moisture reading is 875
in binary that is

00000011 01101011

It is clear that the HighByte is 011 (=3)
and the LowByte is 01101011 (=107)
So reg2 will store '3' and reg3 wil store '107'.

Subsequently the Arduino will also receive '3' and '107'

The get the correct value 875, it cannot just add or multiply these 2 values.
It needs to put the high byte back in the highbyte position.
It does that by pushing the Highbyte 8 postions to the left
Thus 00000011 becomes 00000011 00000000 (=768) we then need to add the LowByte. We could just add 875+107=875, but on binary level, adding is nothing more than OR-ing the HIGH byte (which now has become an integer again) with the LOWByte

00000011 00000000
01101011 OR
00000011 01101011 =875

If you want to write the 8 position left bit shift and subsequent OR-ing in one go, that becomes:

value= msbv<<8 | lsvb; in which msbv = Most Significant Byte Value and lsvb = Least Significant Byte Value.
Most significant and least significant bytes are other names for HIGH Byte and LOW byte

So where do you put that in your receive program? Well, anywhere after reading the two values.
As the receive program is an example only, I just read the 4 registers from the I2C port and printed those.
If you want to 'DO' something with them you would have to read then in a register or read them seperately like:
and subsequently:
moistvalue=msbv<<8 | lsvb;

By the way, obviously having an LDR in that probe is kinda useless, but I added it just as an excercise in I2C. I may alter it in ored to say switch the pump


Reply 6 years ago

Well.. you did a great work to explain me the whole, you cant'belive how low is my level so I understand a little but I'm not able to make the most important thingh, write the right code...

I make the hardware and upload slave I2C code to Attiny, it seem work fine but I need some help for I2C master code to finish the work.

Can you write the whole master code to read moisture value?

Thank you !!!



Reply 6 years ago

Alberto, I already did, it is right there
What you want to do with that moisture value is up to you


Reply 6 years ago

Alberto, I have made one mistake in my previous explanation. I said the reading of the moisture would give a max value of 1023.
That is wrong, I still had an analog reading in mind. So yes the NTC and LDR reading goes to max 1023, but the reading from the capacitive probe in principle could go from 0 to 65535
But for the explanation itself that makes no difference, it just underlines how important it is to use the full integer


6 years ago

A solution for remote capacitive probes (avoiding parasitic capacitances due to the cable length):


"The impedances driving the capacitor are generally high (>500 kΩ),
and long wire lengths can pick up electric fields and induce spurious
voltages (especially from 60-Hz ac mains). Shielding the cable can help,
but this naturally creates additional capacitances that vary according
to cable length, adding to the capacitance measurement errors.

solution is an active shield, driven by an op amp to dynamically servo
the shield to the same voltage as the capacitor voltage, nulling the
capacitance from shield to capacitor voltage.
This remote capacitor
solution essentially requires three connections: shield, capacitor
voltage, and a separate wired ground return. The principle is similar to
the active pin capacitance compensation described earlier. The shield
can also be driven with a gain slightly higher than 1 to additionally
compensate for pin capacitance, though the chosen gain will necessarily
need to change with different cable lengths"


Reply 6 years ago

interesting. will look into that. The issues around the connective wires is exactly why i do the processing at the probe with an attiny


Reply 6 years ago

> ... is exactly why i do the processing at the probe with an attiny

Ok, but... how this corrects the issue of a extra capacitance induced by putting your hand close to one of the wires?


Reply 6 years ago

currently I have my attiny practically on top of the sensor and the reading is done at a distance with an arduino.
I did that to minimize the stray capacitance the environment could
induce. Not saying it totally avoids it. As such I am always open to
As I understood the article they give a solution for a
situation in which your processor is at a distance (mine isnt) and they
do that by adding the TS3002 locally, which they operate in such a way that the stray capacitance is compensated for although I am not entirely sure yet how they do that (need to study it a bit more

Pending my sensor tests, I may opt to integrate the processor PCB with the sensor PCB itself, solely to minimize stray capacity, Try to see if my hand does make a difference and if significant, add the solution as given in the article


Reply 6 years ago

> currently I have my attiny practically on top of the sensor and the reading is done at a distance with an arduino.

Ah, I missed that entirely. Thanks.