Learning Goals 5 min
- Combine the
forloop (L01-11) withanalogWrite(L02-02) to fade an LED from off to full over a target duration. - Calculate the fade duration from
steps × delay, and tune step size + delay to land on a desired length. - Chain two fades together — one up, one down — to make a smooth "heartbeat" or "sigh" effect.
Warm-Up 10 min
Yesterday's sketch jumped the LED between three brightness levels with a 1-second pause. The eye saw three distinct states — bright, brighter, brightest. Today we replace those jumps with a smooth ramp so the eye sees one continuous swell of light.
Quick puzzle
Priya wrote this. Predict what the LED does:
const int LED = 9;
void setup() {
pinMode(LED, OUTPUT);
}
void loop() {
for (int v = 0; v <= 255; v++) {
analogWrite(LED, v);
delay(10);
}
}Reveal
The LED fades from off to full bright over about 2.56 seconds (256 steps × 10 ms each), then jumps back to off when the loop ends and loop() restarts. So you see: smooth swell up over 2.5 s, instant snap to dark, swell up again. The fade looks lovely; the snap is jarring. Today we'll fix it with a second loop counting down.
New Concept · One loop = one ramp 20 min
The pattern
A smooth fade is one for loop whose counter is the brightness:
for (int v = 0; v <= 255; v++) {
analogWrite(LED, v);
delay(10);
}Each pass: increase v by 1, write it to the LED, wait 10 ms. 256 passes total. Your eye sees a continuous ramp, not 256 individual steps, because the change between consecutive duty cycles is far below your perception threshold.
The fade duration formula
Total fade time = number_of_steps × delay_per_step. So:
| Step size | Steps in 0→255 | Delay per step | Total fade time |
|---|---|---|---|
| 1 | 256 | 10 ms | 2.56 s |
| 1 | 256 | 5 ms | 1.28 s — fast |
| 1 | 256 | 20 ms | 5.12 s — slow, lamp-like |
| 5 | 52 | 50 ms | 2.60 s — same length, blockier ramp |
| 1 | 256 | 40 ms | 10.24 s — meditation-pace |
To hit a target duration D seconds with single-step fades, use delay = D × 1000 / 256 ms.
Two loops = up and down
The trick from yesterday's warm-up: after the up-ramp, add a second loop that counts down. i-- instead of i++, condition v >= 0 instead of v <= 255:
for (int v = 0; v <= 255; v++) {
analogWrite(LED, v);
delay(10);
}
for (int v = 255; v >= 0; v--) {
analogWrite(LED, v);
delay(10);
}Up over ~2.5 s, then down over ~2.5 s. A full breath cycle. No snap.
What about the eye's curve?
Recall L02-03 — equal duty steps don't look equal to the eye (γ ≈ 2.2). A 0→255 linear fade looks fast at the bottom, slow at the top. That's a feature for some effects (sun rising) and a bug for others (a smooth breath wants more time near the dim end). One quick fix: change the delay per step instead of the step size, so you spend longer in the dim region. We'll leave gamma-corrected tables for L02-06.
Worked Example · A breathing LED 20 min
Step 1 — wiring
Same single-LED circuit as L02-02. Anode (long leg) of the LED → 220 Ω resistor → Arduino pin ~9. Cathode (short leg) → GND rail. Nothing else changes.
Step 2 — the sketch
Save as breathing-led.ino:
// L02-04: breathing LED — fade up, fade down, repeat
const int LED = 9;
const int STEP_DELAY = 10; // ms per step → ~2.5 s up + 2.5 s down
void setup() {
pinMode(LED, OUTPUT);
}
void loop() {
// inhale
for (int v = 0; v <= 255; v++) {
analogWrite(LED, v);
delay(STEP_DELAY);
}
// exhale
for (int v = 255; v >= 0; v--) {
analogWrite(LED, v);
delay(STEP_DELAY);
}
}Step 3 — upload & watch
The LED swells from dark to full bright, then sinks back to dark, then repeats forever. Total cycle ~5 seconds. Feels like a relaxed breath.
Step 4 — change the speed by changing one number
Want a faster breath? Drop STEP_DELAY to 4 ms → full cycle ≈ 2 s. Want a meditation-slow breath? Bump it to 30 ms → full cycle ≈ 15 s. Notice that you only need to change one constant at the top — the loops themselves never move.
Step 5 — a tiny pause at the peak
If you want the LED to hold at full bright before fading down (like the briefest pause at the top of a sigh), add a single delay(200) between the two loops:
// inhale
for (int v = 0; v <= 255; v++) { analogWrite(LED, v); delay(STEP_DELAY); }
delay(200); // brief peak hold
// exhale
for (int v = 255; v >= 0; v--) { analogWrite(LED, v); delay(STEP_DELAY); }
delay(200); // brief bottom hold (so the next breath isn't instant)That brief hold at each end is the difference between "mechanical fade" and "living breath".
Try It Yourself 20 min
Goal: Make the breath last 8 seconds total (4 up + 4 down).
Set STEP_DELAY to a value that gives a 4-second up-ramp. Show the maths in a comment above the constant.
Reveal
// 256 steps × ? ms = 4000 ms → ? = 4000 / 256 ≈ 15.6 → round to 16
const int STEP_DELAY = 16;Close enough — 256 × 16 = 4.096 s. The eye can't tell 4.0 from 4.1.
Goal: Asymmetric breath — fade up slowly (3 seconds), then fade down quickly (1 second). Like the LED inhales calmly and exhales sharply.
Hint
const int UP_DELAY = 12; // 256 × 12 ≈ 3.07 s
const int DOWN_DELAY = 4; // 256 × 4 ≈ 1.02 s
void loop() {
for (int v = 0; v <= 255; v++) { analogWrite(LED, v); delay(UP_DELAY); }
for (int v = 255; v >= 0; v--) { analogWrite(LED, v); delay(DOWN_DELAY); }
}Goal: A "heartbeat" — fade up + down in 0.4 s, brief pause, fade up + down again in 0.4 s, longer pause. Should feel like "ba-bum ... ba-bum ...".
Reveal one valid version
const int LED = 9;
void pulse() { // one short breath: ~0.4 s total
for (int v = 0; v <= 255; v++) { analogWrite(LED, v); delay(1); }
for (int v = 255; v >= 0; v--) { analogWrite(LED, v); delay(1); }
}
void setup() { pinMode(LED, OUTPUT); }
void loop() {
pulse();
delay(150); // short pause between "ba" and "bum"
pulse();
delay(600); // longer rest before next heartbeat
}The function pulse() from L01-12 keeps the main loop readable — you can see the rhythm at a glance.
Mini-Challenge · The signal lamp 15 min
Build a sketch that fades the LED through three named "moods" with a 1-second pause between each:
- Calm — slow breath, ~6 s up + 6 s down. Repeat 2 breaths.
- Working — medium breath, ~2 s up + 2 s down. Repeat 3 breaths.
- Alert — fast pulse, 0.3 s up + 0.3 s down. Repeat 5 pulses.
The whole sequence runs once per loop() pass, so the entire mood cycle repeats forever.
It works if:
- You can see the three moods are distinctly different in speed.
- You used a
breath(int upMs, int downMs)function so the mainloop()reads like a script, not a wall of for-loops. - The constants for each mood's step delay are computed from the duration formula, not guessed.
Reveal one valid sketch
const int LED = 9;
void breath(int upMs, int downMs) {
int upDelay = upMs / 256; // integer division, fine for our purposes
int downDelay = downMs / 256;
for (int v = 0; v <= 255; v++) { analogWrite(LED, v); delay(upDelay); }
for (int v = 255; v >= 0; v--) { analogWrite(LED, v); delay(downDelay); }
}
void setup() { pinMode(LED, OUTPUT); }
void loop() {
// Calm — 2 long breaths
breath(6000, 6000);
breath(6000, 6000);
delay(1000);
// Working — 3 medium breaths
breath(2000, 2000);
breath(2000, 2000);
breath(2000, 2000);
delay(1000);
// Alert — 5 fast pulses
for (int i = 0; i < 5; i++) breath(300, 300);
delay(1000);
}The breath() helper does the maths for you (duration ÷ 256 = delay per step), so the main loop() reads like a stage cue sheet.
Recap 5 min
A smooth fade is just a for loop whose counter is fed into analogWrite. Going up = v++; going down = v--. Total fade time = steps × delay. To target a duration D seconds, set the delay to D × 1000 / 256 ms. Wrap a single breath in a function and the main loop() becomes readable. Next lesson we cross-fade three LEDs at once for an RGB colour wheel.
- Fade
- A continuous change in brightness over time, typically achieved by stepping
analogWritevalues inside a for loop. - Ramp
- A one-direction fade — up only, or down only. Two ramps back-to-back make a breath.
- Step size
- How much the counter changes each pass (
v++= 1,v += 5= 5). Bigger steps = blockier ramp. - Asymmetric fade
- Up and down ramps with different speeds. Common in breathing / heartbeat effects.
- Helper function
- A named recipe for a re-used pattern, like
breath(upMs, downMs). Keeps the main loop readable.
Homework 5 min
Tune-a-breath. Open the breathing sketch, set STEP_DELAY to 4, then 10, then 20, then 40. Watch each one for 30 seconds. In your notebook:
- Write down each delay's full-breath duration (use the formula, not a stopwatch).
- Pick the delay that feels most like a calm human breath. Note it.
- Now try
STEP_DELAY = 2. Does it look smoother than10? Smoother than4? Note your answer.
Bring back next class:
- Your tuning notes.
- The sketch saved as
hw-l02-04.inowith your favourite delay coded in. - One sentence on why higher PWM step values look closer together than lower ones (recall L02-03's gamma curve).