Learn

PWM on the Raspberry Pi Pico

Sign up freeor see every lesson
  • 192parts on the bench
  • 25boards running now
  • 1.00xreal time, on every board
Raspberry Pi Pico: fadelive0.000 s 0.00x
Click to open it in the editor
The LED on GP25 and an LED on GP14 breathing on hardware PWM, with a scope on GP14.

On an Uno, analogWrite works on six pins marked with a tilde. On a Raspberry Pi Pico it works on every GPIO, because the RP2040 has sixteen PWM channels and a way to route them to any pin. That freedom comes with one rule you need to know before your second PWM output does something strange: pins share hardware in pairs, and in a way that is easy to miss, in pairs sixteen pins apart.

The circuit above is a Pico running its built-in fade example: the LED inside the module on GP25 and an LED with a 220 ohm resistor on GP14 both breathe up and down together. A scope is clipped to GP14. Press Run and its readout shows a frequency of 1.00 kHz and about 3.2 V peak to peak, while the width of each pulse grows and shrinks with the fade.

Slices and channels

The RP2040’s PWM hardware is eight slices. Each slice is one counter with its own frequency, and two outputs, channel A and channel B, with their own duty cycles. GPIO n belongs to slice (n / 2) mod 8, and it is channel A when n is even and channel B when n is odd. GP14 is slice 7, channel A. GP25 is slice 4, channel B.

Two things follow. First, GP14 and GP15 share slice 7, so they must run at the same frequency, though each can have its own duty cycle. That matters when one pin drives an LED and the other a servo that needs 50 Hz. Second, because there are only eight slices for thirty pins, GP0 and GP16 are both slice 0 channel A. They are the same output, so they cannot have different duty cycles at all. When you need outputs that are fully independent, pick pins from different slices: not an even pin and the odd pin after it, and not two pins sixteen apart.

Where 1 kHz comes from

A slice counts system clock ticks, divided by a fractional divider, from 0 up to a value called TOP, then wraps. The output is high while the count is below the channel’s compare value. So the frequency is the clock divided by the divider times (TOP + 1), and the duty is the compare value divided by (TOP + 1).

Mokxi’s Pico runtime sets TOP to 32767 and the divider to 3.8125 on the 125 MHz clock: 125,000,000 / (3.8125 x 32768) = 1000.6 Hz, which the scope reads as 1.00 kHz. analogWrite still takes 0 to 255, and the runtime scales it up by 128 to fill the larger counter. The divider is 8.4 fixed point and cannot go above 255.9375, which is why a slow frequency needs a large TOP rather than a large divider.

The peak to peak reads about 3.2 V rather than 5 V because the Pico is a 3.3 V board. The same LED needs a different resistor from an Uno: with a red LED dropping about 2 V, (3.3 - 2) / 220 ohms is about 6 mA.

The fade itself

The sketch adds 5 to the duty every 15 milliseconds and reverses at 0 and 255, so one breath up and down takes 102 steps, about 1.5 seconds. At 0 and 255 the runtime takes the pin off the PWM and drives it low or high directly, so "off" is really off rather than a very short pulse.

This is a straight ramp, and it shows: the LED seems to rush through its dim end and linger at bright. Your eye is more sensitive to changes in dim light, so a smoother fade uses a curve. The Arduino PWM fade page uses a cosine table for exactly that reason; the same table works on the Pico.

On a Pico W, and what this simulator does not model

On a Pico W the on-board LED is not an RP2040 pin at all. It hangs off the wireless chip, which is reached over its own link, so analogWrite(LED_BUILTIN) cannot dim it; digitalWrite still turns it on and off. On a Pico W, fade a GPIO like GP14.

Mokxi models the RP2040’s GPIO, PWM, UART and timer on one core. It does not model the second core, the PIO state machines, the ADC, the I2C and SPI peripherals, or DMA. So this page cannot show you PIO or dual-core code running, and we would rather say that than show you a picture of it. For analog input and PIO work, the real board is the tool; the Pico page on this site lists exactly what runs.

Questions

Which Raspberry Pi Pico pins support PWM?

All of the GPIO pins, GP0 to GP28. Pairs of pins share a slice and therefore a frequency, and pins sixteen apart share the same channel.

Can I change the PWM frequency on a Pico?

Yes, per slice, by changing the divider and TOP. In the Arduino-Pico core that is analogWriteFreq and analogWriteRange; with the Pico SDK it is pwm_set_clkdiv and pwm_set_wrap. Remember that both pins of a slice change together.

Why is the peak voltage 3.2 V and not 3.3 V?

The pin is driving an LED through a resistor, and a real pin has a little resistance of its own, so its high level sags slightly under load. The more current the pin supplies, the further it sags, which is one reason to keep LED currents modest on a 3.3 V board.

Can Mokxi run MicroPython on the Pico?

No. Mokxi runs Arduino-style C++ compiled for the RP2040. Most Pico tutorials use MicroPython, and the ideas on this page (slices, channels, duty) are the same there.

Build this for real

Open the editor, change a value and watch the number move with it. Nothing to install, and no account needed.