Skip to content

Commit 9736fcd

Browse files
Example using a PIO interrupt (#384)
* Add example of using a PIO interrupt Add PIO UART receive example which uses interrupts.
1 parent 36949cb commit 9736fcd

File tree

2 files changed

+194
-0
lines changed

2 files changed

+194
-0
lines changed

pio/uart_rx/CMakeLists.txt

+13
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,16 @@ pico_add_extra_outputs(pio_uart_rx)
1414

1515
# add url via pico_set_program_url
1616
example_auto_set_url(pio_uart_rx)
17+
18+
# Similar to above but uses an interrupt for RX
19+
add_executable(pio_uart_rx_intr)
20+
pico_generate_pio_header(pio_uart_rx_intr ${CMAKE_CURRENT_LIST_DIR}/uart_rx.pio)
21+
target_sources(pio_uart_rx_intr PRIVATE uart_rx_intr.c)
22+
target_link_libraries(pio_uart_rx_intr PRIVATE
23+
pico_stdlib
24+
pico_multicore
25+
hardware_pio
26+
pico_async_context_threadsafe_background
27+
)
28+
pico_add_extra_outputs(pio_uart_rx_intr)
29+
example_auto_set_url(pio_uart_rx_intr)

pio/uart_rx/uart_rx_intr.c

+181
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
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

Comments
 (0)