1use core::marker::PhantomData;
6
7use embedded_hal::delay::DelayNs;
8use embedded_hal::digital::OutputPin;
9
10use crate::config::units::{Degrees, Steps};
11use crate::config::MechanicalConstraints;
12use crate::error::{Error, MotorError, Result};
13use crate::motion::{Direction, MotionExecutor, MotionPhase, MotionProfile};
14
15use super::position::Position;
16use super::state::{Idle, MotorState, Moving, StateName};
17
18pub struct StepperMotor<STEP, DIR, DELAY, STATE = Idle>
26where
27 STEP: OutputPin,
28 DIR: OutputPin,
29 DELAY: DelayNs,
30 STATE: MotorState,
31{
32 step_pin: STEP,
34
35 dir_pin: DIR,
37
38 delay: DELAY,
40
41 position: Position,
43
44 current_direction: Option<Direction>,
46
47 constraints: MechanicalConstraints,
49
50 name: heapless::String<32>,
52
53 invert_direction: bool,
55
56 backlash_steps: i64,
58
59 executor: Option<MotionExecutor>,
61
62 _state: PhantomData<STATE>,
64}
65
66impl<STEP, DIR, DELAY, STATE> StepperMotor<STEP, DIR, DELAY, STATE>
67where
68 STEP: OutputPin,
69 DIR: OutputPin,
70 DELAY: DelayNs,
71 STATE: MotorState + StateName,
72{
73 #[inline]
75 pub fn name(&self) -> &str {
76 self.name.as_str()
77 }
78
79 #[inline]
81 pub fn position_steps(&self) -> Steps {
82 self.position.steps()
83 }
84
85 #[inline]
87 pub fn position_degrees(&self) -> Degrees {
88 self.position.degrees()
89 }
90
91 #[inline]
93 pub fn constraints(&self) -> &MechanicalConstraints {
94 &self.constraints
95 }
96
97 #[inline]
99 pub fn state_name(&self) -> &'static str {
100 STATE::name()
101 }
102}
103
104impl<STEP, DIR, DELAY> StepperMotor<STEP, DIR, DELAY, Idle>
105where
106 STEP: OutputPin,
107 DIR: OutputPin,
108 DELAY: DelayNs,
109{
110 pub(crate) fn new(
112 step_pin: STEP,
113 dir_pin: DIR,
114 delay: DELAY,
115 constraints: MechanicalConstraints,
116 name: heapless::String<32>,
117 invert_direction: bool,
118 backlash_steps: i64,
119 ) -> Self {
120 Self {
121 step_pin,
122 dir_pin,
123 delay,
124 position: Position::new(constraints.steps_per_degree),
125 current_direction: None,
126 constraints,
127 name,
128 invert_direction,
129 backlash_steps,
130 executor: None,
131 _state: PhantomData,
132 }
133 }
134
135 pub fn move_to(
139 mut self,
140 target: Degrees,
141 ) -> core::result::Result<StepperMotor<STEP, DIR, DELAY, Moving>, (Self, Error)> {
142 let target_steps = Steps::from_degrees(target, self.constraints.steps_per_degree);
144 let delta_steps = target_steps.0 - self.position.steps().0;
145
146 if delta_steps == 0 {
147 return Err((self, Error::Motion(crate::error::MotionError::MoveTooShort {
149 steps: 0,
150 minimum: 1,
151 })));
152 }
153
154 let limit_check = self.constraints.limits.as_ref().and_then(|limits| {
156 if limits.apply(target_steps.0).is_none() {
157 Some(if delta_steps > 0 {
158 limits.max_steps
159 } else {
160 limits.min_steps
161 })
162 } else {
163 None
164 }
165 });
166
167 if let Some(limit) = limit_check {
168 return Err((
169 self,
170 Error::Motor(MotorError::LimitExceeded {
171 position: target_steps.0,
172 limit,
173 }),
174 ));
175 }
176
177 let profile = MotionProfile::symmetric_trapezoidal(
179 delta_steps,
180 self.constraints.max_velocity_steps_per_sec,
181 self.constraints.max_acceleration_steps_per_sec2,
182 );
183
184 let direction = profile.direction;
186 if self.set_direction(direction).is_err() {
187 return Err((self, Error::Motor(MotorError::PinError)));
188 }
189
190 let executor = MotionExecutor::new(profile);
192
193 Ok(StepperMotor {
195 step_pin: self.step_pin,
196 dir_pin: self.dir_pin,
197 delay: self.delay,
198 position: self.position,
199 current_direction: self.current_direction,
200 constraints: self.constraints,
201 name: self.name,
202 invert_direction: self.invert_direction,
203 backlash_steps: self.backlash_steps,
204 executor: Some(executor),
205 _state: PhantomData,
206 })
207 }
208
209 pub fn move_by(
211 self,
212 delta: Degrees,
213 ) -> core::result::Result<StepperMotor<STEP, DIR, DELAY, Moving>, (Self, Error)> {
214 let target = Degrees(self.position.degrees().0 + delta.0);
215 self.move_to(target)
216 }
217
218 pub fn set_origin(&mut self) {
220 self.position.set_origin();
221 }
222
223 pub fn set_position(&mut self, degrees: Degrees) {
225 self.position.set_degrees(degrees);
226 }
227
228 pub fn execute(
249 self,
250 trajectory_name: &str,
251 registry: &crate::trajectory::TrajectoryRegistry,
252 ) -> core::result::Result<Self, (Self, Error)> {
253 let trajectory = match registry.get(trajectory_name) {
255 Some(t) => t,
256 None => {
257 let mut msg: heapless::String<64> = heapless::String::new();
259 let _ = msg.push_str("trajectory '");
260 let _ = msg.push_str(trajectory_name);
261 let _ = msg.push_str("' not found");
262 return Err((
263 self,
264 Error::Trajectory(crate::error::TrajectoryError::InvalidName(msg)),
265 ));
266 }
267 };
268
269 if trajectory.motor.as_str() != self.name.as_str() {
271 let mut msg: heapless::String<64> = heapless::String::new();
272 let _ = msg.push_str("trajectory '");
273 let _ = msg.push_str(trajectory_name);
274 let _ = msg.push_str("' is for motor '");
275 let _ = msg.push_str(trajectory.motor.as_str());
276 let _ = msg.push_str("'");
277 return Err((
278 self,
279 Error::Trajectory(crate::error::TrajectoryError::InvalidName(msg)),
280 ));
281 }
282
283 let target = trajectory.target_degrees;
285 self.move_to_blocking(target)
286 }
287
288 pub fn move_to_blocking(
292 self,
293 target: Degrees,
294 ) -> core::result::Result<Self, (Self, Error)> {
295 match self.move_to(target) {
296 Ok(moving) => {
297 match moving.run_to_completion() {
298 Ok(idle) => Ok(idle),
299 Err(e) => {
300 panic!("Motor step error during move: {:?}", e);
303 }
304 }
305 }
306 Err(e) => Err(e),
307 }
308 }
309
310 fn set_direction(&mut self, direction: Direction) -> core::result::Result<(), ()> {
311 if self.current_direction == Some(direction) {
312 return Ok(());
313 }
314
315 let pin_high = match direction {
316 Direction::Clockwise => !self.invert_direction,
317 Direction::CounterClockwise => self.invert_direction,
318 };
319
320 if pin_high {
321 self.dir_pin.set_high().map_err(|_| ())?;
322 } else {
323 self.dir_pin.set_low().map_err(|_| ())?;
324 }
325
326 self.current_direction = Some(direction);
327 Ok(())
328 }
329}
330
331impl<STEP, DIR, DELAY> StepperMotor<STEP, DIR, DELAY, Moving>
332where
333 STEP: OutputPin,
334 DIR: OutputPin,
335 DELAY: DelayNs,
336{
337 pub fn step(&mut self) -> Result<bool> {
341 let executor = self.executor.as_mut().ok_or(MotorError::NotInitialized)?;
342
343 if executor.is_complete() {
344 return Ok(true);
345 }
346
347 self.step_pin.set_high().map_err(|_| MotorError::PinError)?;
349
350 self.delay.delay_us(2);
352
353 self.step_pin.set_low().map_err(|_| MotorError::PinError)?;
354
355 let direction = executor.profile().direction;
357 self.position.move_steps(direction.sign());
358
359 let interval_ns = executor.current_interval_ns();
361
362 let has_more = executor.advance();
364
365 if has_more {
366 let delay_ns = interval_ns.saturating_sub(2000);
368 if delay_ns > 0 {
369 self.delay.delay_ns(delay_ns);
370 }
371 }
372
373 Ok(!has_more)
374 }
375
376 #[inline]
378 pub fn is_complete(&self) -> bool {
379 self.executor
380 .as_ref()
381 .map(|e| e.is_complete())
382 .unwrap_or(true)
383 }
384
385 #[inline]
387 pub fn progress(&self) -> f32 {
388 self.executor.as_ref().map(|e| e.progress()).unwrap_or(1.0)
389 }
390
391 #[inline]
393 pub fn phase(&self) -> MotionPhase {
394 self.executor
395 .as_ref()
396 .map(|e| e.phase())
397 .unwrap_or(MotionPhase::Complete)
398 }
399
400 pub fn finish(self) -> StepperMotor<STEP, DIR, DELAY, Idle> {
405 StepperMotor {
406 step_pin: self.step_pin,
407 dir_pin: self.dir_pin,
408 delay: self.delay,
409 position: self.position,
410 current_direction: self.current_direction,
411 constraints: self.constraints,
412 name: self.name,
413 invert_direction: self.invert_direction,
414 backlash_steps: self.backlash_steps,
415 executor: None,
416 _state: PhantomData,
417 }
418 }
419
420 pub fn run_to_completion(mut self) -> Result<StepperMotor<STEP, DIR, DELAY, Idle>> {
422 while !self.is_complete() {
423 self.step()?;
424 }
425 Ok(self.finish())
426 }
427}
428
429#[cfg(test)]
430mod tests {
431 }