embedded_flight_scheduler/lib.rs
1//! # embedded-flight-scheduler
2//!
3//! Embedded flight real time scheduler library
4//!
5//! For more check out the [scheduler](https://github.com/matthunz/embedded-flight/tree/main/examples/scheduler) example on GitHub.
6//! ```ignore
7//! let clock = StandardClock::default();
8//!
9//! let a: Task<(), Error> = Task::new(|_| {
10//! dbg!("A");
11//! Ok(())
12//! });
13//!
14//! let b: Task<(), Error> = Task::new(|_| {
15//! dbg!("B");
16//! Ok(())
17//! });
18//!
19//! let mut tasks = [a.with_hz(2.), b.with_hz(1.)];
20//!
21//! let mut scheduler = Scheduler::new(&mut tasks, clock, 400);
22//!
23//! loop {
24//! scheduler.run(&mut ())?;
25//! }
26//! ```
27#![no_std]
28#![deny(missing_docs)]
29
30use embedded_time::{duration::Microseconds, Clock};
31use num_traits::ToPrimitive;
32
33mod error;
34pub use error::Error;
35
36mod task;
37pub use task::{Event, Task};
38
39/// Task scheduler for flight controllers
40pub struct Scheduler<'a, C, T, E = Error> {
41 /// The tasks to run in order of highest to lowest priority.
42 pub tasks: &'a mut [Task<T, E>],
43
44 /// The clock used for timing.
45 pub clock: C,
46
47 /// The current tick, incremented each run.
48 pub tick: u16,
49
50 /// The maximum amount of ticks a task can miss before slowing down the scheduler.
51 pub max_task_slowdown: u8,
52
53 /// The desired loop rate to run (in hz).
54 pub loop_rate_hz: i16,
55
56 // The period of the loop rate (in microseconds).
57 loop_period_us: u16,
58
59 /// The amount of ticks with tasks that exceed the `max_task_slowdown`.
60 pub task_not_achieved: u32,
61
62 /// The amount of ticks with tasks that remain in the `max_task_slowdown`.
63 pub task_all_achieved: u32,
64
65 /// The start time of the run loop (in microseconds).
66 pub loop_timer_start_us: u32,
67
68 /// The start time of the run loop (in seconds).
69 pub last_loop_time_s: f32,
70
71 /// Extra time to spend in the loop to catch up on tasks not achieved (in microseconds).
72 pub extra_loop_us: u32,
73}
74
75impl<'a, C, T, E> Scheduler<'a, C, T, E>
76where
77 C: Clock,
78 C::T: ToPrimitive,
79 E: From<Error>,
80{
81 /// Create a new scheduler from a slice of tasks, a clock, and the loop rate (in hz)
82 pub fn new(tasks: &'a mut [Task<T, E>], clock: C, loop_rate_hz: i16) -> Self {
83 let loop_period_us = (1000000 / loop_rate_hz as i32) as _;
84 Self {
85 tasks,
86 clock,
87 tick: 0,
88 loop_rate_hz,
89 loop_period_us,
90 max_task_slowdown: 4,
91 task_not_achieved: 0,
92 task_all_achieved: 0,
93 loop_timer_start_us: 0,
94 last_loop_time_s: 0.,
95 extra_loop_us: 0,
96 }
97 }
98
99 /// Calculate the time available and run as many tasks as possible.
100 pub fn run(&mut self, state: &mut T) -> Result<(), E> {
101 let sample_time_us = self.micros_since_epoch()?.0;
102
103 // Set initial loop_timer_start if not set
104 if self.loop_timer_start_us == 0 {
105 self.loop_timer_start_us = sample_time_us;
106 self.last_loop_time_s = 1. / self.loop_rate_hz as f32;
107 } else {
108 self.last_loop_time_s = (sample_time_us - self.loop_timer_start_us) as f32 * 1.0e-6;
109 }
110
111 // Reset the tick counter if we reach the limit
112 if self.tick == u16::MAX {
113 self.tick = 0;
114
115 // Todo maybe don't?
116 for task in self.tasks.iter_mut() {
117 task.last_run = 0;
118 }
119 } else {
120 self.tick += 1;
121 }
122
123 // run all the tasks that are due to run. Note that we only
124 // have to call this once per loop, as the tasks are scheduled
125 // in multiples of the main loop tick. So if they don't run on
126 // the first call to the scheduler they won't run on a later
127 // call until scheduler.tick() is called again
128 let loop_us = self.loop_period_us;
129 let now = self.micros_since_epoch()?;
130
131 let mut time_available = 0;
132 let loop_tick_us = now.0 - sample_time_us;
133 if loop_tick_us < loop_us as _ {
134 // get remaining time available for this loop
135 time_available = loop_us as u32 - loop_tick_us;
136 }
137
138 // add in extra loop time determined by not achieving scheduler tasks
139 time_available += self.extra_loop_us;
140
141 self.run_with_time_available_inner(state, now, time_available)?;
142
143 if self.task_not_achieved > 0 {
144 // add some extra time to the budget
145 self.extra_loop_us = (self.extra_loop_us + 100).min(5000);
146 self.task_not_achieved = 0;
147 self.task_all_achieved = 0;
148 } else if self.extra_loop_us > 0 {
149 self.task_all_achieved += 1;
150 if self.task_all_achieved > 50 {
151 // we have gone through 50 loops without a task taking too
152 // long. CPU pressure has eased, so drop the extra time we're
153 // giving each loop
154 self.task_all_achieved = 0;
155 // we are achieving all tasks, slowly lower the extra loop time
156 self.extra_loop_us = 0.max(self.extra_loop_us - 50);
157 }
158 }
159
160 self.loop_timer_start_us = sample_time_us;
161
162 Ok(())
163 }
164
165 /// Run as many tasks as possible in the given `time_available`.
166 pub fn run_with_time_available(
167 &mut self,
168 system: &mut T,
169 time_available: u32,
170 ) -> Result<(), E> {
171 let now = self.micros_since_epoch()?;
172 self.run_with_time_available_inner(system, now, time_available)
173 }
174
175 fn run_with_time_available_inner(
176 &mut self,
177 system: &mut T,
178 now: Microseconds<u32>,
179 time_available: u32,
180 ) -> Result<(), E> {
181 for task in self.tasks.iter_mut() {
182 if !task.is_high_priority {
183 let ticks = task.ticks(self.loop_rate_hz);
184
185 if let Some(dt) = task.ready(self.tick, ticks) {
186 // Check if the scheduler is going beyond the maximum slowdown factor
187 if dt as i16 >= ticks * self.max_task_slowdown as i16 {
188 // This will trigger increasing the time budget
189 self.task_not_achieved += 1;
190 }
191
192 if task.max_time_micros as u32 > time_available {
193 // Not enough time to run this task
194 // Try to fit another task into the time remaining
195 continue;
196 }
197 } else {
198 // This task is not ready
199 continue;
200 }
201 }
202
203 let state = Event {
204 state: system,
205 now,
206 available: Microseconds::new(time_available),
207 };
208 task.run(state, self.tick)?;
209 }
210
211 Ok(())
212 }
213
214 fn micros_since_epoch(&mut self) -> Result<Microseconds<u32>, Error> {
215 let instant = self.clock.try_now()?;
216 let duration = instant.duration_since_epoch();
217 let ms: Microseconds<C::T> = Microseconds::try_from(duration)?;
218 Ok(Microseconds::new(ms.0.to_u32().unwrap()))
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 #[test]
225 fn it_works() {
226 let result = 2 + 2;
227 assert_eq!(result, 4);
228 }
229}