Skip to content

Add isr example with the pio #259

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ App|Description
[uart_tx](pio/uart_tx)| Implement the transmit component of a UART serial port, and print hello world.
[ws2812](pio/ws2812)| Examples of driving WS2812 addressable RGB LEDs.
[addition](pio/addition)| Add two integers together using PIO. Only around 8 billion times slower than Cortex-M0+.
[irq](pio/irq)| Respond to interrupts generated by a PIO state machine.

### PWM

Expand Down
1 change: 1 addition & 0 deletions pio/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,5 @@ if (NOT PICO_NO_HARDWARE)
add_subdirectory(uart_rx)
add_subdirectory(uart_tx)
add_subdirectory(ws2812)
add_subdirectory(irq)
endif ()
18 changes: 18 additions & 0 deletions pio/irq/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
add_executable(pio_irq)

pico_generate_pio_header(pio_irq ${CMAKE_CURRENT_LIST_DIR}/simple_irq.pio)

target_sources(pio_irq PRIVATE main.c)

pico_enable_stdio_usb(pio_irq 1)
pico_enable_stdio_uart(pio_irq 0)

target_link_libraries(pio_irq PRIVATE
pico_stdlib
hardware_pio
)

pico_add_extra_outputs(pio_irq)

# add url via pico_set_program_url
example_auto_set_url(pio_irq)
174 changes: 174 additions & 0 deletions pio/irq/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
/**
* Copyright 2022 (c) Daniel Garcia-Briseno
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
* following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
* disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <stdio.h>
#include "pico/stdlib.h"

#include "simple_irq.pio.h"

/**
* These defines represent each state machine.
* I'll use these to flag which state machine fired the ISR
*/
#define PIO_SM_0_IRQ 0b0001
#define PIO_SM_1_IRQ 0b0010
#define PIO_SM_2_IRQ 0b0100
#define PIO_SM_3_IRQ 0b1000


/**
* This variable will shadow the IRQ flags set by the PIO state machines.
* Typically you do not want to do work in ISRs because the main thread
* has more important things to do. Because of that, when we get the ISR
* I'm going to copy the state machine that fired the ISR into this
* variable.
*
* Variable is volatile so that it doesn't get cached in a CPU register
* in the main thread. Without this it's possible that you never see
* irq_flags get set even though the ISR is firing all the time.
*
* Of course, you can really do whatever you want in the ISR, it's up to you.
*/
volatile uint8_t irq_flags = 0;

/**
* This function is called when the IRQ is fired by the state machine.
* @note See enable_pio_irqs for how to register this function to be called
*/
void simple_irq_handler() {
// Check the interrupt source and set the flag accordingly
// This method can handle multiple simultaneous interrupts from the PIO
// since it checks all the irq flags. The advantage to this is that the
// ISR would execute only once when simultaneous interrupts happen instead
// of being executed for each interrupt individually.
// For other applications, make sure to check the correct pio (pio0/pio1)
irq_flags = pio0_hw->irq;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps some compilers will need an explicit cast here, to avoid warnings? 🤷 (alternatively, I guess you could make irq_flags a uint32_t again)

// Clear the flags since we've saved them to check later.
hw_clear_bits(&pio0_hw->irq, irq_flags);

// alternatively you could use pio_interrupt_get(pio0, #) to check a specific
// irq flag.
}

/**
* Lets the pico know that we want it to notify us of the PIO ISRs.
* @note in simple_irq.pio we enable irq0. This tells the state machine
* to send the ISRs to the core, we still need to tell the core
* to send them to our program.
*/
void enable_pio_irqs() {
// Set the function that will be called when the PIO IRQ comes in.
irq_set_exclusive_handler(PIO0_IRQ_0, simple_irq_handler);
Copy link
Contributor

@henrygab henrygab Oct 16, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be:

irq_add_shared_handler(PIO0_IRQ_0, simple_irq_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);

After all, each state machine may have its own handler attached, as they could each run entirely distinct programs.


// Once that function is set, we can go ahead and allow the interrupts
// to come in. You want to set the function before enabling the interrupt
// just in case. The docs say if an IRQ comes in and there's no handler
// then it will work like a breakpoint, which seems bad.
irq_set_enabled(PIO0_IRQ_0, true);
}

/**
* Loads simple_irq pio program into PIO memory
*/
void load_pio_programs() {
PIO pio = pio0;

// Load the program into PIO memory
uint offset = pio_add_program(pio, &simple_irq_program);

// Tell each state machine to run the program.
// They are allowed to run the same program in memory.
simple_irq_program_init(pio, 0, offset);
simple_irq_program_init(pio, 1, offset);
simple_irq_program_init(pio, 2, offset);
simple_irq_program_init(pio, 3, offset);
}

/**
* Writes to the tx fifo of the given state machine.
* This will make the simple_irq program send an ISR to us!
*/
void trigger_irq(int sm) {
printf("Triggering ISR from state machine %d\n", sm);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ISR -> IRQ here? There's also a few comments that still say "ISR", where perhaps "IRQ" would be more appropriate? (although there are some comments where it might be appropriate to stick to "ISR"?)

In my understanding (which may still be wrong, as I've not done much low-level coding), IRQ = Interrupt ReQuest, i.e. the actual hardware signal, and ISR = Interrupt Service Routine i.e. the callback function you've registered to handle the IRQ.

pio_sm_put_blocking(pio0, sm, 1);
// Interrupt will fire from the pio right here since they trigger
// whenever data goes into the TX FIFO, see simple_irq.pio

// Print the irq we expect based on the given state machine
printf("Expected IRQ flags: 0x%08X\n", (1 << sm));
printf("Actual IRQ Flags: 0x%08X\n", irq_flags);

// Here you could dispatch work for the irq depending one
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"depending one" ? Looks like you might have missed some words out?

// flagged it. The work should be done here rather than inside
// the irq function. While executing code in an irq, no other
// irq's can execute. By having the processing code outside of
// the irq, we can still be responsive to new irqs.
if (irq_flags & PIO_SM_0_IRQ) {
// handle_sm0_irq();
}
if (irq_flags & PIO_SM_1_IRQ) {
// handle_sm1_irq();
}
if (irq_flags & PIO_SM_2_IRQ) {
// handle_sm2_irq();
}
if (irq_flags & PIO_SM_3_IRQ) {
// handle_sm3_irq();
}

// clear irq flags now.
irq_flags = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than setting all irq_flags to 0 here at the end, I wonder if it might be more idiomatic to clear the flags individually e.g. by doing something like irq_flags &= !PIO_SM_1_IRQ inside the if (irq_flags & PIO_SM_1_IRQ) { block? Or perhaps that's unnecessary complication? 🤷

}

int main() {
// Init stdio
stdio_init_all();

// Load simple_irq into memory
load_pio_programs();

// Enable IRQs to respond to simple_irq
enable_pio_irqs();

// simple_irq is programmed to fire an ISR when we write
// to their tx fifo. So let's do that now.
while (true) {
// Fire state machine 0
trigger_irq(0);
sleep_ms(1000);

// Fire state machine 1
trigger_irq(1);
sleep_ms(1000);

// Fire state machine 2
trigger_irq(2);
sleep_ms(1000);

// Fire state machine 3
trigger_irq(3);
sleep_ms(1000);
}
}

72 changes: 72 additions & 0 deletions pio/irq/simple_irq.pio
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
; Copyright (c) 2022 Daniel Garcia-Briseno
;
; Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
; following conditions are met:
;
; 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
; disclaimer.
;
; 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
; disclaimer in the documentation and/or other materials provided with the distribution.
;
; 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
; derived from this software without specific prior written permission.
;
; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
; INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
; THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

.program simple_irq

; Stall on OSR. Wait for software to send a signal to continue
; by writing to the OSR
out x, 1

; Do work here
nop

; Notify the software via irq that some work has been done
; irq wait means to wait for software to acknowledge the irq before
; continuing. rel means let the irq be relative to the state machine.
; by using "0 rel" on all state machines, software will see a different
; interrupt source for each state machine. Technically state machines
; can set any irq source, so in order to know where the irq is coming from
; it's best to set 0 rel
irq wait 0 rel

% c-sdk {
/**
* Initializer for the fake irq program program
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"program program" ?

* @param[in] pio the PIO instance to use
* @param[in] sm state machine to use for the PIO instance
* @param[in] offset Offset into PIO memory to place the program into
*/
static inline void simple_irq_program_init(PIO pio, uint sm, uint offset) {
// Enable the IRQ source
// The reason for doing interrupt0 + sm:
// IRQ sources are enabled per irq flag. Since the irq flag being set depends on the state
// machine because of the "0 rel", we want to make sure we're enabling the correct interrupt
// source for the state machine the program is loaded into.
pio_set_irq0_source_enabled(pio, (pis_interrupt0 + sm), true);
// Make sure the interrupt starts cleared. It should already be cleared, so this should
// basically be a no-op. I call it defensive programming.
pio_interrupt_clear(pio, sm);

// Build the configuration for the state machine
pio_sm_config config = simple_irq_program_get_default_config(offset);

// Set up autopull to pull the TX Fifo into the OSR
// This is what actually makes the "out" instruction wait
// for input from software.
// params are (config, shift_right (ignored here), autopull (true), pull threshold (1 bit))
sm_config_set_out_shift(&config, true, true, 1);

// Load the config and execute the state machine
pio_sm_init(pio, sm, offset, &config);
pio_sm_set_enabled(pio, sm, true);
}
%}