Learning Goals 5 min
- Wire a rain-drop sensor board — a small PCB with interleaved copper traces — and read it as an analog moisture detector.
- Set two thresholds rather than one: a higher one to enter "raining" state and a lower one to leave it. This is your first hysteresis — the fix for sensors that chatter around a single threshold.
- Build a tiny "rain alarm" whose LED stays calmly ON during a light drizzle, instead of flickering at exactly the drizzle threshold.
Warm-Up 10 min
The rain-drop sensor is electrically almost identical to the soil probe: a couple of copper traces whose conductivity goes up when water bridges them. The difference is the form factor — a flat plate that catches falling drops, rather than two forks you push into dirt — and the use case: weather rather than gardening.
The threshold-chatter problem
Imagine a plain threshold sketch: "if reading < 700, it's raining". On the edge of a light drizzle the reading oscillates 695, 705, 692, 712, 698, 707 — every tick the sketch flips state. The LED strobes; the buzzer chatters; downstream code goes mad.
The fix is to widen the threshold into a band:
- Below 650 → definitely raining → LED on.
- Above 750 → definitely dry → LED off.
- Between 650 and 750 → keep doing whatever you were doing.
That gap is called hysteresis. The state is "sticky" — once you're in "raining" you have to climb well above the rain trigger to exit. Engineers use it everywhere: thermostats (heater off at 22 °C, on at 18 °C), Schmitt triggers, the Caps Lock light. Today it's your turn.
New Concept · Two thresholds > one 20 min
Wiring
| Sensor board | Connect to |
|---|---|
| VCC | +5 V |
| GND | GND |
| A0 (analog) | UNO A0 |
| D0 (digital) | Leave unconnected |
Some boards have two PCBs: the comparator board with the four pins, and a separate "collector plate" with two wires. Use a pair of jumper wires to connect the collector plate to the two screw terminals on the comparator board.
Mount the collector plate at a slight angle (~15°) so water runs off rather than pooling. A pooled plate stays "wet" for ages after the drizzle stops.
Calibration — quick & dirty
Don't bother with the full 5-second auto-calibration for this sensor. The behaviour is too binary — bone dry or genuinely wet, with very little in between — and the dry/wet baselines are stable enough across days that hard-coded thresholds work. Print a few raw readings while you drip water on the plate, then pick a HIGH and LOW that bracket your "raining" band:
raw: 1018 (bone dry) raw: 1015 (bone dry) raw: 712 (1 drop) raw: 540 (3 drops) raw: 320 (heavy)
From that, sensible thresholds: RAIN_ON = 650 (low value → wet enough to call it rain), RAIN_OFF = 800 (high value → dry enough to call it stopped). The 150-count gap is the hysteresis band.
The hysteresis pattern in code
bool isRaining = false; // remembers the current state
void loop() {
int raw = analogRead(RAIN);
if (!isRaining && raw < RAIN_ON) isRaining = true;
else if (isRaining && raw > RAIN_OFF) isRaining = false;
// otherwise: do nothing, stay in current state
digitalWrite(LED, isRaining ? HIGH : LOW);
}Two ideas wrapped together:
- State variable.
isRainingstays the same across loop iterations unless one of the two conditions is met. - Asymmetric thresholds. Different value to enter (
RAIN_ON = 650, raw must drop below 650) vs to leave (RAIN_OFF = 800, raw must rise above 800). The 150-count gap is what kills the chatter.
Worked Example · Rain alarm with hysteresis 20 min
Step 1 — wiring
Rain sensor on A0, LED on D9 with 220 Ω in series. Three wires for the sensor, two for the LED. Tilt the collector plate slightly so it sheds water.
Step 2 — the sketch
Save as rain-alarm.ino:
// L02-17: Rain alarm with hysteresis
const int RAIN = A0;
const int LED = 9;
const int RAIN_ON = 650; // raw must drop below this to enter "raining"
const int RAIN_OFF = 800; // raw must rise above this to exit "raining"
bool isRaining = false;
void setup() {
pinMode(LED, OUTPUT);
Serial.begin(9600);
}
void loop() {
int raw = analogRead(RAIN);
bool prevState = isRaining;
if (!isRaining && raw < RAIN_ON) isRaining = true;
else if (isRaining && raw > RAIN_OFF) isRaining = false;
digitalWrite(LED, isRaining ? HIGH : LOW);
if (isRaining != prevState) {
Serial.print(">> state change: ");
Serial.println(isRaining ? "RAINING" : "stopped");
}
Serial.print("raw: "); Serial.print(raw);
Serial.print(" state: "); Serial.println(isRaining ? "wet" : "dry");
delay(500);
}The prevState trick prints a one-line announcement only when the state flips — a clean way to log transitions without spamming on every reading.
Step 3 — test the chatter-free behaviour
Start with a dry plate:
raw: 1018 state: dry raw: 1015 state: dry
Now flick a single drop of water onto the plate with your finger:
raw: 612 state: wet >> state change: RAINING raw: 645 state: wet
Notice that raw = 645 is still < 800, so isRaining stays true. The LED stays calmly on instead of flickering. Now wipe the plate with a paper towel:
raw: 752 state: wet ← still in raining state, below RAIN_OFF raw: 815 state: dry >> state change: stopped
The reading had to climb all the way to 815 (above RAIN_OFF = 800) before we said "rain stopped".
Step 4 — try a single threshold to feel the difference
Temporarily change the if-chain to use a single threshold:
isRaining = (raw < 725); // single threshold — chatter incomingNow drip a drop on the plate near the threshold. You'll see the LED flicker as raw values oscillate ±20 either side of 725. Switch back to the two-threshold version and the flicker vanishes. That is hysteresis.
Step 5 — tune for your room
Different rain sensors give slightly different baselines depending on plate cleanliness and humidity. If your sensor never reads above ~900 even bone dry, lower RAIN_OFF to ~750. If it drips down to ~400 in a heavy spray, you might raise RAIN_ON to 700 to call lighter drizzle "raining". The pattern stays the same; only the numbers change.
Try It Yourself 20 min
Goal: Add a buzzer chirp on the moment of state change to "raining" (i.e. the very transition, not while it's raining). One short beep when the rain starts, one different short beep when it stops.
Hint
You already detect the transition with isRaining != prevState. Inside that if block, branch on which way the change went:
if (isRaining != prevState) {
if (isRaining) tone(8, 1500, 200); // rising chirp
else tone(8, 800, 300); // falling chirp
}Goal: Track the total time it's been raining today (in seconds). Print the running total in the "state change to stopped" line.
Hint
unsigned long rainStart = 0;
unsigned long totalRainMs = 0;
// in the transition block:
if (isRaining) {
rainStart = millis();
} else {
totalRainMs += millis() - rainStart;
Serial.print(">> total rain so far: ");
Serial.print(totalRainMs / 1000); Serial.println(" s");
}One millis() stamp at the start, one subtraction at the end. The running total is in milliseconds; divide by 1000 to print seconds. After a few cycles you have the day's raining time without storing a single reading.
Goal: Show rain intensity via PWM on an LED. Light drizzle → LED at 30% brightness; heavy rain → LED at 100%. Use the analog reading directly, not just the binary state.
Hint
Map the wet portion of the range (RAIN_ON down to a "heavy" threshold of ~300) into PWM 50–255:
int brightness = 0;
if (isRaining) {
int clipped = constrain(raw, 300, RAIN_ON);
brightness = map(clipped, RAIN_ON, 300, 50, 255);
}
analogWrite(LED, brightness);The LED brightness now visually mirrors how heavy the rain is. Combined with the hysteresis state, you get both "is it raining" (LED on/off) and "how heavily" (brightness).
Mini-Challenge · Bring-the-laundry-in alarm 15 min
A real Malaysian household problem: laundry is drying outside, the sky opens, you don't hear it from inside the house. Build the alarm:
- Rain sensor on A0.
- Loud buzzer on D8.
- Red LED on D9 (visible from the kitchen).
- Hysteresis: RAIN_ON = 700, RAIN_OFF = 850 (or whatever your sensor needs).
Behaviour spec:
- On boot, prints "laundry guard armed".
- While dry, LED off, buzzer silent.
- The instant rain is detected (state change to wet): three urgent beeps + LED on.
- While it keeps raining: LED stays on. Buzzer chirps once every 60 s as a reminder (in case you slept through the first three beeps).
- When rain stops: LED off, buzzer silent, one happy chime, prints "all clear — total rain time: X s".
It's done when:
- The three opening beeps fire exactly once per rain event, not on every loop iteration.
- The 60-second reminder cycle works without using
delay(60000). - The all-clear chime fires once when rain stops, and the total-time printout is accurate.
- The LED never flickers around the threshold — confirm by dripping water slowly.
Reveal one valid sketch
const int RAIN = A0;
const int LED = 9;
const int BUZZ = 8;
const int RAIN_ON = 700;
const int RAIN_OFF = 850;
bool isRaining = false;
unsigned long rainStart = 0;
unsigned long lastReminder = 0;
const unsigned long REMINDER_MS = 60000;
void chirpStart() {
for (int i = 0; i < 3; i++) {
tone(BUZZ, 1800, 120);
delay(180); // small blocking burst is okay at start
}
}
void chimeStop() {
tone(BUZZ, 1000, 250);
}
void setup() {
pinMode(LED, OUTPUT);
Serial.begin(9600);
Serial.println("laundry guard armed");
}
void loop() {
int raw = analogRead(RAIN);
bool prev = isRaining;
if (!isRaining && raw < RAIN_ON) isRaining = true;
else if (isRaining && raw > RAIN_OFF) isRaining = false;
digitalWrite(LED, isRaining ? HIGH : LOW);
if (isRaining && !prev) {
chirpStart();
rainStart = millis();
lastReminder = millis();
Serial.println(">> RAIN — bring it in!");
} else if (!isRaining && prev) {
chimeStop();
unsigned long total = (millis() - rainStart) / 1000;
Serial.print(">> all clear — total rain time: ");
Serial.print(total); Serial.println(" s");
} else if (isRaining && millis() - lastReminder >= REMINDER_MS) {
tone(BUZZ, 1500, 150);
lastReminder = millis();
}
delay(200);
}Four states in one loop — entering rain, leaving rain, staying-in-rain reminder, staying-dry — all handled by the prev/now pattern plus a millis() gate. A real product would also log to SD or send a phone notification; we'll cover both in Cluster G and Level 3.
Recap 5 min
The rain-drop sensor is a thin board whose resistance drops sharply when water bridges its traces. Wired as a simple analog input it's easy to read; the new lesson today was hysteresis — using two thresholds (one to enter the active state, one to leave it) to keep the output calm when the reading hovers near a single value. This is a general-purpose tool: it appears in thermostats, day/night detectors, motor controllers, and yes, rain alarms. The other new pattern was the "detect a state transition" trick — saving the previous state, comparing it with the new state, and acting only on the change.
- Rain-drop sensor
- A board with interleaved copper traces that catches falling drops; resistance drops when water bridges the traces. Cheap, corrodes outdoors.
- Hysteresis
- Using two thresholds — one to enter a state, one to leave it — so the output stays calm when the input hovers near a single value. Kills threshold chatter.
- State variable
- A variable that remembers what mode the sketch is in (
isRaining,doorOpen, etc.) across loop iterations. Needs to survive between loops — declare it global orstatic. - Transition detection
- Saving the previous value of a state variable and comparing it to the new value, so you can run code only when the state changes rather than on every loop iteration.
- Schmitt trigger
- A hardware comparator with built-in hysteresis. We just implemented one in software.
- Collector plate
- The flat PCB that catches rain drops, separate from the small comparator board with the four pins. Mount it tilted so water runs off.
Homework 5 min
Real-world hysteresis hunt. Hysteresis is hiding in many objects you use every day. Find three real examples — write one sentence each on what triggers the "on" transition and what triggers the "off":
- A household thermostat (your home or aircon).
- The auto-headlight setting on a car.
- Your phone's screen auto-brightness, or the Caps Lock light, or anything similar you can think of.
Then design (don't build) a fourth example of your own: a project that would benefit from hysteresis if you built it. Write 3–4 sentences: what does it sense, what does it do, what would the chatter look like with a single threshold, why two thresholds are better.
Bring back next class:
- Your three real-world hysteresis examples.
- Your fourth original example.
- Your
hw-l02-17.inosketch — tomorrow we use the piezo as a knock sensor and you'll find another use for state transitions.