|
| 1 | +/** |
| 2 | + * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: BSD-3-Clause |
| 5 | + */ |
| 6 | + |
| 7 | +#include <stdio.h> |
| 8 | +#include <stdlib.h> |
| 9 | + |
| 10 | +#include "pico/stdlib.h" |
| 11 | +#include "pico/multicore.h" |
| 12 | +#include "pico/util/queue.h" |
| 13 | +#include "pico/async_context_threadsafe_background.h" |
| 14 | + |
| 15 | +#include "hardware/pio.h" |
| 16 | +#include "hardware/uart.h" |
| 17 | +#include "uart_rx.pio.h" |
| 18 | + |
| 19 | +// This program |
| 20 | +// - Uses UART1 (the spare UART, by default) to transmit some text |
| 21 | +// - Uses a PIO state machine to receive that text |
| 22 | +// - Use an interrupt to determine when the PIO FIFO has some data |
| 23 | +// - Saves characters in a queue |
| 24 | +// - Uses an async context to perform work when notified by the irq |
| 25 | +// - Prints out the received text to the default console (UART0) |
| 26 | +// This might require some reconfiguration on boards where UART1 is the |
| 27 | +// default UART. |
| 28 | + |
| 29 | +#define SERIAL_BAUD PICO_DEFAULT_UART_BAUD_RATE |
| 30 | +#define HARD_UART_INST uart1 |
| 31 | + |
| 32 | +// You'll need a wire from GPIO4 -> GPIO3 |
| 33 | +#define HARD_UART_TX_PIN 4 |
| 34 | +#define PIO_RX_PIN 3 |
| 35 | +#define FIFO_SIZE 64 |
| 36 | +#define MAX_COUNTER 10 |
| 37 | + |
| 38 | +static PIO pio; |
| 39 | +static uint sm; |
| 40 | +static int8_t pio_irq; |
| 41 | +static queue_t fifo; |
| 42 | +static uint offset; |
| 43 | +static uint32_t counter; |
| 44 | +static bool work_done; |
| 45 | + |
| 46 | +// Ask core 1 to print a string, to make things easier on core 0 |
| 47 | +static void core1_main() { |
| 48 | + while(counter < MAX_COUNTER) { |
| 49 | + sleep_ms(1000 + (rand() % 1000)); |
| 50 | + static char text[64]; |
| 51 | + sprintf(text, "Hello, world from PIO with interrupts! %u\n", counter++); |
| 52 | + uart_puts(HARD_UART_INST, text); |
| 53 | + } |
| 54 | +} |
| 55 | + |
| 56 | +static void async_worker_func(async_context_t *async_context, async_when_pending_worker_t *worker); |
| 57 | + |
| 58 | +// An async context is notified by the irq to "do some work" |
| 59 | +static async_context_threadsafe_background_t async_context; |
| 60 | +static async_when_pending_worker_t worker = { .do_work = async_worker_func }; |
| 61 | + |
| 62 | +// IRQ called when the pio fifo is not empty, i.e. there are some characters on the uart |
| 63 | +// This needs to run as quickly as possible or else you will lose characters (in particular don't printf!) |
| 64 | +static void pio_irq_func(void) { |
| 65 | + while(!pio_sm_is_rx_fifo_empty(pio, sm)) { |
| 66 | + char c = uart_rx_program_getc(pio, sm); |
| 67 | + if (!queue_try_add(&fifo, &c)) { |
| 68 | + panic("fifo full"); |
| 69 | + } |
| 70 | + } |
| 71 | + // Tell the async worker that there are some characters waiting for us |
| 72 | + async_context_set_work_pending(&async_context.core, &worker); |
| 73 | +} |
| 74 | + |
| 75 | +// Process characters |
| 76 | +static void async_worker_func(async_context_t *async_context, async_when_pending_worker_t *worker) { |
| 77 | + work_done = true; |
| 78 | + while(!queue_is_empty(&fifo)) { |
| 79 | + char c; |
| 80 | + if (!queue_try_remove(&fifo, &c)) { |
| 81 | + panic("fifo empty"); |
| 82 | + } |
| 83 | + putchar(c); // Display character in the console |
| 84 | + } |
| 85 | +} |
| 86 | + |
| 87 | +// Find a free pio and state machine and load the program into it. |
| 88 | +// Returns false if this fails |
| 89 | +static bool init_pio(const pio_program_t *program, PIO *pio_hw, uint *sm, uint *offset) { |
| 90 | + // Find a free pio |
| 91 | + *pio_hw = pio1; |
| 92 | + if (!pio_can_add_program(*pio_hw, program)) { |
| 93 | + *pio_hw = pio0; |
| 94 | + if (!pio_can_add_program(*pio_hw, program)) { |
| 95 | + *offset = -1; |
| 96 | + return false; |
| 97 | + } |
| 98 | + } |
| 99 | + *offset = pio_add_program(*pio_hw, program); |
| 100 | + // Find a state machine |
| 101 | + *sm = (int8_t)pio_claim_unused_sm(*pio_hw, false); |
| 102 | + if (*sm < 0) { |
| 103 | + return false; |
| 104 | + } |
| 105 | + return true; |
| 106 | +} |
| 107 | + |
| 108 | +int main() { |
| 109 | + // Console output (also a UART, yes it's confusing) |
| 110 | + setup_default_uart(); |
| 111 | + printf("Starting PIO UART RX interrupt example\n"); |
| 112 | + |
| 113 | + // Set up the hard UART we're going to use to print characters |
| 114 | + uart_init(HARD_UART_INST, SERIAL_BAUD); |
| 115 | + gpio_set_function(HARD_UART_TX_PIN, GPIO_FUNC_UART); |
| 116 | + |
| 117 | + // create a queue so the irq can save the data somewhere |
| 118 | + queue_init(&fifo, 1, FIFO_SIZE); |
| 119 | + |
| 120 | + // Setup an async context and worker to perform work when needed |
| 121 | + if (!async_context_threadsafe_background_init_with_defaults(&async_context)) { |
| 122 | + panic("failed to setup context"); |
| 123 | + } |
| 124 | + async_context_add_when_pending_worker(&async_context.core, &worker); |
| 125 | + |
| 126 | + // Set up the state machine we're going to use to receive them. |
| 127 | + // In real code you need to find a free pio and state machine in case pio resources are used elsewhere |
| 128 | + if (!init_pio(&uart_rx_program, &pio, &sm, &offset)) { |
| 129 | + panic("failed to setup pio"); |
| 130 | + } |
| 131 | + uart_rx_program_init(pio, sm, offset, PIO_RX_PIN, SERIAL_BAUD); |
| 132 | + |
| 133 | + // Find a free irq |
| 134 | + static_assert(PIO0_IRQ_1 == PIO0_IRQ_0 + 1 && PIO1_IRQ_1 == PIO1_IRQ_0 + 1, ""); |
| 135 | + pio_irq = (pio == pio0) ? PIO0_IRQ_0 : PIO1_IRQ_0; |
| 136 | + if (irq_get_exclusive_handler(pio_irq)) { |
| 137 | + pio_irq++; |
| 138 | + if (irq_get_exclusive_handler(pio_irq)) { |
| 139 | + panic("All IRQs are in use"); |
| 140 | + } |
| 141 | + } |
| 142 | + |
| 143 | + // Enable interrupt |
| 144 | + irq_add_shared_handler(pio_irq, pio_irq_func, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); // Add a shared IRQ handler |
| 145 | + irq_set_enabled(pio_irq, true); // Enable the IRQ |
| 146 | + const uint irq_index = pio_irq - ((pio == pio0) ? PIO0_IRQ_0 : PIO1_IRQ_0); // Get index of the IRQ |
| 147 | + 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 |
| 148 | + |
| 149 | + // Tell core 1 to print text to uart1 |
| 150 | + multicore_launch_core1(core1_main); |
| 151 | + |
| 152 | + // Echo characters received from PIO to the console |
| 153 | + while (counter < MAX_COUNTER || work_done) { |
| 154 | + // Note that we could just sleep here as we're using "threadsafe_background" that uses a low priority interrupt |
| 155 | + // But if we changed to use a "polling" context that wouldn't work. The following works for both types of context. |
| 156 | + // When using "threadsafe_background" the poll does nothing. This loop is just preventing main from exiting! |
| 157 | + work_done = false; |
| 158 | + async_context_poll(&async_context.core); |
| 159 | + async_context_wait_for_work_ms(&async_context.core, 2000); |
| 160 | + } |
| 161 | + |
| 162 | + // Disable interrupt |
| 163 | + pio_set_irqn_source_enabled(pio, irq_index, pis_sm0_rx_fifo_not_empty + sm, false); |
| 164 | + irq_set_enabled(pio_irq, false); |
| 165 | + irq_remove_handler(pio_irq, pio_irq_func); |
| 166 | + |
| 167 | + // Cleanup pio |
| 168 | + pio_sm_set_enabled(pio, sm, false); |
| 169 | + pio_remove_program(pio, &uart_rx_program, offset); |
| 170 | + pio_sm_unclaim(pio, sm); |
| 171 | + |
| 172 | + async_context_remove_when_pending_worker(&async_context.core, &worker); |
| 173 | + async_context_deinit(&async_context.core); |
| 174 | + queue_free(&fifo); |
| 175 | + |
| 176 | + uart_deinit(HARD_UART_INST); |
| 177 | + |
| 178 | + printf("Test complete\n"); |
| 179 | + sleep_ms(100); |
| 180 | + return 0; |
| 181 | +} |
0 commit comments