Learning Goals 5 min
- Explain in your own words how a digital pin can pretend to be analog — by switching on and off very fast.
- Use
analogWrite(pin, value)with values from0to255to set LED brightness on any PWM-capable pin (~3, 5, 6, 9, 10, 11 on the UNO). - Identify which UNO pins support PWM at a glance — the ones with the squiggly
~symbol next to their number.
Warm-Up 10 min
So far every output pin you've driven has been fully on or fully off. An LED is bright or dark; a motor is spinning or stopped. There's no half-bright, no slow-spin.
But you know they're possible — a TV brightness slider clearly works. So how do we get "in-between" from a chip that can only output 0 V or 5 V?
Predict-the-output puzzle
Wei Jie thinks he can dim an LED by switching it on and off really fast. He writes this:
void loop() {
digitalWrite(9, HIGH);
delay(5);
digitalWrite(9, LOW);
delay(5);
}What does the LED look like?
Reveal
The LED is on 5 ms, off 5 ms — about 100 times per second. Faster than your eye can resolve, so you see half-brightness. Wei Jie has just hand-built a PWM signal at 50% duty cycle. Today we replace those four lines with one Arduino built-in: analogWrite.
New Concept · PWM, the "fake analog" trick 20 min
The big idea — duty cycle
The chip can only output 0 V or 5 V. So it cheats: it switches between them very fast. The fraction of time the pin spends HIGH is the duty cycle.
- 0% duty → always
LOW→ LED off. - 25% duty → on for 1 unit of time, off for 3 → LED at about a quarter brightness.
- 50% duty → on/off equal → LED at about half brightness.
- 100% duty → always
HIGH→ LED full bright.
Your eye averages the on/off flicker into a single perceived brightness. Same trick used in TV displays, dimmer switches, motor controllers, even server fans.
The Arduino built-in: analogWrite
analogWrite(pin, value);
// value is an integer 0..255
// 0 = always LOW (0% duty)
// 64 = ~25% duty
// 128 = ~50% duty
// 192 = ~75% duty
// 255 = always HIGH (100% duty)The number 255 isn't magic — it's 28 − 1, the biggest value that fits in one byte. The Arduino uses an 8-bit register for PWM, so you get 256 brightness steps from 0 to 255.
Only PWM-capable pins
Not every UNO pin can do PWM. Look at your board: pins with a squiggly ~ mark are the PWM pins. On a standard UNO they are ~3, ~5, ~6, ~9, ~10, ~11 — six pins out of fourteen.
If you call analogWrite on a non-PWM pin (say, pin 7), the IDE doesn't crash. The pin just acts like a regular digitalWrite: 0 = LOW, anything else = HIGH. No dimming. The lesson: always wire LEDs you want to fade onto a ~ pin.
The PWM frequency
On most UNO pins, PWM runs at ~490 Hz. On D5 and D6 it's ~980 Hz. Either way, much faster than the human eye can see (we top out at roughly 60 Hz of flicker perception). For LEDs you can ignore the frequency entirely; for motors and audio it matters more — but that's a Level 3 conversation.
No pinMode required
One small surprise — you don't need pinMode(pin, OUTPUT) before analogWrite. The function sets the pin to output for you. Adding it does no harm, and most authors include it for symmetry with their other pins. Up to you.
Worked Example · Three brightness levels 20 min
Step 1 — wire one LED on a PWM pin
Same wiring as the L01-07 single-LED circuit, but use pin ~9 (which supports PWM) instead of pin 13. Anode of the LED → 220 Ω resistor → Arduino pin ~9. Cathode → GND rail.
Step 2 — set three brightnesses
Save as three-brightnesses.ino:
// L02-02: three brightness steps with analogWrite
const int LED = 9;
void setup() {
pinMode(LED, OUTPUT);
}
void loop() {
analogWrite(LED, 64); // ~25% — dim
delay(1000);
analogWrite(LED, 128); // ~50% — medium
delay(1000);
analogWrite(LED, 255); // 100% — full
delay(1000);
}Step 3 — upload and watch
The LED cycles between dim, medium, full, dim, medium, full... at one-second steps. No on/off flicker visible — the PWM is far too fast for your eye.
What you should see
(dim ─ 1s) → (medium ─ 1s) → (full ─ 1s) → (dim ─ 1s) → …
Step 4 — predict before you tweak
Before you change any number, ask yourself: what would analogWrite(LED, 0) look like? What about analogWrite(LED, 1)? Or analogWrite(LED, 254)?
0→ dead off, indistinguishable from a broken wire.1→ just barely glowing — in a dark room you'll see it, in daylight you might not.254→ looks identical to255to your eye. The eye can't tell the difference of one PWM step at the top of the range.
Try It Yourself 20 min
Goal: Add a fourth brightness step at analogWrite(LED, 192) so the LED cycles dim → medium → bright → full. Predict roughly how bright 192 is before uploading.
Hint
192 / 255 ≈ 75% duty cycle. You should see a clear "bright but not blazing" step between 128 and 255.
Goal: Wire a second LED on pin ~10 (also PWM). Drive them at different brightnesses — say pin 9 at 64 and pin 10 at 192 — and watch how clearly different they look side by side. Each PWM pin runs its own duty cycle independently.
const int LED_A = 9;
const int LED_B = 10;
void setup() {
pinMode(LED_A, OUTPUT);
pinMode(LED_B, OUTPUT);
}
void loop() {
analogWrite(LED_A, 64);
analogWrite(LED_B, 192);
}Goal: Cycle a single LED through six brightness levels (0, 51, 102, 153, 204, 255) with 300 ms between each step. Use a for loop with step size 51, not six hand-typed lines.
Hint
for (int v = 0; v <= 255; v += 51) {
analogWrite(LED, v);
delay(300);
}The shorthand v += 51 is the same as v = v + 51. With six values (0, 51, 102, 153, 204, 255) one cycle takes 6 × 300 ms = 1.8 seconds.
Mini-Challenge · Three-level mood lamp 15 min
Build a small sketch where pressing the button (on pin 2 with INPUT_PULLUP) cycles the LED through three brightness levels: low, medium, high, low, medium, high...
It works if:
- Power-up state is
lowbrightness. - Each press of the button advances to the next level (low → medium → high → low → ...).
- The button works reliably — no skipping levels because of debounce noise (recall L01-18). A 200 ms blocking delay after each press is fine here.
- The LED is on a PWM pin (~9 / ~10 / ~11) so the brightness is visibly different at each level.
Reveal one valid sketch
const int LED = 9;
const int BTN = 2;
int level = 0;
const int LEVELS[3] = {32, 128, 255};
void setup() {
pinMode(LED, OUTPUT);
pinMode(BTN, INPUT_PULLUP);
analogWrite(LED, LEVELS[0]);
}
void loop() {
if (digitalRead(BTN) == LOW) {
level = (level + 1) % 3;
analogWrite(LED, LEVELS[level]);
delay(200); // simple debounce
}
}The modulus % 3 wraps the level back to 0 after 2 — same trick we used in PY-L1-13's number guesser. Brightness levels 32/128/255 give a clear "quiet / normal / bedside-lamp" feel.
Recap 5 min
PWM gets analog-style output from a digital pin by switching very fast. analogWrite(pin, value) takes a value from 0 to 255 and sets the duty cycle accordingly. Only the six ~-marked UNO pins support it; the others fall back to plain on/off. Tomorrow we'll do the maths on duty cycle properly — frequency, period and the difference between "average voltage" and "perceived brightness".
- PWM (Pulse-Width Modulation)
- Switching a digital pin on and off so quickly that the average voltage acts analog. Your eye / a motor / a speaker doesn't see the flicker — only the average.
- Duty cycle
- The fraction of time the pin spends
HIGHwithin one PWM period. 0% = always off, 100% = always on.analogWrite's value maps to duty asvalue / 255 × 100%. analogWrite(pin, value)- Arduino built-in.
valueis 0..255 (8-bit). Only works on PWM-capable pins. NopinModerequired. - PWM pins (UNO)
- The six pins marked with
~on the board silkscreen: D3, D5, D6, D9, D10, D11.
Homework 5 min
Predict-then-test. In your notebook, write down what each of these analogWrite values will look like on the LED:
analogWrite(LED, 0)→ ____analogWrite(LED, 32)→ ____analogWrite(LED, 96)→ ____analogWrite(LED, 160)→ ____analogWrite(LED, 224)→ ____analogWrite(LED, 255)→ ____
Then write a sketch that sets the LED to each value for 800 ms in turn, upload it, and check your guesses. Note in your notebook anywhere your guess was off — that's the gap between the duty-cycle maths (linear) and your eye's response (a curve we'll meet tomorrow).
Bring back next class:
- Your predicted vs actual table.
- The
.inofile (rename tohw-l02-02.ino).