Datasheet

Table Of Contents
Figure 56. Pulse width
modulation (PWM).
The state machine
outputs positive
voltage pulses at
regular intervals. The
width of these pulses
is controlled, so that
the line is high for
some controlled
fraction of the time
(the duty cycle). One
use of this is to
smoothly vary the
brightness of an LED,
by pulsing it faster
than human
persistence of vision.
This program repeatedly counts down to 0 with the Y register, whilst comparing the Y count to a pulse width held in the
X register. The output is asserted low before counting begins, and asserted high when the value in Y reaches X. Once Y
reaches 0, the process repeats, and the output is once more driven low. The fraction of time that the output is high is
therefore proportional to the pulse width stored in X.
Pico Examples: https://github.com/raspberrypi/pico-examples/tree/master/pio/pwm/pwm.pio Lines 9 - 21
Ê9 .program pwm
10 .side_set 1 opt
11
12 pull noblock side 0 ; Pull from FIFO to OSR if available, else copy X to OSR.
13 mov x, osr ; Copy most-recently-pulled value back to scratch X
14 mov y, isr ; ISR contains PWM period. Y used as counter.
15 countloop:
16 jmp x!=y noset ; Set pin high if X == Y, keep the two paths length matched
17 jmp skip side 1
18 noset:
19 nop ; Single dummy cycle to keep the two paths the same length
20 skip:
21 jmp y-- countloop ; Loop until Y hits 0, then pull a fresh PWM value from FIFO
Often, a PWM can be left at a particular pulse width for thousands of pulses, rather than supplying a new pulse width
each time. This example highlights how a nonblocking PULL (Section 3.4.7) can achieve this: if the TX FIFO is empty, a
nonblocking PULL will copy X to the OSR. After pulling, the program copies the OSR into X, so that it can be compared to
the count value in Y. The net effect is that, if a new duty cycle value has not been supplied through the TX FIFO at the
start of this period, the duty cycle from the previous period (which has been copied from X to OSR via the failed PULL, and
then back to X via the MOV) is reused, for as many periods as necessary.
Another useful technique shown here is using the ISR as a configuration register, if IN instructions are not required.
System software can load an arbitrary 32-bit value into the ISR (by executing instructions directly on the state machine),
and the program will copy this value into Y each time it begins counting. The ISR can be used to configure the range of
PWM counting, and the state machine’s clock divider controls the rate of counting.
To start modulating some pulses, we first need to map the state machine’s side-set pins to the GPIO we want to output
PWM on, and tell the state machine where the program is loaded in the PIO instruction memory:
Pico Examples: https://github.com/raspberrypi/pico-examples/tree/master/pio/pwm/pwm.pio Lines 24 - 30
24 static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin) {
25 pio_gpio_init(pio, pin);
26 pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
27 pio_sm_config c = pwm_program_get_default_config(offset);
28 sm_config_set_sideset_pins(&c, pin);
29 pio_sm_init(pio, sm, offset, &c);
30 }
A little footwork is required to load the ISR with the desired counting range:
Pico Examples: https://github.com/raspberrypi/pico-examples/tree/master/pio/pwm/pwm.c Lines 14 - 20
14 void pio_pwm_set_period(PIO pio, uint sm, uint32_t period) {
15 pio_sm_set_enabled(pio, sm, false);
RP2040 Datasheet
3.6. Examples 384