Project 5.3: "Automatic Irrigation System"
What you’ll learn
- ✅ Read soil moisture: Measure soil wet/dry using an analog sensor.
- ✅ Control a “pump”: Turn an L9110 fan module on/off to simulate irrigation.
- ✅ Automate by threshold: Water only when the soil is too dry.
- ✅ Timed watering: Run the pump for a set number of seconds.
- ✅ App-ready format: Print clear status messages that an app can read later.
Key ideas
- Short definition: An analog sensor gives numbers; we use a threshold to decide when to turn the pump on.
- Real-world link: Automatic sprinklers water plants only when moisture is low.
Blocks glossary (used in this project)
- Analog input (ADC): Reads a changing voltage from the soil sensor and returns a number.
- Digital output / PWM: Controls the L9110 fan module as a simple on/off “pump” or variable speed.
- if / else: Makes decisions based on moisture level.
- Delay ms / seconds: Runs the pump for a set time, then stops.
- Map / constrain: Converts sensor values to a clean 0–100% range for display.
- Serial println: Shows status so students can see what the robot is doing.
What you need
| Part | How many? | Pin connection |
|---|---|---|
| D1 R32 | 1 | USB cable (30 cm) |
| Soil humidity sensor | 1 | Signal → Pin 32 (ADC), VCC, GND |
| L9110 fan module (pump sim) | 1 | IN1 → Pin 23 (pump control), VCC, GND |
🔌 Wiring tip: Keep sensor wires short and stable. Connect soil sensor signal to Pin 32 (ADC). Connect L9110 IN1 to Pin 23 to switch the fan on/off.
📍 Pin map snapshot: Using pins 32 (ADC) and 23 (pump). Other pins remain free for future display or app integration.
Before you start
- USB cable is plugged in
- Serial monitor is open
- Test print shows:
print("Ready!") # Confirm serial is working
Microprojects (5 mini missions)
Microproject 5.3.1 – Soil moisture reading
Goal: Read raw moisture values from the sensor.
Blocks used:
- Analog input (ADC): Get a number from Pin 32.
- Serial println: Show the reading.
Block sequence:
- Setup ADC on Pin 32.
- Read value.
- Print it.
MicroPython code:
import machine # Import machine to access ADC hardware
adc32 = machine.ADC(machine.Pin(32)) # Create ADC on Pin 32 for soil sensor
adc32.atten(machine.ADC.ATTN_11DB) # Set attenuation for wider voltage range
adc32.width(machine.ADC.WIDTH_12BIT) # Use 12-bit resolution (0–4095)
raw = adc32.read() # Read raw analog value from sensor
print("Microproject 5.3.1: Soil raw value =", raw) # Show the sensor number to understand scale
Reflection: Numbers tell the moisture story—write them down and learn what “dry” vs “wet” looks like.
Challenge:
- Easy: Read three times and print the average.
- Harder: Add a second print that labels “DRY” if raw is above your chosen threshold.
Microproject 5.3.2 – “Pump” activation by threshold
Goal: Turn the pump on when the soil is too dry.
Blocks used:
- if / else: Decide based on the raw value.
- Digital output: Control Pin 23 to switch the L9110.
Block sequence:
- Create pump pin on 23.
- Read ADC.
- If dry (raw high), pump ON; else OFF.
- Print status.
MicroPython code:
import machine # Import machine for Pin and ADC control
adc32 = machine.ADC(machine.Pin(32)) # ADC on Pin 32 for soil sensor
adc32.atten(machine.ADC.ATTN_11DB) # Configure attenuation for proper scaling
adc32.width(machine.ADC.WIDTH_12BIT) # Set ADC resolution to 12-bit
pump = machine.Pin(23, machine.Pin.OUT) # Create pump control pin on 23
raw = adc32.read() # Read soil moisture raw value
print("Microproject 5.3.2: Soil raw =", raw) # Print raw value for decision context
threshold = 2500 # Example dry threshold (adjust for your sensor)
print("Using threshold =", threshold) # Explain the chosen threshold
if raw >= threshold: # Check if soil is dry (raw high means less moisture on many sensors)
pump.value(1) # Turn pump ON
print("Pump: ON (soil is dry)") # Tell student why pump turned on
else: # Otherwise soil is not dry
pump.value(0) # Turn pump OFF
print("Pump: OFF (soil is wet enough)") # Explain the off state
Reflection: A threshold is a simple rule—above means “do it,” below means “don’t.”
Challenge:
- Easy: Change threshold to a value that matches your plant’s needs.
- Harder: Print “Needs water” or “OK” as clear labels.
Microproject 5.3.3 – Irrigation for a set time
Goal: Run the pump for N seconds, then stop.
Blocks used:
- Digital output: Switch pump on/off.
- Delay seconds: Wait N seconds.
Block sequence:
- Set watering_time = 5 seconds.
- Pump ON.
- Delay N seconds.
- Pump OFF.
- Print start/end.
MicroPython code:
import machine # Import machine for Pin control
import time # Import time for delays
pump = machine.Pin(23, machine.Pin.OUT) # Create pump control pin on 23
watering_time = 5 # Choose irrigation duration in seconds
print("Microproject 5.3.3: Watering for", watering_time, "seconds") # Announce watering plan
pump.value(1) # Turn pump ON to start irrigation
print("Pump: ON") # Confirm pump state
time.sleep(watering_time) # Wait for the set irrigation time
pump.value(0) # Turn pump OFF after watering
print("Pump: OFF (watering finished)") # Confirm completion
Reflection: Time control helps plants get a consistent sip—neither too much nor too little.
Challenge:
- Easy: Change watering_time to 3 seconds.
- Harder: Print a countdown each second.
Microproject 5.3.4 – Continuous monitoring and automatic irrigation
Goal: Check soil repeatedly; water only if dry.
Blocks used:
- Loop: Repeat forever.
- if / else: Decide based on threshold.
- Delay seconds: Pace the checks.
Block sequence:
- Setup ADC and pump.
- Loop: read, compare, act, print.
- Delay between checks.
MicroPython code:
import machine # Import machine for ADC and Pin
import time # Import time for delays
adc32 = machine.ADC(machine.Pin(32)) # ADC on Pin 32 for soil sensor
adc32.atten(machine.ADC.ATTN_11DB) # Configure ADC attenuation
adc32.width(machine.ADC.WIDTH_12BIT) # Set ADC resolution
pump = machine.Pin(23, machine.Pin.OUT) # Pump pin on 23
threshold = 2500 # Dry threshold (adjust after testing)
print("Microproject 5.3.4: Auto-irrigation start, threshold =", threshold) # Announce auto mode
while True: # Begin continuous monitoring loop
raw = adc32.read() # Read current soil value
print("Soil raw =", raw) # Show current reading
if raw >= threshold: # If soil is dry
pump.value(1) # Turn pump ON
print("Pump: ON (auto mode)") # Explain action
time.sleep(2) # Run for 2 seconds per cycle
pump.value(0) # Turn pump OFF
print("Pump: OFF (cycle complete)") # Explain completion
else: # Soil is wet enough
pump.value(0) # Keep pump OFF
print("Pump: OFF (soil OK)") # Explain status
time.sleep(3) # Wait before next reading to avoid rapid toggling
Reflection: Regular checks prevent overwatering—short cycles are gentler than long blasts.
Challenge:
- Easy: Change the pump run time to 1 second.
- Harder: Add a “cooldown” period (e.g., 30 seconds) after watering.
Microproject 5.3.5 – Integration with app for remote control (format-ready)
Goal: Print clean status strings your app can read later.
Blocks used:
- Serial println: Show “STATUS” and “PUMP” messages.
- Variable: Store current moisture percent.
Block sequence:
- Map raw to 0–100% (inverted if needed).
- Print “STATUS:OK” or “STATUS:DRY”.
- Print “PUMP:ON” or “PUMP:OFF”.
MicroPython code:
import machine # Import machine for ADC
adc32 = machine.ADC(machine.Pin(32)) # ADC on Pin 32 for soil sensor
adc32.atten(machine.ADC.ATTN_11DB) # Configure ADC range
adc32.width(machine.ADC.WIDTH_12BIT) # Use 12-bit resolution
raw = adc32.read() # Read current soil value
print("Microproject 5.3.5: Raw =", raw) # Show raw value
moisture_pct = int((4095 - raw) * 100 / 4095) # Convert to percent (higher=wet)
print("Moisture % =", moisture_pct) # Show mapped moisture percent
threshold_pct = 40 # Choose 40% as dryness threshold (adjust after testing)
print("Threshold % =", threshold_pct) # Explain threshold
status = "STATUS:DRY" if moisture_pct < threshold_pct else "STATUS:OK" # Build status string
print(status) # Print status in app-friendly format
pump_state = "PUMP:ON" if status == "STATUS:DRY" else "PUMP:OFF" # Build pump state string
print(pump_state) # Print pump state for app display
Reflection: Clear “KEY:VALUE” strings make app integration easy and reliable.
Challenge:
- Easy: Change threshold_pct to 30.
- Harder: Add “ALERT:LOW” when moisture < 20%.
Main project – Automatic irrigation system
Blocks steps (with glossary)
- Analog input (ADC): Read soil moisture from Pin 32.
- Map to percent: Convert raw 0–4095 to 0–100% for human-friendly display.
- Decision (if): Compare moisture to threshold.
- Digital output: Control L9110 on Pin 23 as the pump switch.
- Timed cycle: Keep the pump on briefly, then off, repeat.
- Serial messages: Print “STATUS” and “PUMP” for monitoring/app use.
Block sequence:
- Setup ADC (Pin 32) and pump output (Pin 23).
- Read raw moisture and map to % (invert if needed).
- If moisture < threshold% → pump ON for N seconds → pump OFF.
- Else → pump OFF, print “OK”.
- Loop with a delay to avoid rapid toggling.
MicroPython code (mirroring blocks)
# Project 5.3 – Automatic Irrigation System
import machine # Access ADC and Pin for hardware control
import time # Provide delays for timing
adc32 = machine.ADC(machine.Pin(32)) # Create ADC on Pin 32 (soil sensor signal)
adc32.atten(machine.ADC.ATTN_11DB) # Set attenuation for full-scale readings
adc32.width(machine.ADC.WIDTH_12BIT) # Use 12-bit resolution (0–4095)
print("ADC ready on Pin 32") # Confirm sensor input setup
pump = machine.Pin(23, machine.Pin.OUT) # Create pump control pin on 23
pump.value(0) # Ensure pump starts OFF
print("Pump pin ready on Pin 23 (OFF)") # Confirm pump setup
threshold_pct = 40 # Moisture percent threshold for watering
watering_time = 3 # Seconds pump stays ON during each watering cycle
check_delay = 5 # Seconds between soil checks
print("Threshold % =", threshold_pct) # Explain threshold to student
print("Watering time (s) =", watering_time) # Explain watering duration
print("Check delay (s) =", check_delay) # Explain monitoring interval
while True: # Begin continuous automatic irrigation loop
raw = adc32.read() # Read current soil raw value
print("Soil raw =", raw) # Show raw sensor value
moisture_pct = int((4095 - raw) * 100 / 4095) # Map raw to percent (higher number = wetter)
print("Moisture % =", moisture_pct) # Show mapped moisture percent
if moisture_pct < threshold_pct: # If soil is too dry
print("STATUS:DRY") # Status message for app/monitor
pump.value(1) # Turn pump ON to irrigate
print("PUMP:ON for", watering_time, "seconds") # Explain action
time.sleep(watering_time) # Keep pump ON for the set irrigation time
pump.value(0) # Turn pump OFF after watering
print("PUMP:OFF (cycle complete)") # Confirm completion
else: # Soil moisture is acceptable
print("STATUS:OK") # Status message for app/monitor
pump.value(0) # Ensure pump remains OFF
print("PUMP:OFF (soil OK)") # Confirm off state
time.sleep(check_delay) # Wait before the next reading to avoid chattering
External explanation
- What it teaches: Your robot measures moisture, decides if plants need water, and runs the “pump” briefly.
- Why it works: The ADC turns sensor voltage into a number; we map it to percent, compare to a threshold, and switch a pin to control the L9110 for a timed cycle.
- Key concept: “Measure → decide → act → wait.”
Story time
You’ve built a guardian gardener. It checks the soil like a careful friend and gives water only when your plant asks for it.
Debugging (2)
Debugging 5.3.A – Humidity sensor gives incorrect readings
Problem: Values look upside down (wet reads “dry”) or jump around.
Clues: High numbers when the soil is wet; prints don’t match reality.
Broken code:
moisture_pct = int(raw * 100 / 4095) # Not inverted (may be wrong for your sensor)
print("Moisture % =", moisture_pct) # Prints misleading percent
Fixed code:
moisture_pct = int((4095 - raw) * 100 / 4095) # Invert if wet=low raw, dry=high raw
print("Moisture % =", moisture_pct) # Now matches wet/dry behavior
Why it works: Many soil sensors output lower voltage when wet; inverting aligns percent with reality.
Avoid next time: Test wet vs dry first; adjust mapping and threshold accordingly.
Debugging 5.3.B – Pump does not activate
Problem: Even when dry, the pump stays OFF.
Clues: Serial prints “STATUS:DRY” but “PUMP:OFF.”
Broken code:
if moisture_pct < threshold_pct: # Condition says DRY
# pump.value(1) # Forgot to turn ON
print("PUMP:ON") # Message shows ON, but pin never changed
Fixed code:
if moisture_pct < threshold_pct: # Condition says DRY
pump.value(1) # Actually turn pump ON
print("PUMP:ON") # Confirm action
Why it works: Printing isn’t control—setting the pin changes the real hardware state.
Avoid next time: Always set the pin value and confirm with a serial message.
Final checklist
- I saw raw and percent moisture values
- The pump turned ON when moisture was below threshold
- The pump turned OFF after the set time
Extras
- 🧠 Student tip: Add “MODE:MANUAL” to force a single watering cycle when you press a button.
- 🧑🏫 Instructor tip: Have students record raw values in wet/dry tests to set a realistic threshold.
- 📖 Glossary:
- ADC (Analog-to-Digital Converter): Turns voltage into a number the board can read.
- Threshold: The line where the robot decides to act.
- L9110 module: A driver that switches DC motors or a small fan.
- 💡 Mini tips:
- Stabilize readings by averaging 3–5 samples.
- Add a cooldown (e.g., 30–60 s) between waterings.
- Keep serial prints short and clear for easy app parsing.
Hard stop rule respected: All sections included, and every code line has a comment or a clear, explanatory print message.