There are decent examples available on how to handle PIO interrupts in C. But not so much on dealing with relative IRQs.
With a normal IRQ, PIO fires an interrupt with a fixed number. The number that you give it.
A relative IRQ - in the context of this post - is an IRQ raised by a PIO, that has state machine ID encoded. It's not just "this interrupt from that state machine". The interrupt number and state machine ID are combined. You have to use that combination, in different ways, in your C code.
A relative IRQ adds the state machine ID and the two lowest bits of the IRQ number, and does a MOD 4 on that result. So it only affects those last two bits. I ran all combinations (state machine 0 - 3 and interrupt 0 -3) and collected two pieces of info:
- what interrupt do I have to register / enable / clear in C
- how do I know what state machine trew the interrupt, based on its state
Each PIO has 4 state machines. In this post I consequently call them 0 to 3. And C can capture the first 4 interrupts (on the Pico 2: all 8). I call those 0 to 3 too.
This is the table that reflects each scenario. Blue is the value I retrieved via the debugger. Green is me calculating those same attributes (and hopefully getting the same result). Black are the settings I used (state machine and interrupt
irq value in PIO | irq received | interrupt to manage | irq value to validate | ||
sm | irq | pio irq | ARM irq | MOD(sm+irq,4) | bitshift left |
0 | 0 | 1 | 0 | 0 | 1 |
0 | 1 | 2 | 1 | 1 | 2 |
0 | 2 | 4 | 2 | 2 | 4 |
0 | 3 | 8 | 3 | 3 | 8 |
1 | 0 | 2 | 1 | 1 | 2 |
1 | 1 | 4 | 2 | 2 | 4 |
1 | 2 | 8 | 3 | 3 | 8 |
1 | 3 | 1 | 0 | 0 | 1 |
2 | 0 | 4 | 2 | 2 | 4 |
2 | 1 | 8 | 3 | 3 | 8 |
2 | 2 | 1 | 0 | 0 | 1 |
2 | 3 | 2 | 1 | 1 | 2 |
3 | 0 | 8 | 3 | 3 | 8 |
3 | 1 | 1 | 0 | 0 | 1 |
3 | 2 | 2 | 1 | 1 | 2 |
3 | 3 | 4 | 2 | 2 | 4 |
This is the binary representation - easier to understand what's happening with those 2 last bits:
bin | |||||
sm | irq | pio irq | int | mod | shift left |
0000 | 0000 | 0001 | 0000 | 0000 | 0001 |
0000 | 0001 | 0010 | 0001 | 0001 | 0010 |
0000 | 0010 | 0100 | 0010 | 0010 | 0100 |
0000 | 0011 | 1000 | 0011 | 0011 | 1000 |
0001 | 0000 | 0010 | 0001 | 0001 | 0010 |
0001 | 0001 | 0100 | 0010 | 0010 | 0100 |
0001 | 0010 | 1000 | 0011 | 0011 | 1000 |
0001 | 0011 | 0001 | 0000 | 0000 | 0001 |
0010 | 0000 | 0100 | 0010 | 0010 | 0100 |
0010 | 0001 | 1000 | 0011 | 0011 | 1000 |
0010 | 0010 | 0001 | 0000 | 0000 | 0001 |
0010 | 0011 | 0010 | 0001 | 0001 | 0010 |
0011 | 0000 | 1000 | 0011 | 0011 | 1000 |
0011 | 0001 | 0001 | 0000 | 0000 | 0001 |
0011 | 0010 | 0010 | 0001 | 0001 | 0010 |
0011 | 0011 | 0100 | 0010 | 0010 | 0100 |
What I learned from this:
- to tell in C what interrupt to enable, catch and clear, I have to calculate the sum of the two LSB of the IRQ, + the state machine ID, mod 4: Let's call that the relative_irq
uint relative_irq = IRQ_NUMBER & 0x03; // two last bits relative_irq += sm; // add state machine ID relative_irq = relative_irq % 4; // modulo 4, retaining only the last 2 bits again
I created a function to calculate that value:
// 10 (REL): the state machine ID (0…3) is added to the IRQ flag index, by way of // modulo-4 addition on the two LSBs inline uint relative_interrupt(const uint32_t ir, const uint sm) { uint32_t retval = ir & 0x03; // last 2 bits retval += sm; // add relative value (is sm) retval = retval % 4; // mod 4 return retval; }
this function hasn't been tested for interrupts 4 .. 7, that can be propagated on a Pico 2. Send me one...
- to see which of the 4 state machine actually threw the interrupt, we have to interpret the value we get in the pio_hw_t.irq. structure. This variable has always 1 bit set when we get the interrupt in C. The position of that bit reflects the state machine ID.
We can check if the interrupt is coming from the right state machine:
assert(piostep->irq == 1 << relative_interrupt(stepper_PIO_IRQ_DONE, sm));
or we can retrieve what state machine threw the interrupt by using this code:
uint sm_from_interrupt(const uint32_t irq_val, const uint32_t ir) { uint i; for (i = 0; i < 4; i++) { if (irq_val == 1 << relative_interrupt(ir, i)) { break; } } return i; }
this function hasn't been tested for interrupts 4 .. 7, that can be propagated on a Pico 2.
Let's take a pause now . At this moment, we know all the numbers needed. let's do something easy
How to generate a relative IRQ in PIO
To make PIO generate relative interrupts, you add "rel" to IRQ command:
How to enable PIO to throw the relative IRQ
inline pio_interrupt_source interrupt_source(const pio_interrupt_source is, const uint32_t ir) {
return static_cast<pio_interrupt_source>(std::to_underlying(is) + ir);
}
pio_set_irq0_source_enabled(
pio1, interrupt_source(pis_interrupt0,
relative_interrupt(stepper_PIO_IRQ_DONE, sm)), true);
How to register and enable the request handler correctly for the relative IRQ
This is easier. We just have to know which PIO we use, what interrupt channel we use, and what the handler name is
irq_set_exclusive_handler(PIO1_IRQ_0, pio_irq_handler);
//Set the handler in the NVIC
The first parameter is dependent on what PIO you use, and what interrupt channel we'll use. In my code, I use PIO1 and IRQ_0. We 'll see the content of parameter 2, the handler function, in a bit.
Then enable this interrupt on ARM:
Find (or validate) the state machine in the interrupt handler
Last step: if we want to react on the interrupt, we need a handler. This handler will be called when interrupts arrive from any state machine of the PIO. Inside the handler, we have ability to either (or both):
- check if the interrupt came from a particular state machine. This works well if you know what SM you want to react to. Let's say you have one stepper motor, and you know it runs on state machine 0 of pio1. And the interrupt ID is 0
assert(pio1->irq == 1 << relative_interrupt(0, 0)); - find out what state machine caused the interrupt. Let's say you have a pio that runs 4 stepper motors independently, each controlled by its own state machine.
sm_from_interrupt(pio1->irq, 0));
You have to clear the interrupt in the handler:
pio_interrupt_clear(pio1, relative_interrupt(0, 0));
An example function, that increments a count, each time it receives an interrupt 0 from state machine 0:
volitile uint commands_completed = 0U; void pio_irq_handler(void){ uint ir = relative_interrupt(0, 0); assert(piostep->irq == 1 << ir); // develop check: interrupt is from the correct state machine pio_interrupt_clear(pio1, ir); commands_completed = commands_completed + 1; }
Next post C++: Handle Raspberry Pico PIO "relative interrupts" in C++ .
For a project that uses this, check Stepper Motor Control with Raspberry Pico PIO and DRV8711 driver- Part 7: autonomous PIO with interrupt when done .
Thank you for reading.