Normally, you'd use DigitalInOut to turn an LED on and off in CircuitPython. However, as a simple introduction to PIO, it can also be used to turn an LED on or off.

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 example https://github.com/raspberrypi/pico-examples/tree/master/pio/hello_pio

import time
import board
import rp2pio
import adafruit_pioasm

hello = """
.program hello
loop:
    pull
    out pins, 1
; This program uses a 'jmp' at the end to follow the example.  However,
; in a many cases (including this one!) there is no jmp needed at the end
; and the default "wrap" behavior will automatically return to the "pull"
; instruction at the beginning.
    jmp loop
"""

assembled = adafruit_pioasm.assemble(hello)

sm = rp2pio.StateMachine(
    assembled,
    frequency=2000,
    first_out_pin=board.LED,
)
print("real frequency", sm.frequency)

while True:
    sm.write(bytes((1,)))
    time.sleep(0.5)
    sm.write(bytes((0,)))
    time.sleep(0.5)

Save the file below as code.py and transfer it to your CIRCUITPY drive. Your device should automatically restart and run the code. Shortly, a LED will blink on and off about once every second. If it doesn't, use Mu to connect to the Serial REPL of your device and you'll be able to see any errors that occurred.

Code Walkthrough

The full program is shown above, but let's look at the interesting bits a few lines at a time:

hello = """
.program hello
loop:
    pull
    out pins, 1
; This program uses a 'jmp' at the end to follow the example. However,
; in a many cases (including this one!) there is no jmp needed at the end
; and the default "wrap" behavior will automatically return to the "pull"
; instruction at the beginning.
    jmp loop
"""

PIO programs are included within your CircuitPython source as strings, and then converted into a program with the assemble function.

sm = rp2pio.StateMachine(
    assembled,
    frequency=10000,
    first_out_pin=board.LED,
)

The PIO peripheral contains several "state machines", which are the units that run PIO programs. The StateMachine constructor takes the assembled program as well as some additional information:

  • frequency says how quickly each pio instruction executes. If you have a task that you need to take "exactly X microseconds" or "execute at exactly Y kHz", this will allow you to determine the right frequency value.
  • first_out_pin names the first pin that will be updated by out instructions in the PIO program. This program only affects a single pin.
while True:
    sm.write(bytes((1,)))
    time.sleep(0.5)
    sm.write(bytes((0,)))
    time.sleep(0.5)

The forever-loop of our Python code alternates between sending the byte 1 and the byte 0 to the PIO state machine. Each time a byte is sent, the PIO program acts on it.

loop:
    pull
    out pins, 1
    jmp loop

Every PIO instruction is documented in the RP2040 datasheet, so while this guide will give a high-level description of what is happening, it eliminates many details in order to keep the descriptions sort.

The first line, loop:, is a label. This line, together with the last line jmp loop create a forever-loop.

The second line contains the first instruction, pull, which waits until a value is sent to the State Machine (A value is sent by the sm.write function calls in our Python code). The value is stored in a location (register) called the OSR (Output Shift Register).

The next line contains another instruction, out pins, 1. This is our first instruction with operands. The first operand, pins, says where the data is being transferred to. The second operand, 1, says how many bits are being transferred. The source of the data is the OSR, the same as the implicit destination of the pull instruction.

The final line contains the last instruction, jmp loop. A jmp instruction makes the program continue at the named location instead of the next line.

The net effect of this program is to turn the related pin HIGH if the number sent is even and the pin LOW if the number sent is odd. And that's why the Python forever-loop makes the Pico's LED turn off and on about once per second.

This guide was first published on Mar 03, 2021. It was last updated on 2021-03-03 10:39:28 -0500.

This page (Using PIO to turn an LED on and off) was last updated on Oct 15, 2021.

Text editor powered by tinymce.