aboutsummaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
author1-rafael-1 <[email protected]>2024-12-31 15:54:42 +0100
committer1-rafael-1 <[email protected]>2024-12-31 15:54:42 +0100
commite6001e66f8b21483a707d0015f9d278556759a7c (patch)
tree02476009a25cc74e8e74f4d865e2e900f4029eaf /examples
parent667dfa34b525f727936d621ba91001fa25d80426 (diff)
Improve orchestrate_tasks example with shared state and better documentation
Add mutex-protected shared system state Improve task coordination and signaling Add more documentation
Diffstat (limited to 'examples')
-rw-r--r--examples/rp/src/bin/orchestrate_tasks.rs312
1 files changed, 156 insertions, 156 deletions
diff --git a/examples/rp/src/bin/orchestrate_tasks.rs b/examples/rp/src/bin/orchestrate_tasks.rs
index 0e21d5833..6f209da95 100644
--- a/examples/rp/src/bin/orchestrate_tasks.rs
+++ b/examples/rp/src/bin/orchestrate_tasks.rs
@@ -1,20 +1,18 @@
1//! This example demonstrates some approaches to communicate between tasks in order to orchestrate the state of the system. 1//! This example demonstrates some approaches to communicate between tasks in order to orchestrate the state of the system.
2//! 2//!
3//! We demonstrate how to: 3//! The system consists of several tasks:
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. 4//! - Three tasks that generate random numbers at different intervals (simulating i.e. sensor readings)
5//! - use a signal to terminate a task. 5//! - A task that monitors USB power connection (hardware event handling)
6//! - use command channels to send commands to another task. 6//! - A task that reads system voltage (ADC sampling)
7//! - use different ways to receive messages, from a straightforwar awaiting on one channel to a more complex awaiting on multiple futures. 7//! - A consumer task that processes all this information
8//! 8//!
9//! There are more patterns to orchestrate tasks, this is just one example. 9//! The system maintains state in a single place, wrapped in a Mutex.
10//! 10//!
11//! We will use these tasks to generate example "state information": 11//! We demonstrate how to:
12//! - a task that generates random numbers in intervals of 60s 12//! - use a mutex to maintain shared state between tasks
13//! - a task that generates random numbers in intervals of 30s 13//! - use a channel to send events between tasks
14//! - a task that generates random numbers in intervals of 90s 14//! - use an orchestrator task to coordinate tasks and handle state transitions
15//! - a task that notifies about being attached/disattached from usb power 15//! - use signals to notify about state changes and terminate tasks
16//! - a task that measures vsys voltage in intervals of 30s
17//! - a task that consumes the state information and reacts to it
18 16
19#![no_std] 17#![no_std]
20#![no_main] 18#![no_main]
@@ -28,15 +26,12 @@ use embassy_rp::clocks::RoscRng;
28use embassy_rp::gpio::{Input, Pull}; 26use embassy_rp::gpio::{Input, Pull};
29use embassy_rp::{bind_interrupts, peripherals}; 27use embassy_rp::{bind_interrupts, peripherals};
30use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex; 28use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
31use embassy_sync::{channel, signal}; 29use embassy_sync::{channel, mutex::Mutex, signal};
32use embassy_time::{Duration, Timer}; 30use embassy_time::{Duration, Timer};
33use rand::RngCore; 31use rand::RngCore;
34use {defmt_rtt as _, panic_probe as _}; 32use {defmt_rtt as _, panic_probe as _};
35 33
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. 34// Hardware resource assignment. See other examples for different ways of doing this.
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! { 35assign_resources! {
41 vsys: Vsys { 36 vsys: Vsys {
42 adc: ADC, 37 adc: ADC,
@@ -47,228 +42,233 @@ assign_resources! {
47 }, 42 },
48} 43}
49 44
45// Interrupt binding - required for hardware peripherals like ADC
50bind_interrupts!(struct Irqs { 46bind_interrupts!(struct Irqs {
51 ADC_IRQ_FIFO => InterruptHandler; 47 ADC_IRQ_FIFO => InterruptHandler;
52}); 48});
53 49
54/// This is the type of Events that we will send from the worker tasks to the orchestrating task. 50/// Events that worker tasks send to the orchestrator
55enum Events { 51enum Events {
56 UsbPowered(bool), 52 UsbPowered(bool), // USB connection state changed
57 VsysVoltage(f32), 53 VsysVoltage(f32), // New voltage reading
58 FirstRandomSeed(u32), 54 FirstRandomSeed(u32), // Random number from 30s timer
59 SecondRandomSeed(u32), 55 SecondRandomSeed(u32), // Random number from 60s timer
60 ThirdRandomSeed(u32), 56 ThirdRandomSeed(u32), // Random number from 90s timer
61 ResetFirstRandomSeed, 57 ResetFirstRandomSeed, // Signal to reset the first counter
62} 58}
63 59
64/// This is the type of Commands that we will send from the orchestrating task to the worker tasks. 60/// Commands that can control task behavior.
65/// Note that we are lazy here and only have one command, you might want to have more. 61/// Currently only used to stop tasks, but could be extended for other controls.
66enum Commands { 62enum Commands {
67 /// This command will stop the appropriate worker task 63 /// Signals a task to stop execution
68 Stop, 64 Stop,
69} 65}
70 66
71/// 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. 67/// The central state of our system, shared between tasks.
72#[derive(Default, Debug, Clone, Format)] 68#[derive(Clone, Format)]
73struct State { 69struct State {
74 usb_powered: bool, 70 usb_powered: bool,
75 vsys_voltage: f32, 71 vsys_voltage: f32,
76 first_random_seed: u32, 72 first_random_seed: u32,
77 second_random_seed: u32, 73 second_random_seed: u32,
78 third_random_seed: u32, 74 third_random_seed: u32,
75 first_random_seed_task_running: bool,
79 times_we_got_first_random_seed: u8, 76 times_we_got_first_random_seed: u8,
80 maximum_times_we_want_first_random_seed: u8, 77 maximum_times_we_want_first_random_seed: u8,
81} 78}
82 79
80/// A formatted view of the system status, used for logging. Used for the below `get_system_summary` fn.
81#[derive(Format)]
82struct SystemStatus {
83 power_source: &'static str,
84 voltage: f32,
85}
86
83impl State { 87impl State {
84 fn new() -> Self { 88 const fn new() -> Self {
85 Self { 89 Self {
86 usb_powered: false, 90 usb_powered: false,
87 vsys_voltage: 0.0, 91 vsys_voltage: 0.0,
88 first_random_seed: 0, 92 first_random_seed: 0,
89 second_random_seed: 0, 93 second_random_seed: 0,
90 third_random_seed: 0, 94 third_random_seed: 0,
95 first_random_seed_task_running: false,
91 times_we_got_first_random_seed: 0, 96 times_we_got_first_random_seed: 0,
92 maximum_times_we_want_first_random_seed: 3, 97 maximum_times_we_want_first_random_seed: 3,
93 } 98 }
94 } 99 }
100
101 /// Returns a formatted summary of power state and voltage.
102 /// Shows how to create methods that work with shared state.
103 fn get_system_summary(&self) -> SystemStatus {
104 SystemStatus {
105 power_source: if self.usb_powered {
106 "USB powered"
107 } else {
108 "Battery powered"
109 },
110 voltage: self.vsys_voltage,
111 }
112 }
95} 113}
96 114
97/// Channel for the events that we want the orchestrator to react to, all state events are of the type Enum Events. 115/// The shared state protected by a mutex
98/// 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 116static SYSTEM_STATE: Mutex<CriticalSectionRawMutex, State> = Mutex::new(State::new());
99/// 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 117
100/// 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 118/// Channel for events from worker tasks to the orchestrator
101/// in the queue, so the worker tasks can in all nominal cases send their events and continue with their work without waiting.
102/// 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.
103static EVENT_CHANNEL: channel::Channel<CriticalSectionRawMutex, Events, 10> = channel::Channel::new(); 119static EVENT_CHANNEL: channel::Channel<CriticalSectionRawMutex, Events, 10> = channel::Channel::new();
104 120
105/// 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. 121/// Signal used to stop the first random number task
106static STOP_FIRST_RANDOM_SIGNAL: signal::Signal<CriticalSectionRawMutex, Commands> = signal::Signal::new(); 122static STOP_FIRST_RANDOM_SIGNAL: signal::Signal<CriticalSectionRawMutex, Commands> = signal::Signal::new();
107 123
108/// Channel for the state that we want the consumer task to react to. We use a channel here, because we want to have a queue of state changes, although 124/// Signal for notifying about state changes
109/// we want the queue to be of size 1, because we want to finish rwacting to the state change before the next one comes in. This is just a design choice 125static STATE_CHANGED: signal::Signal<CriticalSectionRawMutex, ()> = signal::Signal::new();
110/// and depends on your use case.
111static CONSUMER_CHANNEL: channel::Channel<CriticalSectionRawMutex, State, 1> = channel::Channel::new();
112
113// And now we can put all this into use
114 126
115/// 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
116/// 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.
117#[embassy_executor::main] 127#[embassy_executor::main]
118async fn main(spawner: Spawner) { 128async fn main(spawner: Spawner) {
119 // initialize the peripherals
120 let p = embassy_rp::init(Default::default()); 129 let p = embassy_rp::init(Default::default());
121 // split the resources, for convenience - see above
122 let r = split_resources! {p}; 130 let r = split_resources! {p};
123 131
124 // spawn the tasks
125 spawner.spawn(orchestrate(spawner)).unwrap(); 132 spawner.spawn(orchestrate(spawner)).unwrap();
126 spawner.spawn(random_60s(spawner)).unwrap(); 133 spawner.spawn(random_60s(spawner)).unwrap();
127 spawner.spawn(random_90s(spawner)).unwrap(); 134 spawner.spawn(random_90s(spawner)).unwrap();
135 // `random_30s` is not spawned here, butin the orchestrate task depending on state
128 spawner.spawn(usb_power(spawner, r.vbus)).unwrap(); 136 spawner.spawn(usb_power(spawner, r.vbus)).unwrap();
129 spawner.spawn(vsys_voltage(spawner, r.vsys)).unwrap(); 137 spawner.spawn(vsys_voltage(spawner, r.vsys)).unwrap();
130 spawner.spawn(consumer(spawner)).unwrap(); 138 spawner.spawn(consumer(spawner)).unwrap();
131} 139}
132 140
133/// This is the task handling the system state and orchestrating the other tasks. WEe can regard this as the "main loop" of the system. 141/// Main task that processes all events and updates system state.
134#[embassy_executor::task] 142#[embassy_executor::task]
135async fn orchestrate(_spawner: Spawner) { 143async fn orchestrate(spawner: Spawner) {
136 let mut state = State::new();
137
138 // we need to have a receiver for the events
139 let receiver = EVENT_CHANNEL.receiver(); 144 let receiver = EVENT_CHANNEL.receiver();
140 145
141 // and we need a sender for the consumer task
142 let state_sender = CONSUMER_CHANNEL.sender();
143
144 loop { 146 loop {
145 // we await on the receiver, this will block until a new event is available 147 // Do nothing until we receive any event
146 // 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
147 // see the embassy_futures docs: https://docs.embassy.dev/embassy-futures/git/default/select/index.html
148 // The task random_30s does a select, if you want to have a look at that.
149 // 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.
150 // We keep it simple here.
151 let event = receiver.receive().await; 148 let event = receiver.receive().await;
152 149
153 // react to the events 150 // Scope in which we want to lock the system state. As an alternative we could also call `drop` on the state
154 match event { 151 {
155 Events::UsbPowered(usb_powered) => { 152 let mut state = SYSTEM_STATE.lock().await;
156 // update the state and/or react to the event here 153
157 state.usb_powered = usb_powered; 154 match event {
158 info!("Usb powered: {}", usb_powered); 155 Events::UsbPowered(usb_powered) => {
159 } 156 state.usb_powered = usb_powered;
160 Events::VsysVoltage(voltage) => { 157 info!("Usb powered: {}", usb_powered);
161 // update the state and/or react to the event here 158 info!("System summary: {}", state.get_system_summary());
162 state.vsys_voltage = voltage; 159 }
163 info!("Vsys voltage: {}", voltage); 160 Events::VsysVoltage(voltage) => {
164 } 161 state.vsys_voltage = voltage;
165 Events::FirstRandomSeed(seed) => { 162 info!("Vsys voltage: {}", voltage);
166 // update the state and/or react to the event here 163 }
167 state.first_random_seed = seed; 164 Events::FirstRandomSeed(seed) => {
168 // here we change some meta state, we count how many times we got the first random seed 165 state.first_random_seed = seed;
169 state.times_we_got_first_random_seed += 1; 166 state.times_we_got_first_random_seed += 1;
170 info!( 167 info!(
171 "First random seed: {}, and that was iteration {} of receiving this.", 168 "First random seed: {}, and that was iteration {} of receiving this.",
172 seed, &state.times_we_got_first_random_seed 169 seed, &state.times_we_got_first_random_seed
173 ); 170 );
174 } 171 }
175 Events::SecondRandomSeed(seed) => { 172 Events::SecondRandomSeed(seed) => {
176 // update the state and/or react to the event here 173 state.second_random_seed = seed;
177 state.second_random_seed = seed; 174 info!("Second random seed: {}", seed);
178 info!("Second random seed: {}", seed); 175 }
179 } 176 Events::ThirdRandomSeed(seed) => {
180 Events::ThirdRandomSeed(seed) => { 177 state.third_random_seed = seed;
181 // update the state and/or react to the event here 178 info!("Third random seed: {}", seed);
182 state.third_random_seed = seed; 179 }
183 info!("Third random seed: {}", seed); 180 Events::ResetFirstRandomSeed => {
181 state.times_we_got_first_random_seed = 0;
182 state.first_random_seed = 0;
183 info!("Resetting the first random seed counter");
184 }
184 } 185 }
185 Events::ResetFirstRandomSeed => { 186
186 // update the state and/or react to the event here 187 // Handle task orchestration based on state
187 state.times_we_got_first_random_seed = 0; 188 // Just placed as an example here, could be hooked into the event system, puton a timer, ...
188 state.first_random_seed = 0; 189 match state.times_we_got_first_random_seed {
189 info!("Resetting the first random seed counter"); 190 max if max == state.maximum_times_we_want_first_random_seed => {
191 info!("Stopping the first random signal task");
192 STOP_FIRST_RANDOM_SIGNAL.signal(Commands::Stop);
193 EVENT_CHANNEL.sender().send(Events::ResetFirstRandomSeed).await;
194 }
195 0 => {
196 let respawn_first_random_seed_task = !state.first_random_seed_task_running;
197 // Deliberately dropping the Mutex lock here to release it before a lengthy operation
198 drop(state);
199 if respawn_first_random_seed_task {
200 info!("(Re)-Starting the first random signal task");
201 spawner.spawn(random_30s(spawner)).unwrap();
202 }
203 }
204 _ => {}
190 } 205 }
191 } 206 }
192 // we now have an altered state 207
193 // there is a crate for detecting field changes on crates.io (https://crates.io/crates/fieldset) that might be useful here 208 STATE_CHANGED.signal(());
194 // for now we just keep it simple
195
196 // we send the state to the consumer task
197 // since the channel has a size of 1, this will block until the consumer task has received the state, which is what we want here in this example
198 // **Note:** It is bad design to send too much data between tasks, with no clear definition of what "too much" is. In this example we send the
199 // whole state, in a real world application you might want to send only the data, that is relevant to the consumer task AND only when it has changed.
200 // We keep it simple here.
201 state_sender.send(state.clone()).await;
202 } 209 }
203} 210}
204 211
205/// This task will consume the state information and react to it. This is a simple example, in a real world application this would be more complex 212/// Task that monitors state changes and logs system status.
206/// and we could have multiple consumer tasks, each reacting to different parts of the state.
207#[embassy_executor::task] 213#[embassy_executor::task]
208async fn consumer(spawner: Spawner) { 214async fn consumer(_spawner: Spawner) {
209 // we need to have a receiver for the state
210 let receiver = CONSUMER_CHANNEL.receiver();
211 let sender = EVENT_CHANNEL.sender();
212 loop { 215 loop {
213 // we await on the receiver, this will block until a new state is available 216 // Wait for state change notification
214 let state = receiver.receive().await; 217 STATE_CHANGED.wait().await;
215 // react to the state, in this case here we just log it 218
216 info!("The consumer has reveived this state: {:?}", &state); 219 let state = SYSTEM_STATE.lock().await;
217 220 info!(
218 // here we react to the state, in this case here we want to start or stop the first random signal task depending on the state of the system 221 "State update - {} | Seeds - First: {} (count: {}/{}, running: {}), Second: {}, Third: {}",
219 match state.times_we_got_first_random_seed { 222 state.get_system_summary(),
220 max if max == state.maximum_times_we_want_first_random_seed => { 223 state.first_random_seed,
221 info!("Stopping the first random signal task"); 224 state.times_we_got_first_random_seed,
222 // we send a command to the task 225 state.maximum_times_we_want_first_random_seed,
223 STOP_FIRST_RANDOM_SIGNAL.signal(Commands::Stop); 226 state.first_random_seed_task_running,
224 // we notify the orchestrator that we have sent the command 227 state.second_random_seed,
225 sender.send(Events::ResetFirstRandomSeed).await; 228 state.third_random_seed
226 } 229 );
227 0 => {
228 // we start the task, which presents us with an interesting problem, because we may return here before the task has started
229 // here we just try and log if the task has started, in a real world application you might want to handle this more gracefully
230 info!("Starting the first random signal task");
231 match spawner.spawn(random_30s(spawner)) {
232 Ok(_) => info!("Successfully spawned random_30s task"),
233 Err(e) => info!("Failed to spawn random_30s task: {:?}", e),
234 }
235 }
236 _ => {}
237 }
238 } 230 }
239} 231}
240 232
241/// This task will generate random numbers in intervals of 30s 233/// Task that generates random numbers every 30 seconds until stopped.
242/// The task will terminate after it has received a command signal to stop, see the orchestrate task for that. 234/// Shows how to handle both timer events and stop signals.
243/// Note that we are not spawning this task from main, as we will show how such a task can be spawned and closed dynamically. 235/// As an example of some routine we want to be on or off depending on other needs.
244#[embassy_executor::task] 236#[embassy_executor::task]
245async fn random_30s(_spawner: Spawner) { 237async fn random_30s(_spawner: Spawner) {
238 {
239 let mut state = SYSTEM_STATE.lock().await;
240 state.first_random_seed_task_running = true;
241 }
242
246 let mut rng = RoscRng; 243 let mut rng = RoscRng;
247 let sender = EVENT_CHANNEL.sender(); 244 let sender = EVENT_CHANNEL.sender();
245
248 loop { 246 loop {
249 // we either await on the timer or the signal, whichever comes first. 247 // Wait for either 30s timer or stop signal (like select() in Go)
250 let futures = select(Timer::after(Duration::from_secs(30)), STOP_FIRST_RANDOM_SIGNAL.wait()).await; 248 match select(Timer::after(Duration::from_secs(30)), STOP_FIRST_RANDOM_SIGNAL.wait()).await {
251 match futures {
252 Either::First(_) => { 249 Either::First(_) => {
253 // we received are operating on the timer
254 info!("30s are up, generating random number"); 250 info!("30s are up, generating random number");
255 let random_number = rng.next_u32(); 251 let random_number = rng.next_u32();
256 sender.send(Events::FirstRandomSeed(random_number)).await; 252 sender.send(Events::FirstRandomSeed(random_number)).await;
257 } 253 }
258 Either::Second(_) => { 254 Either::Second(_) => {
259 // we received the signal to stop
260 info!("Received signal to stop, goodbye!"); 255 info!("Received signal to stop, goodbye!");
256
257 let mut state = SYSTEM_STATE.lock().await;
258 state.first_random_seed_task_running = false;
259
261 break; 260 break;
262 } 261 }
263 } 262 }
264 } 263 }
265} 264}
266 265
267/// This task will generate random numbers in intervals of 60s 266/// Task that generates random numbers every 60 seconds. As an example of some routine.
268#[embassy_executor::task] 267#[embassy_executor::task]
269async fn random_60s(_spawner: Spawner) { 268async fn random_60s(_spawner: Spawner) {
270 let mut rng = RoscRng; 269 let mut rng = RoscRng;
271 let sender = EVENT_CHANNEL.sender(); 270 let sender = EVENT_CHANNEL.sender();
271
272 loop { 272 loop {
273 Timer::after(Duration::from_secs(60)).await; 273 Timer::after(Duration::from_secs(60)).await;
274 let random_number = rng.next_u32(); 274 let random_number = rng.next_u32();
@@ -276,11 +276,12 @@ async fn random_60s(_spawner: Spawner) {
276 } 276 }
277} 277}
278 278
279/// This task will generate random numbers in intervals of 90s 279/// Task that generates random numbers every 90 seconds. . As an example of some routine.
280#[embassy_executor::task] 280#[embassy_executor::task]
281async fn random_90s(_spawner: Spawner) { 281async fn random_90s(_spawner: Spawner) {
282 let mut rng = RoscRng; 282 let mut rng = RoscRng;
283 let sender = EVENT_CHANNEL.sender(); 283 let sender = EVENT_CHANNEL.sender();
284
284 loop { 285 loop {
285 Timer::after(Duration::from_secs(90)).await; 286 Timer::after(Duration::from_secs(90)).await;
286 let random_number = rng.next_u32(); 287 let random_number = rng.next_u32();
@@ -288,31 +289,30 @@ async fn random_90s(_spawner: Spawner) {
288 } 289 }
289} 290}
290 291
291/// This task will notify if we are connected to usb power 292/// Task that monitors USB power connection. As an example of some Interrupt somewhere.
292#[embassy_executor::task] 293#[embassy_executor::task]
293pub async fn usb_power(_spawner: Spawner, r: Vbus) { 294pub async fn usb_power(_spawner: Spawner, r: Vbus) {
294 let mut vbus_in = Input::new(r.pin_24, Pull::None); 295 let mut vbus_in = Input::new(r.pin_24, Pull::None);
295 let sender = EVENT_CHANNEL.sender(); 296 let sender = EVENT_CHANNEL.sender();
297
296 loop { 298 loop {
297 sender.send(Events::UsbPowered(vbus_in.is_high())).await; 299 sender.send(Events::UsbPowered(vbus_in.is_high())).await;
298 vbus_in.wait_for_any_edge().await; 300 vbus_in.wait_for_any_edge().await;
299 } 301 }
300} 302}
301 303
302/// This task will measure the vsys voltage in intervals of 30s 304/// Task that reads system voltage through ADC. As an example of some continuous sensor reading.
303#[embassy_executor::task] 305#[embassy_executor::task]
304pub async fn vsys_voltage(_spawner: Spawner, r: Vsys) { 306pub async fn vsys_voltage(_spawner: Spawner, r: Vsys) {
305 let mut adc = Adc::new(r.adc, Irqs, Config::default()); 307 let mut adc = Adc::new(r.adc, Irqs, Config::default());
306 let vsys_in = r.pin_29; 308 let vsys_in = r.pin_29;
307 let mut channel = Channel::new_pin(vsys_in, Pull::None); 309 let mut channel = Channel::new_pin(vsys_in, Pull::None);
308 let sender = EVENT_CHANNEL.sender(); 310 let sender = EVENT_CHANNEL.sender();
311
309 loop { 312 loop {
310 // read the adc value 313 Timer::after(Duration::from_secs(30)).await;
311 let adc_value = adc.read(&mut channel).await.unwrap(); 314 let adc_value = adc.read(&mut channel).await.unwrap();
312 // convert the adc value to voltage.
313 // 3.3 is the reference voltage, 3.0 is the factor for the inbuilt voltage divider and 4096 is the resolution of the adc
314 let voltage = (adc_value as f32) * 3.3 * 3.0 / 4096.0; 315 let voltage = (adc_value as f32) * 3.3 * 3.0 / 4096.0;
315 sender.send(Events::VsysVoltage(voltage)).await; 316 sender.send(Events::VsysVoltage(voltage)).await;
316 Timer::after(Duration::from_secs(30)).await;
317 } 317 }
318} 318}