Example using a PIO interrupt (#384)
* Add example of using a PIO interrupt Add PIO UART receive example which uses interrupts.
This commit is contained in:
@@ -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)
|
||||
|
||||
181
pio/uart_rx/uart_rx_intr.c
Normal file
181
pio/uart_rx/uart_rx_intr.c
Normal file
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user