aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/pages/faq.adoc3
-rw-r--r--docs/pages/sharing_peripherals.adoc2
-rw-r--r--examples/rp/src/bin/sharing.rs150
3 files changed, 154 insertions, 1 deletions
diff --git a/docs/pages/faq.adoc b/docs/pages/faq.adoc
index a2f56a539..fc1316062 100644
--- a/docs/pages/faq.adoc
+++ b/docs/pages/faq.adoc
@@ -352,7 +352,8 @@ There are two main ways to handle concurrency in Embassy:
352 352
353In general, either of these approaches will work. The main differences of these approaches are: 353In general, either of these approaches will work. The main differences of these approaches are:
354 354
355When using **separate tasks**, each task needs its own RAM allocation, so there's a little overhead for each task, so one task that does three things will likely be a little bit smaller than three tasks that do one thing (not a lot, probably a couple dozen bytes). In contrast, with **multiple futures in one task**, you don't need multiple task allocations, and it will generally be easier to share data, or use borrowed resources, inside of a single task. 355When using **separate tasks**, each task needs its own RAM allocation, so there's a little overhead for each task, so one task that does three things will likely be a little bit smaller than three tasks that do one thing (not a lot, probably a couple dozen bytes). In contrast, with **multiple futures in one task**, you don't need multiple task allocations, and it will generally be easier to share data, or use borrowed resources, inside of a single task.
356An example showcasing some methods for sharing things between tasks link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/sharing.rs[can be found here].
356 357
357But when it comes to "waking" tasks, for example when a data transfer is complete or a button is pressed, it's faster to wake a dedicated task, because that task does not need to check which future is actually ready. `join` and `select` must check ALL of the futures they are managing to see which one (or which ones) are ready to do more work. This is because all Rust executors (like Embassy or Tokio) only have the ability to wake tasks, not specific futures. This means you will use slightly less CPU time juggling futures when using dedicated tasks. 358But when it comes to "waking" tasks, for example when a data transfer is complete or a button is pressed, it's faster to wake a dedicated task, because that task does not need to check which future is actually ready. `join` and `select` must check ALL of the futures they are managing to see which one (or which ones) are ready to do more work. This is because all Rust executors (like Embassy or Tokio) only have the ability to wake tasks, not specific futures. This means you will use slightly less CPU time juggling futures when using dedicated tasks.
358 359
diff --git a/docs/pages/sharing_peripherals.adoc b/docs/pages/sharing_peripherals.adoc
index 6bcd56b01..ebd899c4e 100644
--- a/docs/pages/sharing_peripherals.adoc
+++ b/docs/pages/sharing_peripherals.adoc
@@ -126,3 +126,5 @@ async fn toggle_led(control: Sender<'static, ThreadModeRawMutex, LedState, 64>,
126 126
127This example replaces the Mutex with a Channel, and uses another task (the main loop) to drive the LED. The advantage of this approach is that only a single task references the peripheral, separating concerns. However, using a Mutex has a lower overhead and might be necessary if you need to ensure 127This example replaces the Mutex with a Channel, and uses another task (the main loop) to drive the LED. The advantage of this approach is that only a single task references the peripheral, separating concerns. However, using a Mutex has a lower overhead and might be necessary if you need to ensure
128that the operation is completed before continuing to do other work in your task. 128that the operation is completed before continuing to do other work in your task.
129
130An example showcasing more methods for sharing link:https://github.com/embassy-rs/embassy/blob/main/examples/rp/src/bin/sharing.rs[can be found here]. \ No newline at end of file
diff --git a/examples/rp/src/bin/sharing.rs b/examples/rp/src/bin/sharing.rs
new file mode 100644
index 000000000..5416e20ce
--- /dev/null
+++ b/examples/rp/src/bin/sharing.rs
@@ -0,0 +1,150 @@
1//! This example shows some common strategies for sharing resources between tasks.
2//!
3//! We demonstrate five different ways of sharing, covering different use cases:
4//! - Atomics: This method is used for simple values, such as bool and u8..u32
5//! - Blocking Mutex: This is used for sharing non-async things, using Cell/RefCell for interior mutability.
6//! - Async Mutex: This is used for sharing async resources, where you need to hold the lock across await points.
7//! The async Mutex has interior mutability built-in, so no RefCell is needed.
8//! - Cell: For sharing Copy types between tasks running on the same executor.
9//! - RefCell: When you want &mut access to a value shared between tasks running on the same executor.
10//!
11//! More information: https://embassy.dev/book/#_sharing_peripherals_between_tasks
12
13#![no_std]
14#![no_main]
15
16use core::cell::{Cell, RefCell};
17use core::sync::atomic::{AtomicU32, Ordering};
18
19use cortex_m_rt::entry;
20use defmt::info;
21use embassy_executor::{Executor, InterruptExecutor};
22use embassy_rp::clocks::RoscRng;
23use embassy_rp::interrupt::{InterruptExt, Priority};
24use embassy_rp::peripherals::UART0;
25use embassy_rp::uart::{self, InterruptHandler, UartTx};
26use embassy_rp::{bind_interrupts, interrupt};
27use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
28use embassy_sync::{blocking_mutex, mutex};
29use embassy_time::{Duration, Ticker};
30use rand::RngCore;
31use static_cell::{ConstStaticCell, StaticCell};
32use {defmt_rtt as _, panic_probe as _};
33
34type UartAsyncMutex = mutex::Mutex<CriticalSectionRawMutex, UartTx<'static, UART0, uart::Async>>;
35
36struct MyType {
37 inner: u32,
38}
39
40static EXECUTOR_HI: InterruptExecutor = InterruptExecutor::new();
41static EXECUTOR_LOW: StaticCell<Executor> = StaticCell::new();
42
43// Use Atomics for simple values
44static ATOMIC: AtomicU32 = AtomicU32::new(0);
45
46// Use blocking Mutex with Cell/RefCell for sharing non-async things
47static MUTEX_BLOCKING: blocking_mutex::Mutex<CriticalSectionRawMutex, RefCell<MyType>> =
48 blocking_mutex::Mutex::new(RefCell::new(MyType { inner: 0 }));
49
50bind_interrupts!(struct Irqs {
51 UART0_IRQ => InterruptHandler<UART0>;
52});
53
54#[interrupt]
55unsafe fn SWI_IRQ_0() {
56 EXECUTOR_HI.on_interrupt()
57}
58
59#[entry]
60fn main() -> ! {
61 let p = embassy_rp::init(Default::default());
62 info!("Here we go!");
63
64 let uart = UartTx::new(p.UART0, p.PIN_0, p.DMA_CH0, uart::Config::default());
65 // Use the async Mutex for sharing async things (built-in interior mutability)
66 static UART: StaticCell<UartAsyncMutex> = StaticCell::new();
67 let uart = UART.init(mutex::Mutex::new(uart));
68
69 // High-priority executor: runs in interrupt mode
70 interrupt::SWI_IRQ_0.set_priority(Priority::P3);
71 let spawner = EXECUTOR_HI.start(interrupt::SWI_IRQ_0);
72 spawner.must_spawn(task_a(uart));
73
74 // Low priority executor: runs in thread mode
75 let executor = EXECUTOR_LOW.init(Executor::new());
76 executor.run(|spawner| {
77 // No Mutex needed when sharing between tasks running on the same executor
78
79 // Use Cell for Copy-types
80 static CELL: ConstStaticCell<Cell<[u8; 4]>> = ConstStaticCell::new(Cell::new([0; 4]));
81 let cell = CELL.take();
82
83 // Use RefCell for &mut access
84 static REF_CELL: ConstStaticCell<RefCell<MyType>> = ConstStaticCell::new(RefCell::new(MyType { inner: 0 }));
85 let ref_cell = REF_CELL.take();
86
87 spawner.must_spawn(task_b(uart, cell, ref_cell));
88 spawner.must_spawn(task_c(cell, ref_cell));
89 });
90}
91
92#[embassy_executor::task]
93async fn task_a(uart: &'static UartAsyncMutex) {
94 let mut ticker = Ticker::every(Duration::from_secs(1));
95 loop {
96 let random = RoscRng.next_u32();
97
98 {
99 let mut uart = uart.lock().await;
100 uart.write(b"task a").await.unwrap();
101 // The uart lock is released when it goes out of scope
102 }
103
104 ATOMIC.store(random, Ordering::Relaxed);
105
106 MUTEX_BLOCKING.lock(|x| x.borrow_mut().inner = random);
107
108 ticker.next().await;
109 }
110}
111
112#[embassy_executor::task]
113async fn task_b(uart: &'static UartAsyncMutex, cell: &'static Cell<[u8; 4]>, ref_cell: &'static RefCell<MyType>) {
114 let mut ticker = Ticker::every(Duration::from_secs(1));
115 loop {
116 let random = RoscRng.next_u32();
117
118 uart.lock().await.write(b"task b").await.unwrap();
119
120 cell.set(random.to_be_bytes());
121
122 ref_cell.borrow_mut().inner = random;
123
124 ticker.next().await;
125 }
126}
127
128#[embassy_executor::task]
129async fn task_c(cell: &'static Cell<[u8; 4]>, ref_cell: &'static RefCell<MyType>) {
130 let mut ticker = Ticker::every(Duration::from_secs(1));
131 loop {
132 info!("=======================");
133
134 let atomic_val = ATOMIC.load(Ordering::Relaxed);
135 info!("atomic: {}", atomic_val);
136
137 MUTEX_BLOCKING.lock(|x| {
138 let val = x.borrow().inner;
139 info!("blocking mutex: {}", val);
140 });
141
142 let cell_val = cell.get();
143 info!("cell: {:?}", cell_val);
144
145 let ref_cell_val = ref_cell.borrow().inner;
146 info!("ref_cell: {:?}", ref_cell_val);
147
148 ticker.next().await;
149 }
150}