* Add example of using a PIO interrupt Add PIO UART receive example which uses interrupts.
182 lines
6.3 KiB
C
182 lines
6.3 KiB
C
/**
|
|
* 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;
|
|
}
|