Normally, you'd use PWMOut to control the brightness of an LED connected to a GPIO pin. However, as you may expect, you can also use PIO to create a PWM-like effect.

Here's a CircuitPython program to do just that:

# SPDX-FileCopyrightText: 2021 Jeff Epler, written for Adafruit Industries
#
# SPDX-License-Identifier: MIT
#
# Adapted from the an example in Appendix C of RPi_PiPico_Digital_v10.pdf

import time
import board
import rp2pio
import adafruit_pioasm

led_quarter_brightness = adafruit_pioasm.assemble(
    """
    set pins, 0 [2]
    set pins, 1
"""
)

led_half_brightness = adafruit_pioasm.assemble(
    """
    set pins, 0
    set pins, 1
"""
)

led_full_brightness = adafruit_pioasm.assemble(
    """
    set pins, 1
"""
)

while True:
    sm = rp2pio.StateMachine(
        led_quarter_brightness, frequency=10000, first_set_pin=board.LED
    )
    time.sleep(1)
    sm.deinit()

    sm = rp2pio.StateMachine(
        led_half_brightness, frequency=10000, first_set_pin=board.LED
    )
    time.sleep(1)
    sm.deinit()

    sm = rp2pio.StateMachine(
        led_full_brightness, frequency=10000, first_set_pin=board.LED
    )
    time.sleep(1)
    sm.deinit()

Code Walkthrough

The full program is shown above, but let's look at the interesting bits a few lines at a time. In this example, there are three different PIO programs, each one for a different brightness level:

led_quarter_brightness = adafruit_pioasm.assemble(
    """
    set pins, 0 [2]
    set pins, 1
""")

led_half_brightness = adafruit_pioasm.assemble(
    """
    set pins, 0
    set pins, 1
""")

led_full_brightness = adafruit_pioasm.assemble(
    """
    set pins, 1
""")

The "full brightness" program is most self-explanatory: at each moment, it sets its corresponding pin to 1.

The "half brightness" program alternately sets its pin to 0 and then to 1. When it changes rapidly between these two states, the LED appears lit, but dim, to a human eye. With a few exceptions, each instruction takes the same length of time. For one instruction the LED is off and for one instruction the LED is on, so the LED is turned on half the time.

The "quarter brightness" program needs the most explanation. The new element on the first set line, [2], indicates that after the set command, there is an additional delay of 2 cycles before going to the next instruction. That means that the pin is 0 for 3 cycles and 1 for 1 cycle, giving a ratio of 1/4.

while True:
    sm = rp2pio.StateMachine(led_quarter_brightness,
        frequency=10000, first_set_pin=board.LED)
    time.sleep(1)
    sm.deinit()

    sm = rp2pio.StateMachine(led_half_brightness,
        frequency=10000, first_set_pin=board.LED)
    time.sleep(1)
    sm.deinit()

    sm = rp2pio.StateMachine(led_full_brightness,
        frequency=10000, first_set_pin=board.LED)
    time.sleep(1)
    sm.deinit()

The CircuitPython forever loop cycles among the three programs, for one second each. frequency=10000, or 10kHz, means that the on-off cycle of the LED is far too fast to be seen by a human eye; the quarter-brightness LED turns on and off 2500 times per second, or 100 times faster than a "24fps" film.

Because the LED is turned fully off for a short time between programs, you may see a flicker when the brightness changes.

This guide was first published on Mar 03, 2021. It was last updated on May 10, 2022.

This page (Using PIO to control LED brightness) was last updated on Oct 04, 2023.

Text editor powered by tinymce.