aboutsummaryrefslogtreecommitdiff
path: root/examples/rp
diff options
context:
space:
mode:
authorrafael <[email protected]>2024-07-27 14:48:42 +0200
committerrafael <[email protected]>2024-07-27 14:48:42 +0200
commitb2d8d7f009877ad288a563643f3e983eaecafc58 (patch)
tree6c7bb36b0b4c1bd9ac48b8ef5e994a14d4645d7d /examples/rp
parentb88dc137e766d89eca5472bfa6f3bb78cfd1f7e0 (diff)
add example to rp: orchestrate multiple tasks
Diffstat (limited to 'examples/rp')
-rw-r--r--examples/rp/src/bin/orchestrate_tasks.rs267
1 files changed, 267 insertions, 0 deletions
diff --git a/examples/rp/src/bin/orchestrate_tasks.rs b/examples/rp/src/bin/orchestrate_tasks.rs
new file mode 100644
index 000000000..b9282e273
--- /dev/null
+++ b/examples/rp/src/bin/orchestrate_tasks.rs
@@ -0,0 +1,267 @@
1//! This example demonstrates some approaches to communicate between tasks in order to orchestrate the state of the system.
2//!
3//! We demonstrate how to:
4//! - use a channel to send messages between tasks, in this case here in order to have one task control the state of the system.
5//! - use a signal to terminate a task.
6//! - use command channels to send commands to another task.
7//! - use different ways to receive messages, from a straightforwar awaiting on one channel to a more complex awaiting on multiple channels.
8//!
9//! There are more patterns to orchestrate tasks, this is just one example.
10//!
11//! We will use these tasks to generate example "state information":
12//! - a task that generates random numbers in intervals of 60s
13//! - a task that generates random numbers in intervals of 30s
14//! - a task that generates random numbers in intervals of 90s
15//! - a task that notifies about being attached/disattached from usb power
16//! - a task that measures vsys voltage in intervals of 30s
17
18#![no_std]
19#![no_main]
20
21use assign_resources::assign_resources;
22use defmt::*;
23use embassy_executor::Spawner;
24use embassy_futures::select::{select, Either};
25use embassy_rp::adc::{Adc, Channel, Config, InterruptHandler};
26use embassy_rp::bind_interrupts;
27use embassy_rp::clocks::RoscRng;
28use embassy_rp::gpio::{Input, Pull};
29use embassy_rp::peripherals;
30use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
31use embassy_sync::{channel, signal};
32use embassy_time::{Duration, Timer};
33use rand::RngCore;
34use {defmt_rtt as _, panic_probe as _};
35
36// This is just some preparation, see example `assign_resources.rs` for more information on this. We prep the rresources that we will be using in different tasks.
37// **Note**: This will not work with a board that has a wifi chip, because the wifi chip uses pins 24 and 29 for its own purposes. A way around this in software
38// is not trivial, at least if you intend to use wifi, too. Workaround is to wire from vsys and vbus pins to appropriate pins on the board through a voltage divider. Then use those pins.
39// For this example it will not matter much, the concept of what we are showing remains valid.
40assign_resources! {
41 vsys: Vsys {
42 adc: ADC,
43 pin_29: PIN_29,
44 },
45 vbus: Vbus {
46 pin_24: PIN_24,
47 },
48}
49
50bind_interrupts!(struct Irqs {
51 ADC_IRQ_FIFO => InterruptHandler;
52});
53
54/// This is the type of Events that we will send from the worker tasks to the orchestrating task.
55enum Events {
56 UsbPowered(bool),
57 VsysVoltage(f32),
58 FirstRandomSeed(u32),
59 SecondRandomSeed(u32),
60 ThirdRandomSeed(u32),
61}
62
63/// This is the type of Commands that we will send from the orchestrating task to the worker tasks.
64/// Note that we are lazy here and only have one command, you might want to have more.
65enum Commands {
66 /// This command will stop the appropriate worker task
67 Stop,
68}
69
70/// This is the state of the system, we will use this to orchestrate the system. This is a simple example, in a real world application this would be more complex.
71#[derive(Default, Debug, Clone, Format)]
72struct State {
73 usb_powered: bool,
74 vsys_voltage: f32,
75 first_random_seed: u32,
76 second_random_seed: u32,
77 third_random_seed: u32,
78 times_we_got_first_random_seed: u8,
79 maximum_times_we_want_first_random_seed: u8,
80}
81
82impl State {
83 fn new() -> Self {
84 Self {
85 usb_powered: false,
86 vsys_voltage: 0.0,
87 first_random_seed: 0,
88 second_random_seed: 0,
89 third_random_seed: 0,
90 times_we_got_first_random_seed: 0,
91 maximum_times_we_want_first_random_seed: 3,
92 }
93 }
94}
95
96/// Channel for the events that we want the orchestrator to react to, all state events are of the type Enum Events.
97/// We use a channel with an arbitrary size of 10, the precise size of the queue depends on your use case. This depends on how many events we
98/// expect to be generated in a given time frame and how fast the orchestrator can react to them. And then if we rather want the senders to wait for
99/// new slots in the queue or if we want the orchestrator to have a backlog of events to process. In this case here we expect to always be enough slots
100/// in the queue, so the worker tasks can in all nominal cases send their events and continue with their work without waiting.
101/// For the events we - in this case here - do not want to loose any events, so a channel is a good choice. See embassy_sync docs for other options.
102static EVENT_CHANNEL: channel::Channel<CriticalSectionRawMutex, Events, 10> = channel::Channel::new();
103
104/// Signal for stopping the first random signal task. We use a signal here, because we need no queue. It is suffiient to have one signal active.
105static STOP_FIRST_RANDOM_SIGNAL: signal::Signal<CriticalSectionRawMutex, Commands> = signal::Signal::new();
106
107// And now we can put all this into use
108
109/// This is the main task, that will not do very much besides spawning the other tasks. This is a design choice, you could do the
110/// orchestrating here. This is to show that we do not need a main loop here, the system will run indefinitely as long as at least one task is running.
111#[embassy_executor::main]
112async fn main(spawner: Spawner) {
113 // initialize the peripherals
114 let p = embassy_rp::init(Default::default());
115 // split the resources, for convenience - see above
116 let r = split_resources! {p};
117
118 // spawn the tasks
119 spawner.spawn(orchestrate(spawner)).unwrap();
120 spawner.spawn(random_30s(spawner)).unwrap();
121 spawner.spawn(random_60s(spawner)).unwrap();
122 spawner.spawn(random_90s(spawner)).unwrap();
123 spawner.spawn(usb_power(spawner, r.vbus)).unwrap();
124 spawner.spawn(vsys_voltage(spawner, r.vsys)).unwrap();
125}
126
127/// This is the task handling the system state and orchestrating the other tasks. WEe can regard this as the "main loop" of the system.
128#[embassy_executor::task]
129async fn orchestrate(_spawner: Spawner) {
130 let mut state = State::new();
131
132 // we need to have a receiver for the events
133 let receiver = EVENT_CHANNEL.receiver();
134
135 loop {
136 // we await on the receiver, this will block until a new event is available
137 // as an alternative to this, we could also await on multiple channels, this would block until at least one of the channels has an event
138 // see the embassy_futures docs: https://docs.embassy.dev/embassy-futures/git/default/select/index.html
139 // The task random_30s does a select, if you want to have a look at that.
140 // Another reason to use select may also be that we want to have a timeout, so we can react to the absence of events within a time frame.
141 // We keep it simple here.
142 let event = receiver.receive().await;
143
144 // react to the events
145 match event {
146 Events::UsbPowered(usb_powered) => {
147 // update the state and/or react to the event here
148 state.usb_powered = usb_powered;
149 info!("Usb powered: {}", usb_powered);
150 }
151 Events::VsysVoltage(voltage) => {
152 // update the state and/or react to the event here
153 state.vsys_voltage = voltage;
154 info!("Vsys voltage: {}", voltage);
155 }
156 Events::FirstRandomSeed(seed) => {
157 // update the state and/or react to the event here
158 state.first_random_seed = seed;
159 // here we change some meta state, we count how many times we got the first random seed
160 state.times_we_got_first_random_seed += 1;
161 info!(
162 "First random seed: {}, and that was iteration {} of receiving this.",
163 seed, &state.times_we_got_first_random_seed
164 );
165 }
166 Events::SecondRandomSeed(seed) => {
167 // update the state and/or react to the event here
168 state.second_random_seed = seed;
169 info!("Second random seed: {}", seed);
170 }
171 Events::ThirdRandomSeed(seed) => {
172 // update the state and/or react to the event here
173 state.third_random_seed = seed;
174 info!("Third random seed: {}", seed);
175 }
176 }
177 // we now have an altered state
178 // there is a crate for detecting field changes on crates.io (https://crates.io/crates/fieldset) that might be useful here
179 // for now we just keep it simple
180 info!("State: {:?}", &state);
181
182 // here we react to the state, in this case here we want to stop the first random seed task after we got it a defined number of times
183 if state.times_we_got_first_random_seed == state.maximum_times_we_want_first_random_seed {
184 info!("Stopping the first random signal task");
185 // we send a command to the task
186 STOP_FIRST_RANDOM_SIGNAL.signal(Commands::Stop);
187 }
188 }
189}
190
191/// This task will generate random numbers in intervals of 30s
192/// The task will terminate after it has received a command signal to stop, see the orchestrate task for that.
193#[embassy_executor::task]
194async fn random_30s(_spawner: Spawner) {
195 let mut rng = RoscRng;
196 let sender = EVENT_CHANNEL.sender();
197 loop {
198 // we either await on the timer or the signal, whichever comes first.
199 let futures = select(Timer::after(Duration::from_secs(30)), STOP_FIRST_RANDOM_SIGNAL.wait()).await;
200 match futures {
201 Either::First(_) => {
202 // we received are operating on the timer
203 info!("30s are up, generating random number");
204 let random_number = rng.next_u32();
205 sender.send(Events::FirstRandomSeed(random_number)).await;
206 }
207 Either::Second(_) => {
208 // we received the signal to stop
209 info!("Received signal to stop, goodbye!");
210 break;
211 }
212 }
213 }
214}
215
216/// This task will generate random numbers in intervals of 60s
217#[embassy_executor::task]
218async fn random_60s(_spawner: Spawner) {
219 let mut rng = RoscRng;
220 let sender = EVENT_CHANNEL.sender();
221 loop {
222 Timer::after(Duration::from_secs(60)).await;
223 let random_number = rng.next_u32();
224 sender.send(Events::SecondRandomSeed(random_number)).await;
225 }
226}
227
228/// This task will generate random numbers in intervals of 90s
229#[embassy_executor::task]
230async fn random_90s(_spawner: Spawner) {
231 let mut rng = RoscRng;
232 let sender = EVENT_CHANNEL.sender();
233 loop {
234 Timer::after(Duration::from_secs(90)).await;
235 let random_number = rng.next_u32();
236 sender.send(Events::ThirdRandomSeed(random_number)).await;
237 }
238}
239
240/// This task will notify if we are connected to usb power
241#[embassy_executor::task]
242pub async fn usb_power(_spawner: Spawner, r: Vbus) {
243 let mut vbus_in = Input::new(r.pin_24, Pull::None);
244 let sender = EVENT_CHANNEL.sender();
245 loop {
246 sender.send(Events::UsbPowered(vbus_in.is_high())).await;
247 vbus_in.wait_for_any_edge().await;
248 }
249}
250
251/// This task will measure the vsys voltage in intervals of 30s
252#[embassy_executor::task]
253pub async fn vsys_voltage(_spawner: Spawner, r: Vsys) {
254 let mut adc = Adc::new(r.adc, Irqs, Config::default());
255 let vsys_in = r.pin_29;
256 let mut channel = Channel::new_pin(vsys_in, Pull::None);
257 let sender = EVENT_CHANNEL.sender();
258 loop {
259 // read the adc value
260 let adc_value = adc.read(&mut channel).await.unwrap();
261 // convert the adc value to voltage.
262 // 3.3 is the reference voltage, 3.0 is the factor for the inbuilt voltage divider and 4096 is the resolution of the adc
263 let voltage = (adc_value as f32) * 3.3 * 3.0 / 4096.0;
264 sender.send(Events::VsysVoltage(voltage)).await;
265 Timer::after(Duration::from_secs(30)).await;
266 }
267}