2024-08-11

PIO and Interrupts

Introduction

I want to be able to raise system level interrupts from PIO programs and there aren’t too many examples of this, so here is my simple contribution.

PIO program

First, the PIO program, which simply waits for a pin to change and then immediately raises PIO IRQ 0.

.program irq_example
.wrap_target

wait 0 pin 0	; wait for a 0 on IN pin 0
irq nowait 0	; raise PIO IRQ 0

.wrap

Compile this with pioasm then #include the .h in your C program.

C Program

Interrupt Setup

There are four system level interrupts related to PIO: PIO0_IRQ_0, PIO0_IRQ_1, PIO1_IRQ_0, PIO1_IRQ_1. We shall use PIO0_IRQ_0.

First we attach an interrupt handler to PIO0_IRQ_0:

void isr(void) {
	// clear PIO IRQ 0 that was raised by irq nowait 0
	pio_interrupt_clear(pio0, 0);
}

irq_add_shared_handler(PIO0_IRQ_0,
                       isr,
                       PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);

It is important that pio_interrupt_clear() is called otherwise the CPU will lock up constantly executing the interrupt handler (aka ISR - interrupt service routine)

We then enable the interrupt:

irq_set_enabled(PIO0_IRQ_0, true);

Finally we configure PIO0_IRQ_0 to be triggered when PIO IRQ 0 is raised:

pio_set_irq0_source_enabled(pio0, pis_interrupt0, true);

The functions used above are described in the Pico C SDK documentation.

PIO Setup

The PIO program can now by installed the usual way by first setting up the state machine:

uint sm = pio_claim_unused_sm(pio0, true);
uint offset = pio_add_program(pio0, &irq_example_program);
pio_sm_config cfg = irq_example_program_get_default_config(offset);

Configure and connect GPIO to the state machine:

// connect GP 2 to IN of state machine
sm_config_set_in_pins(&cfg, 2);

// it is an input pin
pio_sm_set_consecutive_pindirs(pio0, sm, 2, 1, false);

// we want pull up on the pin
gpio_pull_up(2);

Now we are ready to start the state machine:

pio_sm_init(pio0, sm, offset, &cfg);
pio_sm_set_enabled(pio0, sm, true);

PIO ASM and PlatformIO

Introduction

PIO

The RP2040 has two programmable IO (PIO) blocks, each capable of executing 4 programs in parallel. This allows the RP2040 great flexibility in mix and matching different protocols. Want 3x CANBUS and 1xSPI? RP2040 got you. Want 5xI2C instead? That’s cool too. It is a very flexible approach vs having hardwired peripherals and since PIO execute independently of the CPU, nicer and faster than using the CPU to do bitbang.

PIOs are essentially hyperspecialised CPUs and they have their own assembly language which is parsed by a program called pioasm, part of the pico-sdk. pioasm converts PIO programs in assembly into C-headers which can then be #included and used. Typically you have your PIO programs in a .pioasm file which is then transpiled into a .h file by pioasm.

PlatformIO

PlatformIO is a very nice platform for doing embedded development. It nicely packages up all the toolchains and dependencies for you and sets everything up all neat and tidy.

PlatformIO and pioasm

While PlatformIO knows how to compile C code, it doesn’t natively support pioasm, which means you need to remember to manually invoke pioasm before building with PlatformIO, or configure PlatformIO to run a pre-build step that runs pioasm against all .pioasm.

Alternatively, one can use make which I have opted to do since it feels natural - after all make is designed for this kind of work.

With the following Makefile, all .pioasm files are compiled into their .h counter-part prior to building the program. The Makefile wraps common PlatformIO tasks to provide additional convenience.

PIOASM=/path/to/pioasm

%.h: src/%.pioasm
        $(PIOASM) $< src/$@

build: pio_trigger.h
        pio run

upload: build
        pio run -t upload

This script assumes your source code is in /src and you have a copy of pioasm.

Building pioasm

Because I use the community developed arduino-pico core, a copy of pico-sdk was already available in $HOME/.platformio/packages/framework-arduinopico/pico-sdk/, including the pioasm source code. pioasm can then be built as follows (assuming you are on a unix-like):

cd $HOME/.platformio/packages/framework-arduinopico/pico-sdk/tools/pioasm/
mkdir build
cd build
cmake ..
make

This will produce the pioasm binary in the build/ directory. You can leave it there or move it.