Graeme Winter

Switching PIO SM Programs

What if you need to use all four SM in one PIO, to run one of two programs which is stashed in the PIO program memory, and they share the same I/O configuration (for example, incrementing or decrementing quadrature A/B outputs based on input values) - well, you can with a teeny bit of register hacking.

The Problem

To output four channels of quadrature on a PIO requires using all four state machines. This is fine, but if you want to change the direction of travel then you need to be executing a different program, unless you want to mess around with streaming in the bit sequences to output.

The SM programs are defined by a current program counter (uint5_t) and a top / bottom instruction window (also uint5_t) that are defined in the SM CTRL registers. Switching programs is then as simple as resetting these register values to point at the instructions you want:

from machine import Pin, mem32
import rp2

PIO0_BASE = 0x50200000


@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW, sideset_init=(rp2.PIO.OUT_LOW, rp2.PIO.OUT_LOW))
def up():
    set(pins, 1).side(0)
    nop().side(1)
    set(pins, 0).side(3)
    nop().side(2)


@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW, sideset_init=(rp2.PIO.OUT_LOW, rp2.PIO.OUT_LOW))
def dn():
    set(pins, 0).side(0)
    nop().side(2)
    set(pins, 1).side(3)
    nop().side(1)


pins = [machine.Pin(j, machine.Pin.OUT) for j in range(4)]
led = machine.Pin(25, Pin.OUT)

sm0 = rp2.StateMachine(
    0, up, freq=62_500_000, in_base=pins[0], sideset_base=pins[2], set_base=led
)
sm1 = rp2.StateMachine(
    1, dn, freq=62_500_000, in_base=pins[0], sideset_base=pins[2], set_base=led
)


@micropython.asm_thumb
def advance(r0, r1):
    mov(r2, 0)
    str(r1, [r0, 0])
    str(r2, [r0, 0])
    mov(r0, 0)


mem32[PIO0_BASE | 0xD8] = 0x1C
for j in range(4):
    print(hex(mem32[PIO0_BASE | 0xD8]))
    advance(PIO0_BASE, 0x1)

print("Switching programs")
save = mem32[PIO0_BASE | 0xCC]
mem32[PIO0_BASE | 0xCC] = mem32[PIO0_BASE | 0xE4]

mem32[PIO0_BASE | 0xD8] = 0x18
for j in range(4):
    print(hex(mem32[PIO0_BASE | 0xD8]))
    advance(PIO0_BASE, 0x1)

print("Switching back")
mem32[PIO0_BASE | 0xCC] = save

mem32[PIO0_BASE | 0xD8] = 0x1C
for j in range(4):
    print(hex(mem32[PIO0_BASE | 0xD8]))
    advance(PIO0_BASE, 0x1)

Here we set up SM0, 1 to use the same programs, then copy the configuration regisster from 1 to 0 to switch, before using jmp 0x18 or jmp 0x1C to point the PC at the program we want. Printing the intructions being executed shows that the correct programs are being run in the right orders.