An Arduino Uno, capacitive moisture probe, and tiny peristaltic pump cooperate to keep one plant on target. The OLED dashboard shows live moisture %, 24-hour min/max, and pump runtime while a push button lets you prime the lines.
| Module | Pin | Arduino | Notes |
|---|---|---|---|
| OLED VCC | — | 5V | Display tolerates 3.3–5 V. |
| OLED GND | — | GND | Common ground for whole system. |
| OLED SDA | — | A4 | I²C data. |
| OLED SCL | — | A5 | I²C clock. |
| Moisture sensor VCC | — | 5V | Keep probe traces away from pump wiring. |
| Moisture sensor GND | — | GND | Shielded cable helps in noisy rooms. |
| Moisture sensor OUT | — | A0 | Analog read (0–1023). |
| Prime button | One side | 5V | Use 0.1 µF to ground if you see bouncing. |
| Prime button | Other side | D4 | 10 kΩ pull-down to GND. |
| MOSFET gate | — | D7 | 10 kΩ gate pull-down keeps pump off on reset. |
| MOSFET drain | — | Pump - | Diode from pump - to + (stripe to +). |
| Pump + | — | External 5V + | Do not draw pump current from USB. |
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
constexpr uint8_t OLED_WIDTH = 128;
constexpr uint8_t OLED_HEIGHT = 64;
constexpr uint8_t OLED_ADDR = 0x3C;
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, -1);
constexpr uint8_t MOIST_PIN = A0;
constexpr uint8_t PUMP_PIN = 7;
constexpr uint8_t PRIME_PIN = 4;
constexpr int MOIST_DRY = 880; // Update with your calibration
constexpr int MOIST_WET = 420; // Update with your calibration
constexpr int TARGET_PERCENT = 55;
constexpr unsigned long WATER_MS = 6000;
// Cooldown between watering cycles (15 minutes). Increase for larger pots/cool rooms,
// decrease for small pots/hot, fast-drying conditions.
constexpr unsigned long REST_MS = 15UL * 60UL * 1000UL;
unsigned long lastWater = 0;
unsigned long lastSample = 0;
int min24 = 101;
int max24 = -1;
void setup() {
pinMode(PUMP_PIN, OUTPUT);
digitalWrite(PUMP_PIN, LOW);
pinMode(PRIME_PIN, INPUT);
display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR);
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
}
void loop() {
if (millis() - lastSample > 5000) {
lastSample = millis();
int percent = moisturePercent();
min24 = min(min24, percent);
max24 = max(max24, percent);
drawScreen(percent);
if (shouldWater(percent)) runPump(WATER_MS);
}
if (digitalRead(PRIME_PIN) == HIGH) {
drawMessage("Priming pump");
runPump(3000);
}
}
int moisturePercent() {
int raw = analogRead(MOIST_PIN);
int percent = map(raw, MOIST_DRY, MOIST_WET, 0, 100);
percent = constrain(percent, 0, 100);
return percent;
}
bool shouldWater(int percent) {
if (percent < TARGET_PERCENT && millis() - lastWater > REST_MS) {
lastWater = millis();
return true;
}
return false;
}
void runPump(unsigned long duration) {
digitalWrite(PUMP_PIN, HIGH);
unsigned long start = millis();
while (millis() - start < duration) {
delay(10);
}
digitalWrite(PUMP_PIN, LOW);
}
void drawScreen(int percent) {
display.clearDisplay();
display.setCursor(0, 0);
display.setTextSize(2);
display.print("Soil ");
display.print(percent);
display.println("%");
display.setTextSize(1);
display.print("24h ");
display.print(min24);
display.print("% / ");
display.print(max24);
display.println("%");
display.print("Last water: ");
if (lastWater == 0) display.println("never");
else {
unsigned long ago = (millis() - lastWater) / 60000UL;
display.print(ago);
display.println(" min ago");
}
display.display();
}
void drawMessage(const char* msg) {
display.clearDisplay();
display.setTextSize(2);
display.setCursor(0, 16);
display.println(msg);
display.display();
}