Skip to content

Pulse width modulation

Published: 2022-01-03
Last update: 2023-06-20

In this tutorial you will learn how to get analog results with digital means. You will use digital control to create a square wave, a signal switched between ON and OFF. By changing the portion of the time the signal spends ON versus the time that the signal spends OFF your microcontroller can simulate any voltage in between the full operating voltage (e.g., 5 V on UNO, 3.3 V on a MKR board) and off (0 Volts).

Table of contents

Microcontroller's pins

I explain different type of microcontroller's pins in Different voltage level coexistence part. Please refer to section Type of pins if this topic is new for you or you don't remember this material.

One thing you should keep in mind is that in this part you will use only pure digital pins, that is the pins which can output only two levels of signal: either ON which is the full operating voltage Vcc dependent on the platform you use (e.g., 5 V on UNO, 3.3 V on a MKR board) corresponding to HIGH state or OFF (0 Volts), corresponding to LOW state.


Experiment 1

Assembly simple circuit given below:

Next upload the following code:

When you execute the code you will see the LED turning on and turning off with low rate (1000ms for each state). At the same time voltage observed on multimeter changes from LOW level to HIGH level and back:

Left: LED is turned off (LOW voltage level, close to 0V) Right: LED is turned on (HIGH voltage level, close to 5V)

Experiment 2

In this experiment you use the same circuit as before and almost the same code with tiny modification -- replace the line:

with the:

This way you obtain a high-speed LED switching (1ms for each state). Now voltage is very stable, around 2.2V:

You should expect voltage close to 2.5V:

However, as you have seen in Experiment 1, HIGH voltage is not exactly 5V but rather 4.4V and hence the result:

Your LED shouldn't be so bright as before. To make it clear you can use circuit with two "reference" LEDs: one (most left) always turned off, and second (most right) always fully turned on:

Experiment 3

In this experiment you will continue high speed switching approach but also try to change the portion of the time the signal spends ON versus the time that the signal spends OFF.

First change the code to the following form:

Two constants DELAY_TIME_HIGH and DELAY_TIME_LOW allow you to control independently length of HIGH and LOW state.

Test three cases:

  • HIGH=1ms, LOW=2ms
    Expected voltage is:

    Taking into account that voltage is rather 4.4V than 5V, expected result is:

  • Now "reverse" proportions and set HIGH=2ms and LOW=1ms. For these assumptions you will obtain:

  • And finally make a test for HIGH=1ms, LOW=9ms:

Experiment 4

This experiment requires you to add potentiometer to use analog signal to control LED brightness by changing proportions between the length of HIGH and LOW state without the need of programming microcontroller again and again:

  • Visible flickering
    Use the following code:

    The formula:

    computes brightness to be in range from 0 to 35.

    With the above code you can control brightness but you will see visible and annoying flickering.

  • Visible flickering for selected value
    Using the code from previous experiment note value B of brightness for which you obtain the most visible flickering. In my case it was B=35. Please note that the best results you will have with real hardware and I strongly encourage you to use it instead of simulation in for example Tinkercad.

    Substitute this value instead of brightness and modify the code (at this moment potentiometer is not needed):

  • Keep the same proportion but make the whole cycle shorter
    From previous experiment you know the worse case for brightness. Now you take flickering code you used there and, preserving ratio of HIGH to LOW state which is equal to 1/B as well as cycle time which is equal to 1+B, you change (decrease) time unit from milliseconds to microseconds (1 second [s] = 1000 milliseconds [ms]; 1 milliseconds = 1000 microseconds [us]):

    Now some magic happens. Ratio of HIGH to LOW is the same but because of different time unit (much shorter) flickering is not visible. Of course it doesn't mean that flickering doesn't exist. It is present all the time but for our eyes it is not noticeable.

  • Final step: full control of brightness without flickering thanks to using shorter cycles
    With the following code:

    you get full control of brightness without flickering thanks to using shorter cycles.


The proportion between HIGH state and LOW state is important as it decide about (averaged) output voltage. All timings given below have the same proportion:

but, as you have seen in experiments, output voltage was the most stable in the last case, where total time of one HIGH+LOW cycle was the shortest.

In consequence, you can say that both proportion and length of time window (total time of one cycle) is important.

The duration of ON time is called the pulse width. To get varying analog values, you change (sometimes we say modulate) that pulse width. As you saw, if you apply this pulse width on-off pattern and repeat fast enough, the result is as if the signal is a steady voltage between 0 and Vcc -- you obtain approximated analog value. But still you operate with a square wave, taking only HIGH and LOW value. Averaging these values through time gives approximated analog voltage between 0 and Vcc.

The ratio of time a circuit or load is ON compared to the time the circuit or load is OFF is called duty cycle (sometimes called also duty factor) and is expressed as a percentage of ON time.

PWM -- Hardware switching

Approach examined so far has one undeniable advantage -- you can apply it to any digital output pin. In addition, you have full control on the duty cycle and frequency, however to be honest, it may be quite difficult to determine the appropriate constants for a particular duty cycle and frequency.

The most important disadvantage is that you can't leave the output running with the predefined duty cycle and frequency while the processor does something else.

Because the underlying mechanism is rather simple, the best option would be to have dedicated circuit inside microcontroller to make automatic hardware control on timing for ON and OFF state on a predefined pin. And this is exactly for what PWM (Pulse Width Modulation) exists.

In the Arduino's ecosystem to use PWM you simply call analogWrite(pin, dutyCycle), where dutyCycle is a value from 0 to 255, and pin is one of the PWM pins, because not all digital pins can be controlled by hardware. Often you recognize PWM pins thanks to tilde sign ~ preceding pin number:

The analogWrite() function provides a simple interface to the hardware PWM, but doesn't provide any control over frequency. To fully control PWM you have to dive into more technical details -- please read my XXX tutorial, if you are interested in this topic.

Note that analogWrite() function name is misleading as the output is a pure digital signal, often referred to as a square wave, taking only HIGH and LOW value. Averaging is what gives this signal the character of an analog signal between 0 and Vcc.

analogWrite() function usage is very simply -- see the following example:

Be aware of one detail: you must use PWM pin, for example pin number 11 in Arduino UNO.

Left: 100% duty cycle (always ON) Right: 50% duty cycle (ON half of the time)
Left: 20% duty cycle Right: 0% duty cycle (always OFF)


  • Applications 1: LED
    With hardware PWM LED brightness control becomes trivial tasks. Use the following circuit and the code given below:

  • Application 2: control RGB LED
    You can use a similar approach to control color of RGB LED; each component is controlled by dedicated PWM pin.

    You can apply one of the following codes to your RGB LED:

  • Application 3: servo
    Also servos are controlled with PWM signal; you do this exactly the same way as you do for LED. Please try it on your own. More about servos you can find in my tutorial XXX.