aboutsummaryrefslogtreecommitdiff
path: root/docs/pages/sharing_peripherals.adoc
diff options
context:
space:
mode:
authorUlf Lilleengen <[email protected]>2024-05-18 10:17:03 +0200
committerUlf Lilleengen <[email protected]>2024-05-21 10:05:21 +0200
commit739e5861c2e47db251725163fcd91cd822cf97b7 (patch)
tree947dc7961ca7c42ec216056fc2adf616ab812b10 /docs/pages/sharing_peripherals.adoc
parent51d553092550059afb22b2620cea14bbed21abff (diff)
convert from antora to asciidoctor
Diffstat (limited to 'docs/pages/sharing_peripherals.adoc')
-rw-r--r--docs/pages/sharing_peripherals.adoc128
1 files changed, 128 insertions, 0 deletions
diff --git a/docs/pages/sharing_peripherals.adoc b/docs/pages/sharing_peripherals.adoc
new file mode 100644
index 000000000..6bcd56b01
--- /dev/null
+++ b/docs/pages/sharing_peripherals.adoc
@@ -0,0 +1,128 @@
1= Sharing peripherals between tasks
2
3Often times, more than one task needs access to the same resource (pin, communication interface, etc.). Embassy provides many different synchronization primitives in the link:https://crates.io/crates/embassy-sync[embassy-sync] crate.
4
5The following examples shows different ways to use the on-board LED on a Raspberry Pi Pico board by two tasks simultaneously.
6
7== Sharing using a Mutex
8
9Using mutual exclusion is the simplest way to share a peripheral.
10
11TIP: Dependencies needed to run this example link:/book/dev/basic_application.html#_the_cargo_toml[can be found here].
12[,rust]
13----
14use defmt::*;
15use embassy_executor::Spawner;
16use embassy_rp::gpio;
17use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
18use embassy_sync::mutex::Mutex;
19use embassy_time::{Duration, Ticker};
20use gpio::{AnyPin, Level, Output};
21use {defmt_rtt as _, panic_probe as _};
22
23type LedType = Mutex<ThreadModeRawMutex, Option<Output<'static, AnyPin>>>;
24static LED: LedType = Mutex::new(None);
25
26#[embassy_executor::main]
27async fn main(spawner: Spawner) {
28 let p = embassy_rp::init(Default::default());
29 // set the content of the global LED reference to the real LED pin
30 let led = Output::new(AnyPin::from(p.PIN_25), Level::High);
31 // inner scope is so that once the mutex is written to, the MutexGuard is dropped, thus the
32 // Mutex is released
33 {
34 *(LED.lock().await) = Some(led);
35 }
36 let dt = 100 * 1_000_000;
37 let k = 1.003;
38
39 unwrap!(spawner.spawn(toggle_led(&LED, Duration::from_nanos(dt))));
40 unwrap!(spawner.spawn(toggle_led(&LED, Duration::from_nanos((dt as f64 * k) as u64))));
41}
42
43// A pool size of 2 means you can spawn two instances of this task.
44#[embassy_executor::task(pool_size = 2)]
45async fn toggle_led(led: &'static LedType, delay: Duration) {
46 let mut ticker = Ticker::every(delay);
47 loop {
48 {
49 let mut led_unlocked = led.lock().await;
50 if let Some(pin_ref) = led_unlocked.as_mut() {
51 pin_ref.toggle();
52 }
53 }
54 ticker.next().await;
55 }
56}
57----
58
59The structure facilitating access to the resource is the defined `LedType`.
60
61=== Why so complicated
62
63Unwrapping the layers gives insight into why each one is needed.
64
65==== `Mutex<RawMutexType, T>`
66
67The mutex is there so if one task gets the resource first and begins modifying it, all other tasks wanting to write will have to wait (the `led.lock().await` will return immediately if no task has locked the mutex, and will block if it is accessed somewhere else).
68
69==== `Option<T>`
70
71The `LED` variable needs to be defined outside the main task as references accepted by tasks need to be `'static`. However, if it is outside the main task, it cannot be initialised to point to any pin, as the pins themselves are not initialised. Thus, it is set to `None`.
72
73==== `Output<AnyPin>`
74
75To indicate that the pin will be set to an Output. The `AnyPin` could have been `embassy_rp::peripherals::PIN_25`, however this option lets the `toggle_led` function be more generic.
76
77== Sharing using a Channel
78
79A channel is another way to ensure exclusive access to a resource. Using a channel is great in the cases where the access can happen at a later point in time, allowing you to enqueue operations and do other things.
80
81TIP: Dependencies needed to run this example link:/book/dev/basic_application.html#_the_cargo_toml[can be found here].
82[,rust]
83----
84use defmt::*;
85use embassy_executor::Spawner;
86use embassy_rp::gpio;
87use embassy_sync::blocking_mutex::raw::ThreadModeRawMutex;
88use embassy_sync::channel::{Channel, Sender};
89use embassy_time::{Duration, Ticker};
90use gpio::{AnyPin, Level, Output};
91use {defmt_rtt as _, panic_probe as _};
92
93enum LedState {
94 Toggle,
95}
96static CHANNEL: Channel<ThreadModeRawMutex, LedState, 64> = Channel::new();
97
98#[embassy_executor::main]
99async fn main(spawner: Spawner) {
100 let p = embassy_rp::init(Default::default());
101 let mut led = Output::new(AnyPin::from(p.PIN_25), Level::High);
102
103 let dt = 100 * 1_000_000;
104 let k = 1.003;
105
106 unwrap!(spawner.spawn(toggle_led(CHANNEL.sender(), Duration::from_nanos(dt))));
107 unwrap!(spawner.spawn(toggle_led(CHANNEL.sender(), Duration::from_nanos((dt as f64 * k) as u64))));
108
109 loop {
110 match CHANNEL.receive().await {
111 LedState::Toggle => led.toggle(),
112 }
113 }
114}
115
116// A pool size of 2 means you can spawn two instances of this task.
117#[embassy_executor::task(pool_size = 2)]
118async fn toggle_led(control: Sender<'static, ThreadModeRawMutex, LedState, 64>, delay: Duration) {
119 let mut ticker = Ticker::every(delay);
120 loop {
121 control.send(LedState::Toggle).await;
122 ticker.next().await;
123 }
124}
125----
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
128that the operation is completed before continuing to do other work in your task.