A capacitive probe has no exposed metal, so it doesn’t corrode like the cheap two-prong sensors. It outputs an analog voltage — higher when dry, lower when wet — which the Pico W reads on GP26 (ADC0).
Power it from 3V3, run the script, and note the raw number in air and in water. Those two values turn the reading into a useful 0–100 % moisture figure.
Wiring
| Raspberry Pi Pico W | Capacitive soil moisture sensor v1.2 | Wire | Notes |
|---|---|---|---|
| GND | GND | black | |
| 3V3 | VCC | red | 3V3 is pin 36. At 3.3 V the output stays well under the ADC’s 3.3 V limit. |
| GP26 | AOUT | yellow | GP26 = ADC0, physical pin 31. |
3.3–5.5 V part, 3.3 V board. The output is an analog voltage that stays below the supply. Power it from 3.3 V on ESP32 and Pico so AOUT can never exceed the ADC input range; on an Uno 5 V is fine.
Board quirks
- Every probe reads differently. Note the raw value in dry air and in a glass of water and put them in
DRYandWET. - Some v1.2 boards ship with an NE555 instead of a TLC555 timer, or without the regulator, and barely respond at 3.3 V — if raw hardly changes between air and water, that is why.
- Seal the top edge and the electronics with hot glue or nail polish: monsoon humidity creeps into the exposed board and the reading drifts.
- The on-board LED lights below 30 %. For phone alerts over Wi-Fi, see the soil-moisture project on this site.
Code
# Capacitive soil moisture sensor v1.2 on a Raspberry Pi Pico W (MicroPython).
# Wiring: VCC -> 3V3 (pin 36), GND -> GND, AOUT -> GP26 / ADC0 (pin 31).
# Calibrate: note the raw value with the probe in dry air (DRY) and in a glass of water (WET).
from machine import ADC, Pin
import time
DRY = 52000 # raw reading in dry air <- replace with yours
WET = 21000 # raw reading in water <- replace with yours
sensor = ADC(26)
led = Pin("LED", Pin.OUT)
def moisture():
raw = sum(sensor.read_u16() for _ in range(16)) // 16
pct = (DRY - raw) * 100 // (DRY - WET)
return max(0, min(100, pct)), raw
print("Soil moisture on GP26 - put the probe in dry air, then water, to calibrate")
while True:
pct, raw = moisture()
led.value(pct < 30) # LED on = needs water
print("Moisture {:3d} % (raw {})".format(pct, raw))
time.sleep(2)# Capacitive soil moisture sensor v1.2 on a Raspberry Pi Pico W (MicroPython).
# Wiring: VCC -> 3V3 (pin 36), GND -> GND, AOUT -> GP26 / ADC0 (pin 31).
# Calibrate: note the raw value with the probe in dry air (DRY) and in a glass of water (WET).
from machine import ADC, Pin
import time
DRY = 52000 # raw reading in dry air <- replace with yours
WET = 21000 # raw reading in water <- replace with yours
sensor = ADC(26)
led = Pin("LED", Pin.OUT)
def moisture():
raw = sum(sensor.read_u16() for _ in range(16)) // 16
pct = (DRY - raw) * 100 // (DRY - WET)
return max(0, min(100, pct)), raw
print("Soil moisture on GP26 - put the probe in dry air, then water, to calibrate")
while True:
pct, raw = moisture()
led.value(pct < 30) # LED on = needs water
print("Moisture {:3d} % (raw {})".format(pct, raw))
time.sleep(2)Expected serial output
Soil moisture on GP26 - put the probe in dry air, then water, to calibrate
Moisture 0 % (raw 52310)
Moisture 64 % (raw 32150)
Moisture 98 % (raw 21540)Soil moisture on GP26 - put the probe in dry air, then water, to calibrate Moisture 0 % (raw 52310) Moisture 64 % (raw 32150) Moisture 98 % (raw 21540)
Common errors
Always 0 % or always 100 %
DRY and WET are still the example values. Replace them with your own readings.
Raw value barely changes between air and water
Probably a faulty batch (wrong timer chip or missing regulator). Try the probe at 5 V on another board to confirm, then buy from a different seller.