Digital ChallengeArduino × Processing
Workbook

42 exercises.
Zero busywork.

Predict, calculate, build, code, debug and design. Every exercise has a time estimate, an optional hint and a full solution. Mark exercises as done — progress is saved in this browser.

Basic · 14Intermediate · 13Advanced · 8Challenge · 7≈ 17 lessons of practice
0/ 42 exercises completed
Module
Level
Type
Status
F1

First Steps with Arduino

open module →
F1.1BasicPredict 5 minF1 · Arduino Basics

Predict the rhythm

Without running it, describe exactly what the LED does with this loop(). How many times does it flash per minute?

starterArduino · C++
void loop() {
  digitalWrite(8, HIGH);
  delay(200);
  digitalWrite(8, LOW);
  delay(800);
}
Add the two delays to get the length of one full cycle.
The LED flashes briefly (on for 0.2 s, off for 0.8 s). One cycle lasts 200 + 800 = 1000 ms = 1 s, so it flashes 60 times per minute. The duty cycle (time on ÷ total time) is 20 %.
F1.2BasicCalculate 10 minF1 · Arduino Basics

Choose the resistor

A blue LED needs about 3.0 V and should run at 10 mA. The Arduino pin gives 5 V. Calculate the ideal resistor, then choose a real value from this list: 100 Ω, 150 Ω, 220 Ω, 330 Ω, 1 kΩ.

R = (V_supply − V_LED) / I. Remember 10 mA = 0.010 A. Always round up to the next available value.
R = (5.0 − 3.0) / 0.010 = 200 Ω. The closest standard value that is not smaller is 220 Ω — the current will then be (2.0 / 220) ≈ 9.1 mA, which is safe.
F1.3IntermediateCode 15 minF1 · Arduino Basics

SOS beacon

Program the external LED to blink SOS in Morse code: three short (200 ms), three long (600 ms), three short — then a 2-second pause. Use a helper function so you don’t repeat yourself.

Write void flash(int ms) that turns the LED on for ms, then off for 200 ms. Call it inside for loops.
SOS.inoArduino · C++
const int ledPin = 8;

void flash(int ms) {
  digitalWrite(ledPin, HIGH);
  delay(ms);
  digitalWrite(ledPin, LOW);
  delay(200);
}

void setup() {
  pinMode(ledPin, OUTPUT);
}

void loop() {
  for (int i = 0; i < 3; i++) flash(200);  // S
  for (int i = 0; i < 3; i++) flash(600);  // O
  for (int i = 0; i < 3; i++) flash(200);  // S
  delay(2000);
}
F1.4IntermediateDebug 10 minF1 · Arduino Basics

Find three bugs

This sketch should blink an LED on pin 8 but it does not even compile — and even when it compiles, the LED stays dark. Find all three problems.

starterArduino · C++
const int ledPin = 8

void setup() {
  pinMode(ledPin, INPUT);
}

void loop() {
  digitalWrite(ledPin, HIGH);
  delay(500);
  digitalWrite(ledpin, LOW);
  delay(500);
}
Look for a missing character, a wrong mode, and a spelling difference (C++ is case-sensitive).
1) Missing semicolon after const int ledPin = 8. 2) pinMode must be OUTPUT, not INPUT. 3) ledpin ≠ ledPin — names are case-sensitive.
F1.5AdvancedBuild 20 minF1 · Arduino Basics

Push-button lamp

Add a push button between pin 2 and GND. The LED on pin 8 should be ON only while the button is held down. Use the internal pull-up resistor.

  1. 01Place the button across the middle gap of the breadboard.
  2. 02Connect one side to pin 2 and the other side to GND.
  3. 03In setup() use pinMode(2, INPUT_PULLUP);.
  4. 04Remember: with a pull-up, the pin reads LOW when pressed.
ButtonLamp.inoArduino · C++
const int ledPin = 8;
const int buttonPin = 2;

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(buttonPin, INPUT_PULLUP);
}

void loop() {
  bool pressed = digitalRead(buttonPin) == LOW;
  digitalWrite(ledPin, pressed ? HIGH : LOW);
}
F1.6ChallengeDesign 30 minF1 · Arduino Basics

Traffic light controller

Build a traffic light with red (pin 10), yellow (pin 9) and green (pin 8) LEDs, each with its own 220 Ω resistor. Sequence: green 4 s → yellow 1 s → red 4 s → red + yellow 1 s → repeat. Extension: add a pedestrian button that shortens the green phase.

Write a helper void lights(bool r, bool y, bool g, int ms) that sets all three LEDs and then waits.
TrafficLight.inoArduino · C++
const int RED = 10, YELLOW = 9, GREEN = 8;

void lights(bool r, bool y, bool g, int ms) {
  digitalWrite(RED, r);
  digitalWrite(YELLOW, y);
  digitalWrite(GREEN, g);
  delay(ms);
}

void setup() {
  pinMode(RED, OUTPUT);
  pinMode(YELLOW, OUTPUT);
  pinMode(GREEN, OUTPUT);
}

void loop() {
  lights(0, 0, 1, 4000);  // green
  lights(0, 1, 0, 1000);  // yellow
  lights(1, 0, 0, 4000);  // red
  lights(1, 1, 0, 1000);  // red + yellow
}
F2

Processing — Real-Time Visualization

open module →
F2.1BasicCalculate 10 minF2 · Processing Basics

map() by hand

Calculate each result without a computer, then check with the playground above.

a) map(512, 0, 1023, 0, 300)
b) map(25, 0, 50, 0, 255)
c) map(750, 1023, 300, 0, 100)

result = lo2 + (value − lo1) × (hi2 − lo2) / (hi1 − lo1). In (c) the input range is reversed — that’s fine.
a) 512 × 300 / 1023 ≈ 150.1 px · b) 25 × 255 / 50 = 127.5 · c) (750 − 1023) × 100 / (300 − 1023) = (−273)(100)/(−723) ≈ 37.8 %
F2.2BasicPredict 5 minF2 · Processing Basics

Where will it appear?

In a size(400, 300) window, describe where each shape appears: ellipse(200, 150, 40, 40), rect(0, 280, 400, 20), ellipse(width, 0, 60, 60).

The first circle is exactly in the centre. The rectangle is a 20 px strip along the bottom edge. The last circle is centred on the top-right corner, so only a quarter of it is visible.
F2.3IntermediateCode 15 minF2 · Processing Basics

Bouncing ball

No hardware: make a ball move across the window and bounce off all four walls. Change its colour every time it hits a wall.

Store position (x, y) and speed (dx, dy). Each frame add the speed. If x < 0 or x > width, flip the sign of dx.
Bounce.pdeProcessing · Java
float x = 100, y = 100, dx = 3, dy = 2;
color c = color(61, 245, 255);

void setup() {
  size(500, 300);
  noStroke();
}

void draw() {
  background(20);
  x += dx;
  y += dy;
  if (x < 15 || x > width - 15)  { dx = -dx; c = color(random(255), random(255), random(255)); }
  if (y < 15 || y > height - 15) { dy = -dy; c = color(random(255), random(255), random(255)); }
  fill(c);
  ellipse(x, y, 30, 30);
}
F2.4IntermediateDebug 10 minF2 · Processing Basics

Why is the screen blank?

Arduino is sending numbers, but this sketch shows nothing and sometimes crashes with a NullPointerException. Find the problems.

starterProcessing · Java
import processing.serial.*;
Serial myPort;
int value;

void setup() {
  size(400, 200);
  myPort = new Serial(this, Serial.list()[0], 115200);
}

void draw() {
  fill(0);
  text(value, 50, 100);
}

void serialEvent(Serial p) {
  value = int(trim(p.readStringUntil('\n')));
}
Compare with Lab 2: what speed does Arduino use? What happens to old text when there is no background()? What if the line is not complete yet?
1) Baud rate is 115200 but Arduino uses 9600. 2) No background() in draw(), so numbers pile on top of each other (and default fill is drawn on grey — text may be unreadable). 3) readStringUntil() can return null — check before trim(). Adding myPort.bufferUntil('\n') in setup() also helps.
F2.5AdvancedCode 25 minF2 · Processing Basics

Real-time line graph

Extend Lab 2: keep the last 100 received values in an array and draw them as a scrolling line graph across the window.

Shift every element one place left (vals[i] = vals[i+1]) and put the new value at the end. Use map() for both X (index → width) and Y (value → height, reversed!).
LiveGraph.pdeProcessing · Java
import processing.serial.*;

Serial myPort;
int[] vals = new int[100];

void setup() {
  size(600, 300);
  myPort = new Serial(this, Serial.list()[0], 9600);
  myPort.bufferUntil('\n');
}

void draw() {
  background(15);
  stroke(61, 245, 255);
  strokeWeight(2);
  noFill();
  beginShape();
  for (int i = 0; i < vals.length; i++) {
    float x = map(i, 0, vals.length - 1, 20, width - 20);
    float y = map(vals[i], 0, 100, height - 20, 20);   // reversed: bigger = higher
    vertex(x, y);
  }
  endShape();
}

void serialEvent(Serial p) {
  String s = p.readStringUntil('\n');
  if (s == null) return;
  s = trim(s);
  if (!s.matches("\\d+")) return;
  for (int i = 0; i < vals.length - 1; i++) vals[i] = vals[i + 1];
  vals[vals.length - 1] = int(s);
}
F2.6ChallengeDesign 30 minF2 · Processing Basics

Three-value protocol

Design your own message format for three values (e.g. x,y,button from a joystick). Write the Arduino side with fake values from random(), and a Processing sketch that moves a circle with x/y and changes its colour when button = 1. Your parser must ignore malformed lines.

Check values.length == 3 and wrap each conversion. Test robustness by sending a broken line such as 12,, or hello.
ThreeValues.pde (receiver)Processing · Java
import processing.serial.*;
Serial myPort;
float px = 0, py = 0;
boolean pressed = false;

void setup() {
  size(500, 500);
  myPort = new Serial(this, Serial.list()[0], 9600);
  myPort.bufferUntil('\n');
}

void draw() {
  background(20);
  fill(pressed ? color(198, 255, 61) : color(109, 74, 255));
  float x = map(px, 0, 1023, 25, width - 25);
  float y = map(py, 0, 1023, 25, height - 25);
  ellipse(x, y, 50, 50);
}

void serialEvent(Serial p) {
  String line = p.readStringUntil('\n');
  if (line == null) return;
  String[] v = split(trim(line), ",");
  if (v.length != 3) return;
  for (String s : v) if (!s.matches("\\d+")) return;   // reject malformed lines
  px = float(v[0]);
  py = float(v[1]);
  pressed = v[2].equals("1");
}
P1

Temperature & Humidity — DHT11

open module →
P1.1BasicExplain 10 minP1 · Weather Station

Read the datasheet

The DHT11 datasheet says: range 0–50 °C, accuracy ±2 °C; humidity 20–90 % RH, accuracy ±5 %. Your sensor shows 22 °C and 48 %. What are the possible real values? Could you use this sensor in a freezer?

Real temperature is between 20 °C and 24 °C, humidity between 43 % and 53 %. A freezer (≈ −18 °C) is outside the 0–50 °C range, so the DHT11 is not suitable — a DHT22 (−40…80 °C) would be.
P1.2BasicPredict 5 minP1 · Weather Station

Parse it in your head

For each line Arduino might send, write what temp and hum become in the Processing sketch (or “unchanged”): 25.0,40.0 · Failed to read from DHT sensor! · 26.1, · 27.3,55.0,1

Check each guard: contains(",") and values.length == 2.
25.0,40.0 → temp 25.0, hum 40.0. · Error line → unchanged (no comma). · 26.1, → split gives ["26.1", ""], length 2, so temp = 26.1 and hum = NaN — a hidden bug! · 27.3,55.0,1 → length 3 → unchanged. Improvement: also check that both parts are not empty.
P1.3IntermediateCode 15 minP1 · Weather Station

Comfort zone indicator

Classrooms are comfortable between 20–24 °C and 40–60 % RH. Add a large circle to the Processing sketch that is green inside the comfort zone, yellow if only one value is outside, and red if both are outside. Write the status text next to it.

Add to draw()Processing · Java
boolean tempOk = temp >= 20 && temp <= 24;
boolean humOk  = hum >= 40 && hum <= 60;
int bad = (tempOk ? 0 : 1) + (humOk ? 0 : 1);

if (bad == 0)      fill(0, 200, 90);
else if (bad == 1) fill(255, 200, 0);
else               fill(230, 40, 40);
ellipse(440, 40, 40, 40);

fill(0);
textSize(14);
text(bad == 0 ? "Comfortable" : bad == 1 ? "Check climate" : "Uncomfortable", 330, 80);
P1.4IntermediateCalculate 15 minP1 · Weather Station

Dew point estimate

A simple approximation for the dew point (the temperature at which water condenses) is Td ≈ T − (100 − RH) / 5. Calculate Td for (a) 25 °C / 60 % and (b) 18 °C / 90 %. Then add the calculation to Processing and display it.

(a) 25 − 40/5 = 17 °C · (b) 18 − 10/5 = 16 °C — very close to the air temperature, so fog or condensation on windows is likely. In Processing: float dew = temp - (100 - hum) / 5.0;
P1.5AdvancedCode 20 minP1 · Weather Station

Log to a CSV file

Save every reading with a timestamp to weather.csv so you can open it later in a spreadsheet and draw a chart.

Use PrintWriter output = createWriter("weather.csv"); in setup(), output.println(...) in serialEvent(), and output.flush(); output.close(); when a key is pressed.
Logging additionsProcessing · Java
PrintWriter output;

// in setup():
output = createWriter("weather.csv");
output.println("time,temperature,humidity");

// in serialEvent(), after parsing successfully:
String stamp = nf(hour(), 2) + ":" + nf(minute(), 2) + ":" + nf(second(), 2);
output.println(stamp + "," + temp + "," + hum);

void keyPressed() {
  if (key == 's') {   // press S to save and stop
    output.flush();
    output.close();
    exit();
  }
}
P1.6ChallengeDesign 45 minP1 · Weather Station

Classroom climate study

Run a real investigation: log the classroom climate for one full lesson with windows closed and one with windows open. Plot both datasets, compare the averages, and write a short recommendation for your school (how often should you ventilate?).

  1. 01Formulate a hypothesis.
  2. 02Collect data with your logger (P1.5).
  3. 03Compute min, max and average for each condition.
  4. 04Present a chart and a 5-sentence conclusion.
Open-ended. A good report includes: a clear hypothesis, a description of the method (sensor position, interval), a chart with labelled axes and units, averages compared in a table, a discussion of sensor accuracy (±2 °C / ±5 %) and a practical recommendation.
P2

Light Sensor — Photoresistor (LDR)

open module →
P2.1BasicCalculate 10 minP2 · Light Meter

Voltage divider practice

With R_fixed = 10 kΩ and 5 V supply, calculate V_A0 and the expected analogRead() value when the LDR is (a) 1 kΩ (bright), (b) 10 kΩ (normal room), (c) 100 kΩ (dark).

V_A0 = 5 × 10 / (R_LDR + 10) with resistances in kΩ; reading = V_A0 / 5 × 1023.
(a) 5 × 10/11 = 4.55 V → ≈ 930 · (b) 5 × 10/20 = 2.50 V → ≈ 512 · (c) 5 × 10/110 = 0.45 V → ≈ 93
P2.2BasicPredict 5 minP2 · Light Meter

Swap the parts

What happens to the readings if you swap the LDR and the 10 kΩ resistor (LDR now between A0 and GND)?

The behaviour is inverted: bright light now gives a low reading and darkness a high one, because V_A0 = 5 × R_LDR / (R_LDR + R_fixed).
P2.3IntermediateBuild 20 minP2 · Light Meter

Automatic night-light

Add an LED (pin 9 + 220 Ω). Turn it on when the reading drops below a threshold of your choice and off when it is brighter. Print the state to the Serial Monitor.

NightLight.inoArduino · C++
const int sensorPin = A0;
const int ledPin = 9;
const int THRESHOLD = 300;

void setup() {
  Serial.begin(9600);
  pinMode(ledPin, OUTPUT);
}

void loop() {
  int light = analogRead(sensorPin);
  bool dark = light < THRESHOLD;
  digitalWrite(ledPin, dark ? HIGH : LOW);
  Serial.println(light);
  delay(200);
}
P2.4IntermediateCode 15 minP2 · Light Meter

Smooth dimming with PWM

Instead of on/off, make the LED on pin 9 get brighter as the room gets darker, using analogWrite() (0–255).

Pin 9 supports PWM (~). Use map(light, 0, 1023, 255, 0) — note the reversed output range.
solutionArduino · C++
void loop() {
  int light = analogRead(A0);
  int brightness = map(light, 0, 1023, 255, 0);
  analogWrite(9, constrain(brightness, 0, 255));
  Serial.println(light);
  delay(50);
}
P2.5AdvancedCode 20 minP2 · Light Meter

Calibrate with min/max

Your sensor never reaches 0 or 1023. Make Processing remember the smallest and largest values it has seen and map the bar between those, so it always uses the full width. Press R to reset calibration.

Calibration additionsProcessing · Java
int lo = 1023, hi = 0;

// in serialEvent(), after lightLevel is updated:
lo = min(lo, lightLevel);
hi = max(hi, lightLevel);

// in draw():
float w = (hi > lo) ? map(lightLevel, lo, hi, 0, 300) : 0;
rect(50, 100, w, 30);
text("calibrated: " + lo + " – " + hi, 50, 170);

void keyPressed() {
  if (key == 'r' || key == 'R') { lo = 1023; hi = 0; }
}
P2.6ChallengeDesign 40 minP2 · Light Meter

Sunrise logger

Design an experiment that measures how light levels change near a window during a whole school day. Log a value every minute with a timestamp, then plot a graph. At what time is your classroom brightest? Does it depend on the weather?

Open-ended. Use delay(60000) or millis() on Arduino (or a timer in Processing), log with PrintWriter, and plot in Processing or a spreadsheet. Discuss clouds, blinds, artificial lighting, and the direction the window faces.
P3

Sonar — Ultrasonic Sensor HC-SR04

open module →
P3.1BasicCalculate 10 minP3 · Radar Scanner

Echo arithmetic

pulseIn() returns 1166 µs. (a) How far away is the object? (b) What echo time do you expect for an object 2 m away?

d = t × 0.0343 / 2 (cm, with t in µs). For (b) rearrange: t = 2d / 0.0343.
(a) 1166 × 0.0343 / 2 ≈ 20.0 cm · (b) t = 2 × 200 / 0.0343 ≈ 11 662 µs ≈ 11.7 ms
P3.2BasicExplain 5 minP3 · Radar Scanner

Why divide by two?

A classmate forgets the / 2 in the distance formula. What will their readings look like? Explain using a sketch of the sound path.

Every reading will be exactly twice the real distance, because the measured time covers the path to the object and back.
P3.3IntermediateCalculate 10 minP3 · Radar Scanner

Temperature matters

The speed of sound depends on air temperature: v ≈ 331.3 + 0.606 × T (m/s, T in °C). At 0 °C and 35 °C, what distance would the sensor report for a real distance of 100 cm if the code always assumes 343 m/s?

First find the real echo time t = 2 × 1 m / v_real, then compute the reported distance with v = 343 m/s.
0 °C: v = 331.3 m/s → reported = 100 × 343 / 331.3 ≈ 103.5 cm (+3.5 %). · 35 °C: v ≈ 352.5 m/s → reported ≈ 97.3 cm (−2.7 %). Combine with a DHT11 (Project 1) to correct it!
P3.4IntermediateDebug 15 minP3 · Radar Scanner

Text in the numbers

Exercise 3 sends lines like distance: 23.4 cm. A student uses distance = float(trim(data)); in Processing and the bar never moves. Why? Fix it two ways: once in Arduino, once in Processing.

float("distance: 23.4 cm") returns NaN because the text isn’t a number. Fix A (Arduino): send only the number: Serial.println(distance_cm);. Fix B (Processing): extract it: String[] m = match(data, "([0-9.]+)"); if (m != null) distance = float(m[1]);
P3.5AdvancedCalculate 15 minP3 · Radar Scanner

Polar to screen

The radar centre is at (300, 600) and 100 cm maps to 250 px. Calculate the screen position of an object at angle 30°, distance 60 cm, and at angle 135°, distance 100 cm.

r = 60 × 2.5 = 150 px. x = 300 + r·cos θ, y = 600 − r·sin θ.
30°, 60 cm: r = 150 → x = 300 + 150·0.866 ≈ 430, y = 600 − 150·0.5 = 525. · 135°, 100 cm: r = 250 → x = 300 + 250·(−0.707) ≈ 123, y = 600 − 250·0.707 ≈ 423.
P3.6ChallengeCode 45 minP3 · Radar Scanner

Full radar display

Build the complete radar: green grid arcs every 25 cm, angle lines every 30°, a sweep line following the current angle, a fading trail, and dots colour-coded by distance (red < 30 cm, yellow < 60 cm, green otherwise).

Instead of background(0) each frame, draw a translucent black rectangle (fill(0, 20); rect(0,0,width,height);) — older drawings slowly fade away.
RadarPro.pdeProcessing · Java
import processing.serial.*;

Serial myPort;
int angle = 0;
float distance = 0;
final float MAX_CM = 100;
float R;   // radius in px

void setup() {
  size(800, 450);
  R = width / 2 - 20;
  myPort = new Serial(this, Serial.list()[0], 9600);
  myPort.bufferUntil('\n');
  background(0);
}

void draw() {
  noStroke();
  fill(0, 18);                         // fading trail
  rect(0, 0, width, height);

  translate(width / 2, height - 10);   // radar centre at the bottom
  drawGrid();

  stroke(61, 255, 154);                // sweep line
  strokeWeight(3);
  line(0, 0, R * cos(radians(angle)), -R * sin(radians(angle)));

  if (distance > 0 && distance < MAX_CM) {
    float r = map(distance, 0, MAX_CM, 0, R);
    if (distance < 30)      fill(255, 60, 60);
    else if (distance < 60) fill(255, 210, 60);
    else                    fill(61, 255, 154);
    noStroke();
    ellipse(r * cos(radians(angle)), -r * sin(radians(angle)), 10, 10);
  }

  resetMatrix();
  fill(61, 255, 154);
  textSize(16);
  text("Angle: " + angle + "\u00B0   Distance: " + nf(distance, 0, 1) + " cm", 20, 30);
}

void drawGrid() {
  noFill();
  stroke(61, 255, 154, 90);
  strokeWeight(1);
  for (int cm = 25; cm <= MAX_CM; cm += 25) {
    float d = map(cm, 0, MAX_CM, 0, R) * 2;
    arc(0, 0, d, d, PI, TWO_PI);
  }
  for (int a = 0; a <= 180; a += 30) {
    line(0, 0, R * cos(radians(a)), -R * sin(radians(a)));
  }
}

void serialEvent(Serial p) {
  String data = p.readStringUntil('\n');
  if (data == null) return;
  String[] parts = split(trim(data), ",");
  if (parts.length != 2) return;
  angle = int(parts[0]);
  distance = float(parts[1]);
}
P4

Arduino Ohm Meter with Processing Display

open module →
P4.1BasicCalculate 10 minP4 · Ohm Meter

Derive the formula

Starting from V_A0 = V_in · R_known / (R_x + R_known), show step by step that R_x = R_known · (V_in / V_A0 − 1).

Multiply both sides by (R_x + R_known), then divide by V_A0, then subtract R_known.
V_A0 (R_x + R_known) = V_in · R_known → R_x + R_known = V_in · R_known / V_A0 → R_x = R_known · V_in / V_A0 − R_known = R_known (V_in / V_A0 − 1) ✓
P4.2BasicCalculate 10 minP4 · Ohm Meter

From reading to ohms

With R_known = 1 kΩ, Arduino reads raw = 310. Calculate V_A0 and R_x. Which standard resistor is it probably? (1 kΩ, 2.2 kΩ, 3.3 kΩ, 4.7 kΩ)

V_A0 = 310 × 5 / 1023 ≈ 1.515 V → R_x = 1000 × (5 / 1.515 − 1) ≈ 2300 Ω → most likely a 2.2 kΩ resistor (within ±5 %).
P4.3IntermediateDebug 15 minP4 · Ohm Meter

Debug the textbook

The first edition of this handbook said: “R1 (known) between 5V and A0, R2 (unknown) between A0 and GND” but used the formula R2 = R1 · (Vin/Vout − 1). Using R1 = 1 kΩ and a real unknown of 2.2 kΩ, calculate what the meter would display. What is the correct formula for that wiring?

First find the real Vout for that wiring: Vout = 5 × 2200 / (1000 + 2200).
Vout = 5 × 2200/3200 = 3.44 V. Wrong formula: 1000 × (5/3.44 − 1) ≈ 455 Ω — completely wrong! For that wiring the correct formula is R2 = R1 · Vout / (Vin − Vout) = 1000 × 3.44 / 1.56 ≈ 2200 Ω ✓. Lesson: always check that the formula matches the circuit.
P4.4IntermediateCode 15 minP4 · Ohm Meter

Colour by range

Colour the displayed value: green below 1 kΩ, orange 1 kΩ–10 kΩ, purple above 10 kΩ. Also display large values in kΩ (e.g. 4.70 kΩ).

solutionProcessing · Java
String label;
if (resistance < 1000) {
  fill(0, 170, 80);
  label = nf(resistance, 1, 1) + " \u03A9";
} else {
  fill(resistance < 10000 ? color(255, 140, 0) : color(130, 60, 200));
  label = nf(resistance / 1000.0, 1, 2) + " k\u03A9";
}
textSize(36);
text(label, 50, 140);
P4.5AdvancedCode 25 minP4 · Ohm Meter

Analog gauge

Draw a semicircular gauge (like an old multimeter) from 0 to 10 kΩ with tick marks every 1 kΩ and a needle pointing at the current value.

Use arc(cx, cy, d, d, PI, TWO_PI). Map resistance to an angle between PI and TWO_PI.
Gauge.pde (draw part)Processing · Java
void drawGauge(float value, float maxValue) {
  float cx = width / 2, cy = height - 30, r = 150;
  noFill();
  stroke(40);
  strokeWeight(3);
  arc(cx, cy, r * 2, r * 2, PI, TWO_PI);

  strokeWeight(1);
  for (int i = 0; i <= 10; i++) {                        // ticks
    float a = map(i, 0, 10, PI, TWO_PI);
    line(cx + cos(a) * (r - 12), cy + sin(a) * (r - 12),
         cx + cos(a) * r,        cy + sin(a) * r);
  }

  float a = map(constrain(value, 0, maxValue), 0, maxValue, PI, TWO_PI);
  stroke(220, 40, 40);                                   // needle
  strokeWeight(4);
  line(cx, cy, cx + cos(a) * (r - 20), cy + sin(a) * (r - 20));
}
P4.6ChallengeDesign 40 minP4 · Ohm Meter

Accuracy investigation

Measure at least 8 resistors (100 Ω … 100 kΩ) with your Arduino meter and a real multimeter. Calculate the percentage error for each. Plot error vs. resistance. When is your meter most accurate, and why? Propose an improvement (e.g. auto-ranging).

Expected result: smallest error when R_x ≈ R_known; errors grow for very small or very large R_x, because V_A0 approaches 5 V or 0 V where one ADC step (≈ 4.9 mV) represents a large change in resistance. Improvement: switch between several known resistors (auto-ranging) and average multiple readings.
P5

Automatic Irrigation System

open module →
P5.1BasicCalculate 10 minP5 · Smart Garden

Calibrate to percent

Your sensor reads 820 in dry soil and 340 in freshly watered soil. Convert a reading of 580 into a moisture percentage (0 % = dry, 100 % = wet). Write the Processing map() call.

(820 − 580) / (820 − 340) × 100 = 240 / 480 × 100 = 50 %. In code: float pct = constrain(map(moisture, 820, 340, 0, 100), 0, 100);
P5.2BasicExplain 5 minP5 · Smart Garden

Why a relay?

An Arduino pin can supply about 20 mA at 5 V. A small pump needs about 200 mA. Explain why we can’t connect the pump directly to a pin, and what the relay does instead.

The pump needs ~10× more current than the pin can safely give — it could damage the Arduino. The relay lets the weak Arduino signal switch a separate, stronger power supply, while the two circuits stay electrically separated.
P5.3IntermediateCode 20 minP5 · Smart Garden

Manual override button

Add an on-screen WATER NOW button to the Processing dashboard that sends 1 while held and 0 when released. Show the pump state with an icon or colour.

Use mousePressed() and mouseReleased(); check the mouse is inside the button rectangle.
solutionProcessing · Java
boolean pumping = false;

// in draw():
fill(pumping ? color(61, 155, 255) : color(180));
rect(50, 160, 140, 30, 6);
fill(255);
text("WATER NOW", 65, 182);

void mousePressed() {
  if (mouseX > 50 && mouseX < 190 && mouseY > 160 && mouseY < 190) {
    myPort.write('1');
    pumping = true;
  }
}

void mouseReleased() {
  if (pumping) {
    myPort.write('0');
    pumping = false;
  }
}
P5.4AdvancedCode 20 minP5 · Smart Garden

Add hysteresis

With a single threshold the relay can click on and off every second when the value hovers around 600. Change the Arduino code so the pump turns ON above 650 and only turns OFF below 450. Explain why this is better for the pump and the plant.

Between 450 and 650 the system keeps its previous state, so it doesn’t flicker. This reduces wear on the relay and pump, and waters the plant thoroughly instead of in tiny bursts. A home thermostat works the same way.
Hysteresis.ino (loop)Arduino · C++
const int DRY_ON  = 650;
const int WET_OFF = 450;
bool pumpOn = false;

void loop() {
  int moisture = analogRead(SENSOR_PIN);
  Serial.println(moisture);

  if (!pumpOn && moisture > DRY_ON)  pumpOn = true;
  if (pumpOn  && moisture < WET_OFF) pumpOn = false;

  digitalWrite(RELAY_PIN, pumpOn ? PUMP_ON : PUMP_OFF);
  delay(1000);
}
P5.5AdvancedDebug 10 minP5 · Smart Garden

The thirsty pump

A team’s pump runs all the time, even in wet soil, and stops only when the soil is dry. Their code is the original Exercise 3 with HIGH = water ON. What is wrong, and how do you prove it with a single test?

Their relay is active-LOW, so HIGH actually switches the pump OFF and LOW switches it ON — the logic is inverted. Test: upload a sketch that only does digitalWrite(8, HIGH) and listen/look: if the pump is off, the module is active-LOW. Fix by swapping the constants.
P5.6ChallengeDesign 60 minP5 · Smart Garden

Water-saving study

Run two identical plants for one week: one watered by hand daily, one by your automatic system. Measure the water used (fill the reservoir to a mark, measure refills) and log moisture. Present which method uses less water and keeps moisture more stable.

  1. 01Define fair-test conditions (same plant, pot, soil, location).
  2. 02Log moisture every 10 minutes to CSV.
  3. 03Record water volume used per day.
  4. 04Present a chart and a conclusion with numbers.
Open-ended. Strong reports discuss variables controlled, show a moisture-vs-time chart for both plants, compare total water (ml), and reflect on threshold choice, evaporation and sensor placement.