The RP2040 has hardware support for standard communication protocols like I2C, SPI and UART. For protocols where there is no hardware support, or where there is a requirement of custom I/O behaviour, Programmable Input Output (PIO) comes into play. Also, some MicroPython applications make use of a technique called bit banging in which pins are rapidly turned on and off to transmit data. This can make the entire process slow as the processor concentrates on bit banging rather than executing other logic. However, PIO allows bit banging to happen in the background while the CPU is executing the main work.
Along with the two central Cortex-M0+ processing cores, the RP2040 has two PIO blocks each of which has four independent state machines. These state machines can transfer data to/from other entities using First-In-First-Out (FIFO) buffers, which allow the state machine and main processor to work independently yet also synchronise their data. Each FIFO has four words (each of 32 bits) which can be linked to the DMA to transfer larger amounts of data.
All PIO instructions follow a common pattern:
<instruction> .side(<side_set_value>) [<delay_value>]
.side(...) and delay
[...] parts are both optional, and if
specified allow the instruction to perform more than one operation. This keeps
PIO programs small and efficient.
There are nine instructions which perform the following tasks:
jmp()transfers control to a different part of the code
wait()pauses until a particular action happens
in_()shifts the bits from a source (scratch register or set of pins) to the input shift register
out()shifts the bits from the output shift register to a destination
push()sends data to the RX FIFO
pull()receives data from the TX FIFO
mov()moves data from a source to a destination
irq()sets or clears an IRQ flag
set()writes a literal value to a destination
The instruction modifiers are:
.side()sets the side-set pins at the start of the instruction
delays for a certain number of cycles after execution of the instruction
There are also directives:
wrap_target()specifies where the program execution will get continued from
wrap()specifies the instruction where the control flow of the program will get wrapped from
label()sets a label for use with
word()emits a raw 16-bit value which acts as an instruction in the program
pio_1hz.py example for a simple understanding of how to use the PIO
and state machines. Below is the code for reference.
# Example using PIO to blink an LED and raise an IRQ at 1Hz. import time from machine import Pin import rp2 @rp2.asm_pio(set_init=rp2.PIO.OUT_LOW) def blink_1hz(): # Cycles: 1 + 1 + 6 + 32 * (30 + 1) = 1000 irq(rel(0)) set(pins, 1) set(x, 31)  label("delay_high") nop()  jmp(x_dec, "delay_high") # Cycles: 1 + 7 + 32 * (30 + 1) = 1000 set(pins, 0) set(x, 31)  label("delay_low") nop()  jmp(x_dec, "delay_low") # Create the StateMachine with the blink_1hz program, outputting on Pin(25). sm = rp2.StateMachine(0, blink_1hz, freq=2000, set_base=Pin(25)) # Set the IRQ handler to print the millisecond timestamp. sm.irq(lambda p: print(time.ticks_ms())) # Start the StateMachine. sm.active(1)
This creates an instance of class
rp2.StateMachine which runs the
blink_1hz program at 2000Hz, and connects to pin 25. The
program uses the PIO to blink an LED connected to this pin at 1Hz, and also
raises an IRQ as the LED turns on. This IRQ then calls the
which prints out a millisecond timestamp.
blink_1hz program is a PIO assembler routine. It connects to a single
pin which is configured as an output and starts out low. The instructions do
irq(rel(0))raises the IRQ associated with the state machine.
The LED is turned on via the
The value 31 is put into register X, and then there is a delay for 5 more cycles, specified by the
nop() instruction waits for 30 cycles.
jmp(x_dec, "delay_high")will keep looping to the
delay_highlabel as long as the register X is non-zero, and will also post-decrement X. Since X starts with the value 31 this jump will happen 31 times, so the
nop() runs 32 times in total (note there is also one instruction cycle taken by the
jmpfor each of these 32 loops).
set(pins, 0)will turn the LED off by setting pin 25 low.
Another 32 loops of
wrap()are not specified, their default will be used and execution of the program will wrap around from the bottom to the top. This wrapping does not cost any execution cycles.
The entire routine takes exactly 2000 cycles of the state machine. Setting the frequency of the state machine to 2000Hz makes the LED blink at 1Hz.