Skip to content

Activity 4.2.3 — Pulse Width Modulation (PWM)


Learning Objectives

By the end of this lesson, students will be able to:

  1. Explain what PWM is and how it controls average power
  2. Calculate duty cycle and understand its relationship to output power
  3. Use analogWrite() to control LED brightness
  4. Control DC motor speed using PWM
  5. Position servo motors using the Servo library

Vocabulary

Vocabulary (click to expand)
Term Definition
PWM Pulse Width Modulation - rapidly switching a signal on and off
Duty Cycle Percentage of time a PWM signal is HIGH
Frequency How often the PWM cycle repeats (Hz)
analogWrite() Arduino function to output PWM signal
Servo Motor A motor with precise angular position control

Part 1: What is PWM?

Definition

Pulse Width Modulation (PWM) is a technique that rapidly turns a signal on and off to control the average amount of power delivered to a device.

How It Works

Instead of varying the voltage, PWM switches between fully ON and fully OFF very quickly:

100% Duty Cycle (Always ON):
          ────────────────────────
          │                       │
          │                       │
──────────┘                       ┘

50% Duty Cycle (Half ON, Half OFF):
          ┌─────────┐             ┌─────────┐
          │         │             │         │
          │         │             │         │
──────────┘         └─────────────┘         └────

25% Duty Cycle (Mostly OFF):
          ┌──────┐                     ┌──────┐
          │      │                     │      │
          │      │                     │      │
──────────┘      └─────────────────────┘      └────

0% Duty Cycle (Always OFF):
          ────────────────────────────────
          │                               │
          │                               │
────────────────────────────────────────

Key Terms

Term Definition
Period Total time for one PWM cycle (frequency = 1/period)
Pulse Width Time the signal is HIGH during each cycle
Duty Cycle (Pulse Width / Period) × 100%

Calculating Duty Cycle

If a PWM signal is HIGH for 2ms and LOW for 2ms (period = 4ms):

Duty Cycle = (2ms / 4ms) × 100% = 50%

Why PWM Works

Our eyes cannot see flickering at high frequencies. The eye averages the light, seeing: - 0% duty cycle = OFF (0V average) - 50% duty cycle = DIM (2.5V average) - 100% duty cycle = FULL (5V average)


Part 2: PWM on Arduino

PWM Pins

Not all Arduino pins support PWM. Only pins marked with ~ can output PWM:

  • Arduino Uno: Pins 3, 5, 6, 9, 10, 11
  • These pins are controlled by timer registers

analogWrite() Function

Arduino provides analogWrite() to output PWM:

analogWrite(pin, value);
  • pin: PWM-capable pin number
  • value: 0 to 255 (0 = always off, 255 = always on)
Value Duty Cycle Average Voltage
0 0% 0V
64 25% 1.25V
128 50% 2.5V
191 75% 3.75V
255 100% 5V

Default PWM Frequency

Arduino PWM runs at approximately 490 Hz or 980 Hz (pins 5 and 6).

This is fast enough that: - LEDs appear to dim smoothly - Motors run smoothly - Humans don't hear the switching


Part 3: Project - LED Brightness Control

Circuit

Arduino Pin 9 ──[220Ω resistor]─── LED ── GND

Pin 9 is a PWM pin (marked with ~).

Code - Dimming an LED

int ledPin = 9;        // PWM-capable pin
int brightness = 0;    // Initial brightness (0-255)
int fadeAmount = 5;    // How much to change each step

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

void loop() {
  // Set LED brightness
  analogWrite(ledPin, brightness);

  // Change brightness for next iteration
  brightness = brightness + fadeAmount;

  // Reverse direction at ends
  if (brightness <= 0 || brightness >= 255) {
    fadeAmount = -fadeAmount;
  }

  delay(30);  // Wait 30ms before changing again
}

How It Works

  1. Start with brightness = 0 (LED off)
  2. Gradually increase brightness
  3. When reaches 255, reverse direction
  4. Gradually decrease back to 0
  5. Repeat forever

The result: LED pulses smoothly from dim to bright and back.

Practice: Different Fade Speeds

Try changing these values: - fadeAmount = 1 - Very slow fade - fadeAmount = 20 - Fast pulse - delay(10) - Faster overall cycle - delay(100) - Slower overall cycle


Part 4: Project - DC Motor Speed Control

Circuit

Important: Motors need more current than Arduino can provide. Use a motor driver!

Arduino Pin 5 ──▶ L293D Input
Motor ──▶ L293D Output
+5V ──▶ L293D VCC2
GND ──▶ L293D GND

Code

int motorPin = 5;       // PWM pin connected to motor driver
int speed = 0;          // Motor speed (0-255)

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

void loop() {
  // Accelerate from stop to full speed
  for (speed = 0; speed <= 255; speed += 5) {
    analogWrite(motorPin, speed);
    delay(50);  // Gradual acceleration
  }

  // Hold at full speed
  delay(1000);

  // Decelerate to stop
  for (speed = 255; speed >= 0; speed -= 5) {
    analogWrite(motorPin, speed);
    delay(50);  // Gradual deceleration
  }

  delay(1000);  // Hold at stop
}

Controlling Direction

To control direction (forward/reverse), use an H-bridge like the L293D:

int enablePin = 5;    // Speed control (PWM)
int in1Pin = 4;       // Direction control
int in2Pin = 3;       // Direction control

void setup() {
  pinMode(enablePin, OUTPUT);
  pinMode(in1Pin, OUTPUT);
  pinMode(in2Pin, OUTPUT);
}

void setMotor(int speed, boolean reverse) {
  analogWrite(enablePin, speed);
  digitalWrite(in1Pin, !reverse);
  digitalWrite(in2Pin, reverse);
}

void loop() {
  setMotor(200, false);  // Forward at 78% speed
  delay(2000);
  setMotor(0, false);    // Stop
  delay(1000);
  setMotor(200, true);   // Reverse at 78% speed
  delay(2000);
  setMotor(0, false);    // Stop
  delay(1000);
}

Part 5: Servo Motors

What is a Servo?

A servo motor rotates to a specific angle (typically 0° to 180°) and holds that position.

Servo Control

Servos use a special PWM signal: - Pulse width determines angle - 20ms period (50 Hz refresh rate)

Pulse Width Angle
1 ms
1.5 ms 90°
2 ms 180°

Arduino Servo Library

Arduino includes a built-in Servo library:

#include <Servo.h>

Servo myServo;    // Create servo object
int pos = 0;      // Current position (angle)

void setup() {
  myServo.attach(9);  // Attach to pin 9
}

void loop() {
  // Sweep from 0 to 180 degrees
  for (pos = 0; pos <= 180; pos += 1) {
    myServo.write(pos);  // Move to position
    delay(15);           // Wait for servo to reach position
  }

  // Sweep back from 180 to 0 degrees
  for (pos = 180; pos >= 0; pos -= 1) {
    myServo.write(pos);
    delay(15);
  }
}

Key Servo Functions

Function Description
Servo.attach(pin) Connect servo to a pin
Servo.write(angle) Move to angle (0-180)
Servo.writeMicroseconds(us) Set pulse width in microseconds
Servo.read() Read current angle

Part 6: Practice Problems

Problem 1: LED Breathing

Write a program that creates a "breathing" LED effect - starts at 0%, gradually increases to 100%, then back to 0%, repeating.

Show Solution
int ledPin = 9;
int brightness = 0;

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

void loop() {
  // Fade in
  for (int brightness = 0; brightness <= 255; brightness += 1) {
    analogWrite(ledPin, brightness);
    delay(10);
  }

  // Fade out
  for (int brightness = 255; brightness >= 0; brightness -= 1) {
    analogWrite(ledPin, brightness);
    delay(10);
  }

  delay(500);  // Pause at each end
}

Problem 2: Variable Speed Motor

Modify the motor speed control to use a potentiometer for speed control.

Show Solution
int motorPin = 5;
int potPin = A0;    // Potentiometer on analog pin 0
int speed = 0;

void setup() {
  pinMode(motorPin, OUTPUT);
  pinMode(potPin, INPUT);
}

void loop() {
  // Read potentiometer (0-1023)
  int potValue = analogRead(potPin);

  // Map to motor speed (0-255)
  speed = map(potValue, 0, 1023, 0, 255);

  // Control motor
  analogWrite(motorPin, speed);

  delay(10);  // Small delay for stability
}

Problem 3: Servo Position Control

Write a program that moves a servo to 0°, 45°, 90°, 135°, 180° with a button press.

Show Solution
#include <Servo.h>

Servo myServo;
int servoPin = 9;
int buttonPin = 2;
int angles[] = {0, 45, 90, 135, 180};
int currentIndex = 0;

void setup() {
  myServo.attach(servoPin);
  pinMode(buttonPin, INPUT_PULLUP);
  myServo.write(angles[0]);  // Start at 0 degrees
}

void loop() {
  if (digitalRead(buttonPin) == LOW) {
    delay(50);  // Debounce
    currentIndex = (currentIndex + 1) % 5;  // Next angle
    myServo.write(angles[currentIndex]);

    // Wait for button release
    while (digitalRead(buttonPin) == LOW) {
      delay(10);
    }
    delay(50);  // Additional debounce
  }
}

Summary

  • PWM rapidly switches between ON and OFF to control average power
  • Duty cycle = percentage of time signal is HIGH
  • Arduino PWM: analogWrite(pin, value) where value = 0-255
  • LED dimming uses PWM (value controls brightness)
  • Motor speed uses PWM (value controls speed)
  • Servo motors use angle-based control via Servo library

Key Reminders

  • Only PWM pins (marked ~) support analogWrite()
  • Motors need motor drivers (L293D) - never connect directly to Arduino
  • Higher duty cycle = more power/brightness/speed
  • Servo library handles the special PWM timing automatically
  • 0-255 maps to 0-100% duty cycle

Custom activity — adapted from PLTW Digital Electronics