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}