Predict the rhythm
Without running it, describe exactly what the LED does with this loop(). How many times does it flash per minute?
void loop() {
digitalWrite(8, HIGH);
delay(200);
digitalWrite(8, LOW);
delay(800);
}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.
Without running it, describe exactly what the LED does with this loop(). How many times does it flash per minute?
void loop() {
digitalWrite(8, HIGH);
delay(200);
digitalWrite(8, LOW);
delay(800);
}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Ω.
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.
void flash(int ms) that turns the LED on for ms, then off for 200 ms. Call it inside for loops.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);
}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.
const int ledPin = 8
void setup() {
pinMode(ledPin, INPUT);
}
void loop() {
digitalWrite(ledPin, HIGH);
delay(500);
digitalWrite(ledpin, LOW);
delay(500);
}const int ledPin = 8. 2) pinMode must be OUTPUT, not INPUT. 3) ledpin ≠ ledPin — names are case-sensitive.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.
setup() use pinMode(2, INPUT_PULLUP);.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);
}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.
void lights(bool r, bool y, bool g, int ms) that sets all three LEDs and then waits.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
}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)
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).
No hardware: make a ball move across the window and bounce off all four walls. Change its colour every time it hits a wall.
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);
}Arduino is sending numbers, but this sketch shows nothing and sometimes crashes with a NullPointerException. Find the problems.
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')));
}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.Extend Lab 2: keep the last 100 received values in an array and draw them as a scrolling line graph across the window.
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!).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);
}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.
values.length == 3 and wrap each conversion. Test robustness by sending a broken line such as 12,, or hello.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");
}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?
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
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.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.
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);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.
float dew = temp - (100 - hum) / 5.0;Save every reading with a timestamp to weather.csv so you can open it later in a spreadsheet and draw a chart.
PrintWriter output = createWriter("weather.csv"); in setup(), output.println(...) in serialEvent(), and output.flush(); output.close(); when a key is pressed.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();
}
}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?).
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).
What happens to the readings if you swap the LDR and the 10 kΩ resistor (LDR now between A0 and GND)?
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.
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);
}Instead of on/off, make the LED on pin 9 get brighter as the room gets darker, using analogWrite() (0–255).
map(light, 0, 1023, 255, 0) — note the reversed output range.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);
}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.
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; }
}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?
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.pulseIn() returns 1166 µs. (a) How far away is the object? (b) What echo time do you expect for an object 2 m away?
A classmate forgets the / 2 in the distance formula. What will their readings look like? Explain using a sketch of the sound path.
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?
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]);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.
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).
background(0) each frame, draw a translucent black rectangle (fill(0, 20); rect(0,0,width,height);) — older drawings slowly fade away.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]);
}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).
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Ω)
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?
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Ω).
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);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.
arc(cx, cy, d, d, PI, TWO_PI). Map resistance to an angle between PI and TWO_PI.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));
}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).
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.
float pct = constrain(map(moisture, 820, 340, 0, 100), 0, 100);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.
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.
mousePressed() and mouseReleased(); check the mouse is inside the button rectangle.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;
}
}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.
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);
}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?
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.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.