DEV Community

IgorIOT
IgorIOT

Posted on

Beyond Blink: Understanding PWM with Raspberry Pi and Java


Alright boss?

In the previous article, we learned how to turn an LED on and off using a Raspberry Pi GPIO pin. We wrote our first Pi4J application, explored digital outputs, and made an LED blink.

But what if we want something in between?

Can an LED be half on?

Can we create smooth fading effects?

Can we control motor speed, fan speed, or even generate analog-like voltages using only digital GPIO pins?

The answer is PWM.

What Is PWM?

PWM stands for Pulse Width Modulation.

Despite the intimidating name, the idea is surprisingly simple:

Instead of changing the voltage, we rapidly switch the GPIO pin between ON and OFF.

If we do this fast enough, the LED appears dimmer or brighter depending on how long it stays ON during each cycle.

The GPIO Problem

A GPIO pin is digital.

It only knows two states:

LOW = 0V
HIGH = 3.3V

There is no built-in way to output:

1.65V
2.1V
0.8V

At least not directly.

So how do we create different brightness levels?

The PWM Trick

Imagine a GPIO pin switching on and off repeatedly.

100% Duty Cycle
████████████████

The signal is always ON.

The LED is fully bright.

50% Duty Cycle
████_████_

The signal spends half of its time ON and half OFF.

The LED appears approximately half as bright.

25% Duty Cycle
██___██___

The LED appears much dimmer.

Duty Cycle

The most important PWM concept is the duty cycle.

It represents the percentage of time the signal remains ON during a complete cycle.

Examples:


Table 01: Duty Cycle

Mathematically:

Duty Cycle = ON Time / Total Period × 100%

Frequency Matters

PWM is defined by two parameters:

  1. Duty Cycle
  2. Frequency

Frequency tells us how many complete cycles occur every second.

For example:

100 Hz

means:

100 cycles per second

If the frequency is too low, visible flickering may occur.

If the frequency is high enough, the LED appears continuously illuminated.

A typical LED application often uses frequencies ranging from a few hundred Hertz to several kilohertz.

Frequency vs Resolution

There is often a trade-off between PWM frequency and PWM resolution.

In many systems:

Higher Frequency

Lower Resolution
or
Higher Resolution

Lower Maximum Frequency
Enter fullscreen mode Exit fullscreen mode

This happens because the hardware has a limited amount of time to represent each PWM cycle.

As frequency increases, fewer timing steps are available.

Understanding this trade-off becomes important in more advanced projects.

Hardware PWM vs Software PWM

Not all PWM signals are generated the same way.

Software PWM

CPU
 
GPIO
Enter fullscreen mode Exit fullscreen mode

The CPU continuously generates the pulses.

Advantages

  • Works on many GPIO pins
  • Easy to configure
  • Flexible for simple projects

Disadvantages

  • Consumes CPU resources
  • Less precise timing
  • Accuracy may decrease at higher frequencies or under heavy system load

Hardware PWM

PWM Controller
       
     GPIO
Enter fullscreen mode Exit fullscreen mode

Dedicated hardware generates the signal.

Advantages

  • Extremely accurate timing
  • Minimal CPU usage
  • Stable frequencies and duty cycles
  • Ideal for LEDs, motors, audio generation, and other timing-sensitive applications

Disadvantages

  • Available only on specific GPIO pins
  • Limited number of hardware PWM channels

Key Difference

The biggest difference is where the PWM signal is generated:


Table 02: Software VS Hardware PWM

For most LED projects, both approaches work well. However, when accuracy, efficiency, or higher frequencies matter, Hardware PWM is usually the preferred choice.

Building the Circuit

The circuit is identical to the one used in the Blink article.

GPIO18
   |
 Resistor
   |
  LED
   |
  GND
Enter fullscreen mode Exit fullscreen mode

GPIO18 is a popular choice because it supports hardware PWM on Raspberry Pi.

PWM with Pi4J

Software PWM

Software PWM works on virtually any GPIO configured as an output.

import com.pi4j.Pi4J;
import com.pi4j.context.Context;
import com.pi4j.io.pwm.Pwm;
import com.pi4j.io.pwm.PwmType;

public class SoftwarePwmExample {

   public static void main(String[] args) throws Exception {

       Context pi4j = Pi4J.newAutoContext();

       var pwmConfig = Pwm.newConfigBuilder(pi4j)
               .id("soft-pwm")
               .name("Software PWM LED")
               .address(18)          // BCM GPIO18
               .pwmType(PwmType.SOFTWARE)
               .provider("pigpio-pwm")
               .initial(0)
               .shutdown(0)
               .build();

       Pwm pwm = pi4j.create(pwmConfig);

       for (int duty = 0; duty <= 100; duty++) {
           pwm.on(duty, 1000);
           Thread.sleep(20);
       }

       pwm.off();
       pi4j.shutdown();
   }
}
Enter fullscreen mode Exit fullscreen mode

What Happens Behind the Scenes?

Java Application
      
      CPU generates PWM pulses
      
     GPIO18
Enter fullscreen mode Exit fullscreen mode

In Software PWM, the CPU is responsible for continuously toggling the GPIO pin on and off according to the configured duty cycle and frequency.

Hardware PWM

Hardware PWM uses the Raspberry Pi's dedicated PWM controller.

import com.pi4j.Pi4J;
import com.pi4j.context.Context;
import com.pi4j.io.pwm.Pwm;
import com.pi4j.io.pwm.PwmType;

public class HardwarePwmExample {

   public static void main(String[] args) throws Exception {

       Context pi4j = Pi4J.newAutoContext();

       var pwmConfig = Pwm.newConfigBuilder(pi4j)
               .id("hw-pwm")
               .name("Hardware PWM LED")
               .address(18)
               .pwmType(PwmType.HARDWARE)
               .provider("pigpio-pwm")
               .initial(0)
               .shutdown(0)
               .build();

       Pwm pwm = pi4j.create(pwmConfig);

       pwm.on(50, 1000);

       Thread.sleep(5000);

       pwm.off();
       pi4j.shutdown();
   }
}
Enter fullscreen mode Exit fullscreen mode

What Happens Behind the Scenes?

Java Application
      
 PWM Hardware Controller
      
     GPIO18
Enter fullscreen mode Exit fullscreen mode

In Hardware PWM, the Raspberry Pi's PWM peripheral generates the signal independently of the CPU.

Once configured, the hardware continues producing precise PWM pulses even if the CPU is busy performing other tasks.

First PWM Example

Let's set the LED to 50% brightness.

pwm.on(50, 1000);
Enter fullscreen mode Exit fullscreen mode

This means:

50% Duty Cycle
1000 Hz Frequency

The LED now appears partially illuminated.

Creating a Fade Effect

One of the most satisfying PWM demonstrations is a smooth fade.

for (int duty = 0; duty <= 100; duty++) {
    pwm.on(duty, 1000);
    Thread.sleep(20);
}

for (int duty = 100; duty >= 0; duty--) {
    pwm.on(duty, 1000);
    Thread.sleep(20);
}
Enter fullscreen mode Exit fullscreen mode

The LED gradually brightens and then fades away.

This effect is often called a breathing LED.

PWM Doesn't Control Brightness. It Controls Energy.

This is a subtle but important distinction.

PWM doesn't directly tell an LED to become dimmer.

Instead, it controls how much energy is delivered to the load over time.

For an LED, that energy becomes perceived brightness.

For a DC motor, it becomes speed.

For a heating element, it becomes temperature.

The same PWM technique is used in all three cases.

Only the result changes.

Every Dimmed LED Is Actually Blinking

This surprises many beginners.

A dim LED is not receiving "half voltage."

It is actually turning completely ON and completely OFF hundreds or thousands of times per second.

When you see a LED at 50% brightness, what is really happening is:

ON
OFF
ON
OFF
ON
OFF
Enter fullscreen mode Exit fullscreen mode

Very fast.
So a more accurate statement would be:

Every dimmed LED is actually blinking. It's just blinking too fast for us to notice.

The LED Is Never Half On

This is one of the most common misconceptions.

The apparent brightness comes from how our eyes perceive the rapid switching.

Persistence of Vision

Human vision is not instantaneous.

Our eyes and brains effectively average fast changes in light.

If the LED switches thousands of times per second, we no longer perceive individual flashes.

Instead, we see a stable brightness level.

This phenomenon is called persistence of vision, and it is one of the reasons PWM works so well for LEDs.

PWM Is a Useful Lie

One of the most interesting ways to think about PWM is as a "useful lie."

The Raspberry Pi never outputs intermediate voltages through PWM.
It only outputs:

0V
or
3.3V
Enter fullscreen mode Exit fullscreen mode

Yet both humans and electronic circuits often behave as if intermediate voltages existed.

PWM tricks our eyes.

PWM tricks filters.

PWM tricks motors.

And that makes it incredibly useful.

Understanding Raspberry Pi GPIO Limitations

When working with PWM on a Raspberry Pi, it's important to remember that GPIO pins were never designed to be power outputs.

They are designed to be control signals.

This distinction explains many of the limitations developers encounter when starting with electronics.

GPIO Pins Are Not Power Supplies

A Raspberry Pi GPIO pin can output:

LOW = 0V

HIGH = 3.3V

However, that does not mean you can power arbitrary devices directly from a GPIO pin.

GPIO pins are intended to provide signals, not significant amounts of current.

Typical use cases include:

  • LEDs (with resistors)
  • Logic inputs
  • Sensors
  • Transistors
  • Driver circuits

Not:

  • Motors
  • High-power LEDs
  • Relays
  • Speakers
  • Servos

These devices usually require external power and dedicated drivers.

GPIO Pins Operate at 3.3V

Unlike some microcontroller boards, Raspberry Pi GPIOs are not 5V tolerant.

Applying 5V directly to a GPIO pin can permanently damage the processor.

Always verify:

Input Voltage ≤ 3.3V

when connecting external electronics.

Current Limitations

A GPIO pin can source or sink only a limited amount of current.

A common rule of thumb is:

≈16mA per GPIO

≈50mA total across all GPIOs

Always check the latest Raspberry Pi documentation for the exact specifications of your model.

This is why LEDs should always use a resistor.

For example:

GPIO
 |
220Ω
 |
LED
 |
GND
Enter fullscreen mode Exit fullscreen mode

The resistor protects both the LED and the GPIO pin.

PWM Limitations on Raspberry Pi

PWM is powerful, but it has limitations.

Understanding them helps explain why some projects work perfectly while others require additional hardware.

PWM Is Still Digital

This is the most important limitation.

PWM is not true analog output.

Even at 50% duty cycle, the signal remains:

0V
or
3.3V
Enter fullscreen mode Exit fullscreen mode

only.

PWM merely changes the timing.

Some devices are perfectly happy with this.

Others require actual analog voltages.

Not Every GPIO Supports Hardware PWM

One of the most common surprises for beginners.

Many GPIO pins can be used for:

Digital Input
Digital Output

but only a subset support hardware PWM.

For example, GPIO18 is commonly used because it supports hardware PWM functionality.

This means:

GPIO18 ✓
Random GPIO ? Maybe not

Depending on the PWM provider and Raspberry Pi model, available PWM pins may vary.

https://pinout.xyz/pinout/pwm

Limited Hardware PWM Channels

The Raspberry Pi contains a small number of hardware PWM generators.

Conceptually:

PWM Generator #1
PWM Generator #2

Multiple GPIOs may share these generators.

As a result, changing one PWM configuration can sometimes affect another pin using the same channel.

This limitation becomes important in projects involving:

  • Multiple motors
  • RGB LED strips
  • Audio generation
  • Robotics

PWM Cannot Source Significant Power

A common misconception:

PWM = More Power

Not really.

PWM changes average power delivered to a load.

The GPIO pin itself still has the same current limitations.

For example:

GPIO PWM
   |
Motor
Enter fullscreen mode Exit fullscreen mode

is generally a bad idea.
Instead:

GPIO PWM
   |
Transistor / MOSFET
   |
Motor Power Supply
Enter fullscreen mode Exit fullscreen mode

This allows PWM to control the motor while external circuitry handles the actual power.

Why PWM Is So Efficient

Before PWM became popular, many systems controlled power by wasting energy as heat.

Imagine trying to dim an LED using a variable resistor.

Power

Resistor

Heat
Enter fullscreen mode Exit fullscreen mode

A portion of the energy is simply lost.

PWM works differently.

Power

Load
Enter fullscreen mode Exit fullscreen mode

The signal is either fully ON or fully OFF.

Very little energy is wasted.

This is one of the reasons PWM is found in:

  • Electric vehicles
  • Drones
  • Industrial automation
  • LED lighting systems
  • Computer cooling fans
  • Switching power supplies

Efficiency is one of PWM's greatest strengths.

PWM Is Everywhere

PWM is far more important than controlling LEDs.

The same technique is used in:

  • Computer cooling fans
  • DC motor speed control
  • Servo motors
  • LED lighting systems
  • Audio synthesis
  • Switching power supplies
  • Industrial automation equipment

Once you understand PWM, you begin seeing it everywhere.

Experiment Time

Try connecting a multimeter to the filtered output.

Set the duty cycle to:

0%
25%
50%
75%
100%

Measure the voltage at each step.

You should observe values close to:

0.0V
0.8V
1.65V
2.5V
3.3V

This simple experiment demonstrates one of the most powerful ideas in electronics:

A digital signal can behave like an analog one.

The Smartphone Camera Experiment

Want to prove that the LED is actually blinking?

Point your smartphone camera at it.

Depending on the PWM frequency, you may see:

  • Flickering
  • Rolling bands
  • Horizontal stripes

The camera sensor often captures the rapid switching that our eyes cannot detect.

It's a simple experiment, but it makes PWM feel much more real.

A Fun Experiment

Try running exactly the same fade effect using both Software PWM and Hardware PWM.

for (int duty = 0; duty <= 100; duty++) {
   pwm.on(duty, 1000);
   Thread.sleep(20);
}
Enter fullscreen mode Exit fullscreen mode

First, configure the PWM instance as:

.pwmType(PwmType.SOFTWARE)
Enter fullscreen mode Exit fullscreen mode

Then run the same code again using:

.pwmType(PwmType.HARDWARE)
Enter fullscreen mode Exit fullscreen mode

At first glance, the LED will appear to behave exactly the same. Both versions will smoothly fade from dark to bright.

So what's the difference?

The answer is hidden behind the scenes.

The magic happens behind the scenes. In Software PWM, Java and the CPU generate every pulse. In Hardware PWM, a dedicated Raspberry Pi peripheral takes over the job and continues generating the signal even while your application is busy doing something else.

This distinction may not be visible when fading a single LED, but it becomes important in more demanding applications such as motor control, audio generation, robotics, or any project that requires precise timing.

A useful way to think about it is:

Software PWM

Java

CPU

GPIO
Enter fullscreen mode Exit fullscreen mode

versus

Hardware PWM

Java

PWM Controller

GPIO
Enter fullscreen mode Exit fullscreen mode

In the first case, the CPU is actively creating every pulse. In the second, the CPU simply configures the PWM controller and lets the hardware handle the rest.

For a simple LED project, both approaches work great. But understanding this difference is an important step toward understanding how embedded systems manage timing, performance, and hardware resources.

Final Thoughts

Blink taught us how to control a GPIO pin.

PWM teaches us how to control perception.

By rapidly switching a digital signal on and off, we can create smooth brightness transitions, control motors, regulate power, and even generate analog-like voltages.

What started as a blinking LED turns out to be one of the most important techniques in modern electronics.

And this is only the beginning.

Links

https://osteele.github.io/pwm-explorer/
https://en.wikipedia.org/wiki/Pulse-width_modulation

Top comments (0)