diff --git a/README.md b/README.md index c74a4c266..6916d99d6 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/pio/CMakeLists.txt b/pio/CMakeLists.txt index 0b9d0b3ac..8684fc519 100644 --- a/pio/CMakeLists.txt +++ b/pio/CMakeLists.txt @@ -18,4 +18,5 @@ if (NOT PICO_NO_HARDWARE) add_subdirectory(uart_rx) add_subdirectory(uart_tx) add_subdirectory(ws2812) + add_subdirectory(irq) endif () diff --git a/pio/irq/CMakeLists.txt b/pio/irq/CMakeLists.txt new file mode 100644 index 000000000..c16185ee1 --- /dev/null +++ b/pio/irq/CMakeLists.txt @@ -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) diff --git a/pio/irq/main.c b/pio/irq/main.c new file mode 100644 index 000000000..341c6862a --- /dev/null +++ b/pio/irq/main.c @@ -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 +#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; + // 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); + + // 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); + 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 + // 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; +} + +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); + } +} + diff --git a/pio/irq/simple_irq.pio b/pio/irq/simple_irq.pio new file mode 100644 index 000000000..934ef2eb6 --- /dev/null +++ b/pio/irq/simple_irq.pio @@ -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 + * @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); + } +%}