From 9736fcd4afce60d50e1fb03d1abc17fa66b0ed24 Mon Sep 17 00:00:00 2001 From: Peter Harper <77111776+peterharperuk@users.noreply.github.com> Date: Mon, 5 Jun 2023 16:33:47 +0100 Subject: [PATCH] Example using a PIO interrupt (#384) * Add example of using a PIO interrupt Add PIO UART receive example which uses interrupts. --- pio/uart_rx/CMakeLists.txt | 13 +++ pio/uart_rx/uart_rx_intr.c | 181 +++++++++++++++++++++++++++++++++++++ 2 files changed, 194 insertions(+) create mode 100644 pio/uart_rx/uart_rx_intr.c diff --git a/pio/uart_rx/CMakeLists.txt b/pio/uart_rx/CMakeLists.txt index c2adc6e..5f985e1 100644 --- a/pio/uart_rx/CMakeLists.txt +++ b/pio/uart_rx/CMakeLists.txt @@ -14,3 +14,16 @@ pico_add_extra_outputs(pio_uart_rx) # add url via pico_set_program_url example_auto_set_url(pio_uart_rx) + +# Similar to above but uses an interrupt for RX +add_executable(pio_uart_rx_intr) +pico_generate_pio_header(pio_uart_rx_intr ${CMAKE_CURRENT_LIST_DIR}/uart_rx.pio) +target_sources(pio_uart_rx_intr PRIVATE uart_rx_intr.c) +target_link_libraries(pio_uart_rx_intr PRIVATE + pico_stdlib + pico_multicore + hardware_pio + pico_async_context_threadsafe_background + ) +pico_add_extra_outputs(pio_uart_rx_intr) +example_auto_set_url(pio_uart_rx_intr) diff --git a/pio/uart_rx/uart_rx_intr.c b/pio/uart_rx/uart_rx_intr.c new file mode 100644 index 0000000..0208d66 --- /dev/null +++ b/pio/uart_rx/uart_rx_intr.c @@ -0,0 +1,181 @@ +/** + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include +#include + +#include "pico/stdlib.h" +#include "pico/multicore.h" +#include "pico/util/queue.h" +#include "pico/async_context_threadsafe_background.h" + +#include "hardware/pio.h" +#include "hardware/uart.h" +#include "uart_rx.pio.h" + +// This program +// - Uses UART1 (the spare UART, by default) to transmit some text +// - Uses a PIO state machine to receive that text +// - Use an interrupt to determine when the PIO FIFO has some data +// - Saves characters in a queue +// - Uses an async context to perform work when notified by the irq +// - Prints out the received text to the default console (UART0) +// This might require some reconfiguration on boards where UART1 is the +// default UART. + +#define SERIAL_BAUD PICO_DEFAULT_UART_BAUD_RATE +#define HARD_UART_INST uart1 + +// You'll need a wire from GPIO4 -> GPIO3 +#define HARD_UART_TX_PIN 4 +#define PIO_RX_PIN 3 +#define FIFO_SIZE 64 +#define MAX_COUNTER 10 + +static PIO pio; +static uint sm; +static int8_t pio_irq; +static queue_t fifo; +static uint offset; +static uint32_t counter; +static bool work_done; + +// Ask core 1 to print a string, to make things easier on core 0 +static void core1_main() { + while(counter < MAX_COUNTER) { + sleep_ms(1000 + (rand() % 1000)); + static char text[64]; + sprintf(text, "Hello, world from PIO with interrupts! %u\n", counter++); + uart_puts(HARD_UART_INST, text); + } +} + +static void async_worker_func(async_context_t *async_context, async_when_pending_worker_t *worker); + +// An async context is notified by the irq to "do some work" +static async_context_threadsafe_background_t async_context; +static async_when_pending_worker_t worker = { .do_work = async_worker_func }; + +// IRQ called when the pio fifo is not empty, i.e. there are some characters on the uart +// This needs to run as quickly as possible or else you will lose characters (in particular don't printf!) +static void pio_irq_func(void) { + while(!pio_sm_is_rx_fifo_empty(pio, sm)) { + char c = uart_rx_program_getc(pio, sm); + if (!queue_try_add(&fifo, &c)) { + panic("fifo full"); + } + } + // Tell the async worker that there are some characters waiting for us + async_context_set_work_pending(&async_context.core, &worker); +} + +// Process characters +static void async_worker_func(async_context_t *async_context, async_when_pending_worker_t *worker) { + work_done = true; + while(!queue_is_empty(&fifo)) { + char c; + if (!queue_try_remove(&fifo, &c)) { + panic("fifo empty"); + } + putchar(c); // Display character in the console + } +} + +// Find a free pio and state machine and load the program into it. +// Returns false if this fails +static bool init_pio(const pio_program_t *program, PIO *pio_hw, uint *sm, uint *offset) { + // Find a free pio + *pio_hw = pio1; + if (!pio_can_add_program(*pio_hw, program)) { + *pio_hw = pio0; + if (!pio_can_add_program(*pio_hw, program)) { + *offset = -1; + return false; + } + } + *offset = pio_add_program(*pio_hw, program); + // Find a state machine + *sm = (int8_t)pio_claim_unused_sm(*pio_hw, false); + if (*sm < 0) { + return false; + } + return true; +} + +int main() { + // Console output (also a UART, yes it's confusing) + setup_default_uart(); + printf("Starting PIO UART RX interrupt example\n"); + + // Set up the hard UART we're going to use to print characters + uart_init(HARD_UART_INST, SERIAL_BAUD); + gpio_set_function(HARD_UART_TX_PIN, GPIO_FUNC_UART); + + // create a queue so the irq can save the data somewhere + queue_init(&fifo, 1, FIFO_SIZE); + + // Setup an async context and worker to perform work when needed + if (!async_context_threadsafe_background_init_with_defaults(&async_context)) { + panic("failed to setup context"); + } + async_context_add_when_pending_worker(&async_context.core, &worker); + + // Set up the state machine we're going to use to receive them. + // In real code you need to find a free pio and state machine in case pio resources are used elsewhere + if (!init_pio(&uart_rx_program, &pio, &sm, &offset)) { + panic("failed to setup pio"); + } + uart_rx_program_init(pio, sm, offset, PIO_RX_PIN, SERIAL_BAUD); + + // Find a free irq + static_assert(PIO0_IRQ_1 == PIO0_IRQ_0 + 1 && PIO1_IRQ_1 == PIO1_IRQ_0 + 1, ""); + pio_irq = (pio == pio0) ? PIO0_IRQ_0 : PIO1_IRQ_0; + if (irq_get_exclusive_handler(pio_irq)) { + pio_irq++; + if (irq_get_exclusive_handler(pio_irq)) { + panic("All IRQs are in use"); + } + } + + // Enable interrupt + irq_add_shared_handler(pio_irq, pio_irq_func, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); // Add a shared IRQ handler + irq_set_enabled(pio_irq, true); // Enable the IRQ + const uint irq_index = pio_irq - ((pio == pio0) ? PIO0_IRQ_0 : PIO1_IRQ_0); // Get index of the IRQ + pio_set_irqn_source_enabled(pio, irq_index, pis_sm0_rx_fifo_not_empty + sm, true); // Set pio to tell us when the FIFO is NOT empty + + // Tell core 1 to print text to uart1 + multicore_launch_core1(core1_main); + + // Echo characters received from PIO to the console + while (counter < MAX_COUNTER || work_done) { + // Note that we could just sleep here as we're using "threadsafe_background" that uses a low priority interrupt + // But if we changed to use a "polling" context that wouldn't work. The following works for both types of context. + // When using "threadsafe_background" the poll does nothing. This loop is just preventing main from exiting! + work_done = false; + async_context_poll(&async_context.core); + async_context_wait_for_work_ms(&async_context.core, 2000); + } + + // Disable interrupt + pio_set_irqn_source_enabled(pio, irq_index, pis_sm0_rx_fifo_not_empty + sm, false); + irq_set_enabled(pio_irq, false); + irq_remove_handler(pio_irq, pio_irq_func); + + // Cleanup pio + pio_sm_set_enabled(pio, sm, false); + pio_remove_program(pio, &uart_rx_program, offset); + pio_sm_unclaim(pio, sm); + + async_context_remove_when_pending_worker(&async_context.core, &worker); + async_context_deinit(&async_context.core); + queue_free(&fifo); + + uart_deinit(HARD_UART_INST); + + printf("Test complete\n"); + sleep_ms(100); + return 0; +}