Learning Goals 5 min
By the end of this lesson you will be able to:
- Read a function definition and identify its return type, name, parameters and body.
- Write a small reusable helper called
flash(pin, durationMs)that turns an LED on for a duration and then off, and call it fromloop()with different pin and duration arguments. - Explain why a one-line
flash(9, 200);is easier to read than the three-linedigitalWrite/delay/digitalWriteit replaces — and when the trade is worth making.
Warm-Up 10 min
You've been calling Arduino functions for five lessons — pinMode, digitalWrite, delay, setup, loop. Today you write your own.
Quick-fire puzzle
Iman opens her L01-11 chase sketch. The loop() body looks like this:
for (int i = FIRST_PIN; i <= LAST_PIN; i++) {
digitalWrite(i, HIGH);
delay(200);
digitalWrite(i, LOW);
}She points at the three indented lines and says: "Those three lines together mean something — 'flash this pin for 200 ms'. I wish I could say that in one line."
- What word might Iman invent for that "flash this pin for 200 ms" idea?
- If those three lines could be replaced by one line, how much shorter would a 10-step chase be?
- If Iman later decides she wants every flash to take 100 ms instead of 200 ms, how many places in the chase sketch must she edit today? And how many could she edit if those three lines were tucked into one named helper?
Reveal the answer
- Probably
flash. Short, says exactly what it does. Today we'll write a function with that name. - 20 lines shorter. Each chase step today is 3 lines; with a one-line helper it becomes 1 line. Ten steps × (3 − 1) = 20 lines saved.
- Today: every
delay(200)she ever typed (or every place where the duration matters). With a helper: one place. That single-edit superpower is the real reason we write functions.
New Concept 20 min
The big idea — name a chunk of code, then call it like a built-in
A function is a named block of code that you can call by name, with values plugged in. digitalWrite is a function that Arduino's library wrote for you. Today you write your own functions and the language treats them exactly the same way.
Anatomy of a function definition
void flash(int pin, int durationMs) {
digitalWrite(pin, HIGH);
delay(durationMs);
digitalWrite(pin, LOW);
}Read it from left to right on the first line:
| Piece | Example | What it does |
|---|---|---|
| Return type | void | What the function gives back when it finishes. void means "nothing — just go run, then come back". The same word you've seen on setup() and loop(). |
| Name | flash | How you'll call the function from elsewhere. Use a verb in camelCase: flash, blinkAllLeds, readButton. |
| Parameter list | (int pin, int durationMs) | Named "slots" the caller fills in. Each slot has a type and a name, separated by commas. Inside the body, you use the parameters like variables. |
| Body | { … } | The code that runs when the function is called. Same shape as setup()'s and loop()'s bodies. |
Calling the function
Once the function is defined, you call it just like Arduino's built-ins:
flash(9, 200); // Flash pin 9 for 200 ms.
flash(10, 100); // Flash pin 10 for 100 ms (quicker).
flash(11, 500); // Flash pin 11 for half a second (slower).The two values inside the parentheses are the arguments. They fill in the parameter slots in order: the first argument (9) becomes pin; the second (200) becomes durationMs. Inside the function body, every mention of pin means 9 and every mention of durationMs means 200.
Where the definition lives
Put your custom functions above setup() — between the global const ints and the first void setup() line. That way setup() and loop() can both see and call them.
// 1. Globals — pin constants, range constants
const int RED_PIN = 9;
// 2. Custom functions — your helpers
void flash(int pin, int durationMs) { /* … */ }
// 3. setup() — declared inputs, outputs, etc.
void setup() { /* … */ }
// 4. loop() — runs forever
void loop() { /* … */ }Parameters are local — they don't clash with globals
Inside flash(), the parameter pin is a brand-new variable that only exists for the duration of one call. It does not conflict with the global RED_PIN you declared at the top of the sketch. When you call flash(RED_PIN, 200), the value of RED_PIN (which is 9) is copied into the local pin. The function works with its copy; the original is untouched.
Reuse the L01-10 wiring
Today's hardware is still the three-LED breadboard from L01-10: red on D9, yellow on D10, green on D11, each with its 220 Ω resistor, all sharing GND through the − rail. Keep it set up.
Why it matters
Three big wins make functions worth the upfront cost:
- Read the code as a story.
flash(RED_PIN, 200);reads like a sentence.digitalWrite(9, HIGH); delay(200); digitalWrite(9, LOW);reads like assembly instructions. - Single point of change. Want every flash to fade out smoothly instead of snapping off? You change the body of
flash()in one place. Every caller benefits. - Build bigger from smaller. Tomorrow you'll write
flashTwice(pin)that callsflash(pin)twice. The week after, you'll writepanicSignal()that callsflashTwice()across all the LEDs. Layers.
Worked Example 20 min
Goal: take the for-loop chase from L01-11 and rewrite it with a flash() helper. The behaviour is identical; the loop body becomes a single line.
Step 1 — type the helper above setup()
In the IDE, open the chase-for-loop sketch from L01-11. Above void setup(), add this function:
void flash(int pin, int durationMs) {
digitalWrite(pin, HIGH);
delay(durationMs);
digitalWrite(pin, LOW);
}Step 2 — replace the three lines inside the loop body with one call
void loop() {
for (int i = FIRST_PIN; i <= LAST_PIN; i++) {
flash(i, 200);
}
}The loop body shrank from three lines to one — and that single line reads as plain English: "flash pin i for 200 ms."
Step 3 — the full sketch
Put together, your sketch now looks like this:
const int FIRST_PIN = 9;
const int LAST_PIN = 11;
void flash(int pin, int durationMs) {
digitalWrite(pin, HIGH);
delay(durationMs);
digitalWrite(pin, LOW);
}
void setup() {
for (int i = FIRST_PIN; i <= LAST_PIN; i++) {
pinMode(i, OUTPUT);
}
}
void loop() {
for (int i = FIRST_PIN; i <= LAST_PIN; i++) {
flash(i, 200);
}
}Step 4 — upload and watch
The chase walks across red → yellow → green, exactly as in L01-11. The Arduino can't tell the function call apart from inlined statements. You can — your loop() now reads as a one-line story.
Step 5 — is the function worth it for ONE chase?
Honest answer: for a single chase, the function adds 5 lines of definition and saves 2 lines per call. With 3 calls, you save 6 lines and add 5. Net win: just 1 line. Not huge.
The real win comes when you call flash() many times across the sketch with different arguments — different pins, different durations, in non-uniform patterns that a single for loop can't express. The Mini-Challenge below is your first taste.
Try It Yourself 20 min
Three tasks that grow the flash() helper. Predict before you upload.
Goal: Vary the duration on the fly. Modify your chase so each LED stays on for a different length of time: red 100 ms, yellow 200 ms, green 400 ms. The chase gets visibly slower from left to right.
// Reuse the same flash() function — no changes to its body.
void loop() {
flash(9, 100);
flash(10, 200);
flash(11, 400);
}Questions:
- You didn't touch
flash()'s body at all. Why did the behaviour change? ____ (Hint: think about the parameterdurationMs.) - Which of the three LEDs feels "lazier" to your eye? Does the answer match the arithmetic? ____
Goal: Give flash() a third parameter — the off-time after the flash. Now you can have a quick flash followed by a long pause, or vice versa.
void flash(int pin, int onMs, int offMs) {
digitalWrite(pin, HIGH);
delay(onMs);
digitalWrite(pin, LOW);
delay(offMs);
}Now in loop() use the three-argument form:
void loop() {
flash(9, 50, 950); // brief blink, long gap (lighthouse)
}Questions:
- How is this different from L01-08's standalone "lighthouse beacon"? ____ (Hint: think about reusability — could you flash a different pin at the same rhythm now without copying any code?)
- What does
flash(11, 500, 50);look like on the LED? ____ (Hint: long on, short off.)
Goal: Functions can call other functions. Add a second helper, doubleFlash(pin), that calls flash() twice in quick succession with a short gap. Then use it in loop() to make each LED blink twice.
// The first function we wrote in Step 1.
void flash(int pin, int durationMs) {
digitalWrite(pin, HIGH);
delay(durationMs);
digitalWrite(pin, LOW);
}
// New helper that COMPOSES two flash() calls.
void doubleFlash(int pin) {
flash(pin, 80);
delay(120);
flash(pin, 80);
}Now in loop():
void loop() {
doubleFlash(9);
delay(500);
doubleFlash(10);
delay(500);
doubleFlash(11);
delay(1000);
}Questions:
- What does each LED do in one full cycle of
loop()? ____ - If you wanted a triple flash, what would
tripleFlash(pin)look like? Sketch it on paper without typing — three calls toflash()separated by short delays. - This pattern — small functions calling smaller functions calling even smaller ones — is the spine of every big program you'll ever write. From a phone app to a self-driving car, it's functions all the way down.
Mini-Challenge 15 min
The heartbeat across the breadboard
Build a pattern that can't easily be done with a single for loop: each LED gets a "lub-dub" heartbeat (two quick flashes, close together), then a longer pause before the next LED's heartbeat. The pattern walks red → yellow → green, repeating.
Your task:
- Use the colour-named pin constants (
RED_PIN,YELLOW_PIN,GREEN_PIN) from L01-09. - Define a
flash(pin, durationMs)helper as in Step 1 of the Worked Example. - Define a
doubleFlash(pin)helper as in the 🔴 stretch task. - In
loop(), calldoubleFlashon each pin in turn, with a 500 ms pause between heartbeats and a longer 1000 ms pause at the end of the cycle.
It works if:
- Each LED does two quick flashes ("lub-dub"), unmistakably a heartbeat — not a single flash and not a continuous blink.
- Only one LED is heartbeating at a time; the order is red → yellow → green.
- A classmate watching the LEDs can identify the pattern as a "heartbeat" without you explaining it.
- Your
loop()body has nodigitalWritecalls — only calls to your own helpers anddelay.
Reveal one valid sketch
const int RED_PIN = 9;
const int YELLOW_PIN = 10;
const int GREEN_PIN = 11;
void flash(int pin, int durationMs) {
digitalWrite(pin, HIGH);
delay(durationMs);
digitalWrite(pin, LOW);
}
void doubleFlash(int pin) {
flash(pin, 80);
delay(120);
flash(pin, 80);
}
void setup() {
pinMode(RED_PIN, OUTPUT);
pinMode(YELLOW_PIN, OUTPUT);
pinMode(GREEN_PIN, OUTPUT);
}
void loop() {
doubleFlash(RED_PIN);
delay(500);
doubleFlash(YELLOW_PIN);
delay(500);
doubleFlash(GREEN_PIN);
delay(1000);
}Notice the layering: loop() only knows about doubleFlash; doubleFlash only knows about flash; flash only knows about digitalWrite and delay. Each layer talks to the layer just below it. That's how complex programs are kept understandable.
Recap 5 min
A function is a named, reusable block of code with named slots (parameters) the caller fills in. Write flash(pin, durationMs) once; call it as many times as you like with whatever pin and duration you want. The body of loop() stops looking like a wiring diagram and starts looking like a sentence.
- Function
- A named block of code that you can call by name. Takes optional inputs (parameters), runs its body, and optionally returns a value.
- Return type
- What the function gives back.
voidmeans "nothing — just run, then return". You'll meet other return types likeintin Level 1 Cluster D (Serial Monitor). - Parameter
- A named "slot" in a function's definition that the caller fills in. Inside the body, used like a variable.
- Argument
- The actual value passed into a function call.
flash(9, 200)has two arguments:9and200. - DRY (Don't Repeat Yourself)
- The principle behind functions: if you find yourself typing the same three lines in different places, name them and call the name instead. Every change happens in one place.
- Composition
- Building a bigger function out of calls to smaller functions.
doubleFlashcomposes twoflashcalls.panicSignalwould compose severaldoubleFlashcalls. Functions all the way down.
Homework 5 min
Build a counting flasher. Write a brand-new helper called nameFlash(pin, count) that flashes a pin count times in quick succession. Use it to give each LED a different number of flashes per cycle:
- Red LED: 1 flash
- Yellow LED: 2 flashes
- Green LED: 3 flashes
- Then a 1-second pause, and repeat.
Inside nameFlash() you'll combine the lesson from L01-11 (a for loop) with today's lesson (calling flash() from inside another function). Both ideas in one helper.
// Helper from class.
void flash(int pin, int durationMs) {
digitalWrite(pin, HIGH);
delay(durationMs);
digitalWrite(pin, LOW);
}
// New helper to write — combine a for loop with a flash() call.
void nameFlash(int pin, int count) {
for (int i = 0; i < count; i++) {
flash(pin, 100);
delay(100);
}
}Also: a small reading exercise. Given this trace:
nameFlash(11, 3);- How many times does the body of the inner
forloop run? ____ - How many
digitalWritecalls happen overall when this single line ofloop()executes? ____ - How many
delaycalls happen overall? ____
Bring back next class:
- The saved
.inofile (call itcount-flasher). - A short phone video (10–15 seconds) showing the 1-2-3 pattern.
- Your reading-exercise answers on a notebook page.