accel_stepper/
driver.rs

1#[allow(unused_imports)] // used for rustdoc links
2use crate::CummulativeSteps;
3#[cfg(not(feature = "std"))]
4#[allow(unused_imports)]
5use libm::F32Ext;
6
7use crate::{
8    utils::{Clamp, DurationHelpers},
9    Device, StepContext, SystemClock,
10};
11use core::{f32::EPSILON, time::Duration};
12
13/// A stepper motor driver.
14/// 
15/// # Note
16///
17/// You may want to use the [`CummulativeSteps`] helper to convert a
18/// movement in "real" units (e.g. mm or inches) to the correct number of steps.
19#[derive(Debug, Default, PartialEq)]
20pub struct Driver {
21    max_speed: f32,
22    acceleration: f32,
23    current_position: i64,
24    step_interval: Duration,
25    speed: f32,
26    target_position: i64,
27    last_step_time: Duration,
28
29    /// The step counter for speed calculations
30    step_counter: i64,
31    initial_step_size: Duration,
32    last_step_size: Duration,
33    /// Min step size based on `max_speed`.
34    min_step_size: Duration,
35}
36
37impl Driver {
38    pub fn new() -> Driver {
39        let mut d = Driver::default();
40
41        // Set up some non-zero defaults so we can immediately run at constant
42        // speeds
43        d.set_max_speed(1.0);
44        d.set_acceleration(1.0);
45
46        d
47    }
48
49    /// Move to the specified location relative to the zero point (typically
50    /// set when calibrating using [`Driver::set_current_position()`]).
51    #[inline]
52    pub fn move_to(&mut self, location: i64) {
53        if self.target_position() != location {
54            self.target_position = location;
55            self.compute_new_speed();
56        }
57    }
58
59    /// Move forward by the specified number of steps.
60    #[inline]
61    pub fn move_by(&mut self, delta: i64) {
62        self.move_to(self.current_position() + delta);
63    }
64
65    /// Set the maximum permitted speed in `steps/second`.
66    ///
67    /// # Caution
68    ///
69    /// the maximum speed achievable depends on your processor and clock speed.
70    /// The default max speed is `1.0` step per second.
71    #[inline]
72    pub fn set_max_speed(&mut self, steps_per_second: f32) {
73        debug_assert!(steps_per_second > 0.0);
74
75        self.max_speed = steps_per_second;
76        self.min_step_size =
77            Duration::from_secs_f32_2(steps_per_second.recip());
78    }
79
80    /// Get the maximum speed.
81    #[inline]
82    pub fn max_speed(&self) -> f32 { self.max_speed }
83
84    /// Set the acceleration/deceleration rate (in `steps/sec/sec`).
85    #[inline]
86    pub fn set_acceleration(&mut self, acceleration: f32) {
87        if acceleration == 0.0 {
88            return;
89        }
90
91        let acceleration = acceleration.abs();
92
93        if (self.acceleration - acceleration).abs() > EPSILON {
94            // Recompute step_counter per Equation 17
95            self.step_counter = (self.step_counter as f32 * self.acceleration
96                / acceleration) as i64;
97            // New initial_step_size per Equation 7, with correction per
98            // Equation 15
99            let initial_step_size = 0.676 * (2.0 / acceleration).sqrt();
100            self.initial_step_size =
101                Duration::from_secs_f32_2(initial_step_size);
102            self.acceleration = acceleration;
103            self.compute_new_speed();
104        }
105    }
106
107    /// Get the acceleration/deceleration rate.
108    #[inline]
109    pub fn acceleration(&self) -> f32 { self.acceleration }
110
111    /// Set the desired constant speed in `steps/sec`.
112    ///
113    /// Speeds of more than 1000 steps per second are unreliable. Very slow
114    /// speeds may be set (eg 0.00027777 for once per hour, approximately).
115    /// Speed accuracy depends on the system's clock. Jitter depends on how
116    /// frequently you call the [`Driver::poll_at_constant_speed()`] method. The
117    /// speed will be limited by the current value of [`Driver::max_speed()`].
118    pub fn set_speed(&mut self, speed: f32) {
119        if (speed - self.speed).abs() < EPSILON {
120            return;
121        }
122
123        let speed = Clamp::clamp(speed, -self.max_speed, self.max_speed);
124
125        if speed == 0.0 || !speed.is_finite() {
126            self.step_interval = Duration::new(0, 0);
127        } else {
128            let duration_nanos = (1e9 / speed).abs().round();
129            self.step_interval = Duration::from_nanos(duration_nanos as u64);
130        }
131
132        self.speed = speed;
133    }
134
135    /// Get the most recently set speed.
136    #[inline]
137    pub fn speed(&self) -> f32 { self.speed }
138
139    /// Get the number of steps to go until reaching the target position.
140    #[inline]
141    pub fn distance_to_go(&self) -> i64 {
142        self.target_position() - self.current_position()
143    }
144
145    /// Get the most recently set target position.
146    #[inline]
147    pub fn target_position(&self) -> i64 { self.target_position }
148
149    /// Reset the current motor position so the current location is considered
150    /// the new `0` position.
151    ///
152    ///  Useful for setting a zero position on a stepper after an initial
153    /// hardware positioning move.
154    #[inline]
155    pub fn set_current_position(&mut self, position: i64) {
156        self.current_position = position;
157        self.target_position = position;
158        self.step_interval = Duration::new(0, 0);
159        self.speed = 0.0;
160    }
161
162    /// Get the current motor position, as measured by counting the number of
163    /// pulses emitted.
164    ///
165    /// # Note
166    ///
167    /// Stepper motors are an open-loop system, so there's no guarantee the
168    /// motor will *actually* be at that position.
169    #[inline]
170    pub fn current_position(&self) -> i64 { self.current_position }
171
172    /// Sets a new target position that causes the stepper to stop as quickly as
173    /// possible, using the current speed and acceleration parameters.
174    #[inline]
175    pub fn stop(&mut self) {
176        if self.speed == 0.0 {
177            return;
178        }
179
180        let stopping_distance =
181            (self.speed * self.speed) / (2.0 * self.acceleration);
182        let steps_to_stop = stopping_distance.round() as i64 + 1;
183
184        if self.speed > 0.0 {
185            self.move_by(steps_to_stop);
186        } else {
187            self.move_by(-steps_to_stop);
188        }
189    }
190
191    /// Checks to see if the motor is currently running to a target.
192    #[inline]
193    pub fn is_running(&self) -> bool {
194        self.speed != 0.0 || self.target_position() != self.current_position()
195    }
196
197    fn compute_new_speed(&mut self) {
198        let distance_to = self.distance_to_go();
199        let distance_to_stop =
200            (self.speed() * self.speed()) / (2.0 * self.acceleration());
201        let steps_to_stop = distance_to_stop.round() as i64;
202
203        if distance_to == 0 && steps_to_stop <= 1 {
204            // We are at the target and its time to stop
205            self.step_interval = Duration::new(0, 0);
206            self.speed = 0.0;
207            self.step_counter = 0;
208            return;
209        }
210
211        if distance_to > 0 {
212            // the target is in front of us
213            // We need to go forwards, maybe decelerate now?
214            if self.step_counter > 0 {
215                // Currently accelerating, need to decel now? Or maybe going the
216                // wrong way?
217                if steps_to_stop >= distance_to || distance_to < 0 {
218                    self.step_counter = -steps_to_stop; // start decelerating
219                }
220            } else if self.step_counter < 0 {
221                // Currently decelerating, need to accel again?
222                if steps_to_stop < distance_to && distance_to > 0 {
223                    self.step_counter = -self.step_counter; // start accelerating
224                }
225            }
226        } else if distance_to < 0 {
227            // we've gone past the target and need to go backwards. Maybe
228            // decelerating.
229            if self.step_counter > 0 {
230                // Currently accelerating, need to decel now? Or maybe going the
231                // wrong way?
232                if steps_to_stop >= -distance_to || distance_to > 0 {
233                    self.step_counter = -steps_to_stop;
234                }
235            } else if self.step_counter < 0 {
236                // currently decelerating, need to accel again?
237                if steps_to_stop < -distance_to && distance_to < 0 {
238                    self.step_counter = -self.step_counter;
239                }
240            }
241        }
242
243        if self.step_counter == 0 {
244            // This is the first step after having stopped
245            self.last_step_size = self.initial_step_size;
246        } else {
247            // Subsequent step. Works for accel (n is +_ve) and decel (n is
248            // -ve).
249            let last_step_size = self.last_step_size.as_secs_f32_2();
250            let last_step_size = last_step_size
251                - last_step_size * 2.0
252                    / ((4.0 * self.step_counter as f32) + 1.0);
253            self.last_step_size = Duration::from_secs_f32_2(last_step_size);
254            if self.last_step_size < self.min_step_size {
255                self.last_step_size = self.min_step_size;
256            }
257        }
258
259        self.step_counter += 1;
260        self.step_interval = self.last_step_size;
261        self.speed = self.last_step_size.as_secs_f32_2().recip();
262
263        if distance_to < 0 {
264            self.speed *= -1.0;
265        }
266    }
267
268    /// Poll the driver and step it if a step is due.
269    ///
270    /// This function must called as frequently as possoble, but at least once
271    /// per minimum step time interval, preferably as part of the main loop.
272    ///
273    /// Note that each call to [`Driver::poll()`] will make at most one step,
274    /// and then only when a step is due, based on the current speed and the
275    /// time since the last step.
276    ///
277    /// # Warning
278    ///
279    /// For correctness, the same [`SystemClock`] should be used every time
280    /// [`Driver::poll()`] is called. Failing to do so may mess up internal
281    /// timing calculations.
282    #[inline]
283    pub fn poll<C, D>(&mut self, device: D, clock: C) -> Result<(), D::Error>
284    where
285        C: SystemClock,
286        D: Device,
287    {
288        if self.poll_at_constant_speed(device, clock)? {
289            self.compute_new_speed();
290        }
291
292        Ok(())
293    }
294
295    /// Poll the motor and step it if a step is due, implementing a constant
296    /// speed as set by the most recent call to [`Driver::set_speed()`].
297    ///
298    /// You must call this as frequently as possible, but at least once per step
299    /// interval, returns true if the motor was stepped.
300    pub fn poll_at_constant_speed<C, D>(
301        &mut self,
302        mut device: D,
303        clock: C,
304    ) -> Result<bool, D::Error>
305    where
306        C: SystemClock,
307        D: Device,
308    {
309        // Dont do anything unless we actually have a step interval
310        if self.step_interval == Duration::new(0, 0) {
311            return Ok(false);
312        }
313
314        let now = clock.elapsed();
315
316        if now - self.last_step_time >= self.step_interval {
317            // we need to take a step
318
319            // Note: we can't assign to current_position directly because we
320            // a failed step shouldn't update any internal state
321            let new_position = if self.distance_to_go() > 0 {
322                self.current_position + 1
323            } else {
324                self.current_position - 1
325            };
326
327            let ctx = StepContext {
328                position: new_position,
329                step_time: now,
330            };
331            device.step(&ctx)?;
332
333            self.current_position = new_position;
334            self.last_step_time = now; // Caution: does not account for costs in step()
335
336            Ok(true)
337        } else {
338            Ok(false)
339        }
340    }
341}
342
343#[cfg(test)]
344mod tests {
345    use super::*;
346    use std::cell::Cell;
347
348    #[derive(Debug, Copy, Clone, PartialEq, Default)]
349    struct NopDevice;
350
351    impl Device for NopDevice {
352        type Error = ();
353
354        fn step(&mut self, _ctx: &StepContext) -> Result<(), Self::Error> {
355            Ok(())
356        }
357    }
358
359    #[derive(Debug, Default)]
360    struct DummyClock {
361        ticks: Cell<u32>,
362    }
363
364    impl SystemClock for DummyClock {
365        fn elapsed(&self) -> Duration {
366            let ticks = self.ticks.get();
367            self.ticks.set(ticks + 1);
368
369            Duration::new(ticks as u64, 0)
370        }
371    }
372
373    #[test]
374    fn compute_new_speeds_when_already_at_target() {
375        let mut driver = Driver::default();
376        driver.target_position = driver.current_position;
377
378        driver.compute_new_speed();
379
380        assert_eq!(driver.speed(), 0.0);
381        assert_eq!(driver.step_interval, Duration::new(0, 0));
382    }
383
384    #[test]
385    fn dont_step_when_already_at_target() {
386        let mut forward = 0;
387        let mut back = 0;
388        let clock = DummyClock::default();
389
390        {
391            let mut dev = crate::func_device(|| forward += 1, || back += 1);
392            let mut driver = Driver::new();
393            driver.target_position = driver.current_position;
394
395            for _ in 0..100 {
396                driver.poll(&mut dev, &clock).unwrap();
397            }
398        }
399
400        assert_eq!(forward, 0);
401        assert_eq!(back, 0);
402    }
403}