Breadboard
docs
Example project · Sensors

Plant monitor (soil + OLED)

A soil-moisture probe on the ADC, its reading shown as a percentage and bar on the SH1106 OLED. Drag the probe’s Moisture slider in the Inspector to water the plant.

The Plant monitor (soil + OLED) circuit as rendered by the simulator

How it works

A capacitive soil probe outputs an analog voltage that falls as the soil gets wetter: about 2.5 V bone dry down to ~0.2 V saturated. The C3 reads it on G2 with the real ADC (11 dB attenuation for the full range) and maps the voltage to a percentage with the calibration line (2.5 − v) / 2.3. The 1.3″ SH1106 draws the number and a bar graph; note the driver's +2 column offset, the classic SH1106 quirk that shifts its RAM window relative to the SSD1306.

What's on the bench

  • Battery
  • ESP32-C3
  • OLED 1.3″
  • Soil Moisture

How it's wired

  • Battery · posESP32-C3 · vin
  • Battery · negESP32-C3 · gnd
  • ESP32-C3 · 3v3OLED 1.3″ · vcc
  • ESP32-C3 · gnd2OLED 1.3″ · gnd
  • ESP32-C3 · g9OLED 1.3″ · scl
  • ESP32-C3 · g8OLED 1.3″ · sda
  • ESP32-C3 · 3v3Soil Moisture · vcc
  • ESP32-C3 · gndSoil Moisture · gnd
  • ESP32-C3 · g2Soil Moisture · aout

The code

This MicroPython script runs on the emulated board every boot; edit it in the Code tab.

from machine import Pin, I2C
import framebuf, time
i = I2C(0, scl=Pin(9), sda=Pin(8))
for c in (0xAE,0xD5,0x80,0xA8,0x3F,0xAD,0x8B,0xA1,0xC8,0x81,0xCF,0xA6,0xAF):
    i.writeto(0x3c, bytes([0x00, c]))
buf = bytearray(1024)
fb = framebuf.FrameBuffer(buf, 128, 64, framebuf.MONO_VLSB)
def show():
    for page in range(8):
        i.writeto(0x3c, bytes([0x00, 0xB0 | page, 0x02, 0x10]))
        i.writeto(0x3c, bytes([0x40]) + buf[page * 128:(page + 1) * 128])

from machine import ADC
adc = ADC(Pin(2))
adc.atten(ADC.ATTN_11DB)
while True:
    v = adc.read_u16() / 65535 * 3.3
    pct = max(0, min(100, int((2.5 - v) / 2.3 * 100)))
    fb.fill(0)
    fb.text('PLANT MONITOR', 8, 4)
    fb.text('Moisture', 8, 24)
    fb.text('%d%%' % pct, 8, 42)
    fb.rect(60, 42, 60, 9, 1)
    fb.fill_rect(60, 42, int(60 * pct / 100), 9, 1)
    show()
    print('moisture', pct)
    time.sleep_ms(400)

Try this

  • Drag the probe's Moisture slider up: watering the plant raises the percentage and fills the bar.
  • Hover the AOUT pin while you drag: the voltage falls as moisture rises.
  • Change the calibration constants in the script and re-run to suit an imaginary sensor.
  • Watch the serial tab log a moisture reading every 400 ms.

Related projects