1use embedded_hal::delay::DelayNs;
4use embedded_hal::digital::OutputPin;
5use pamoja_core::Actuator;
6
7use super::{Direction, Drive, Position, Sequencer};
8use crate::error::DriverError;
9
10pub const DEFAULT_STEP_MICROS: u32 = 2_000;
15
16pub const DEFAULT_PULSE_MICROS: u32 = 10;
23
24fn direction_of(steps: i32) -> Direction {
25 if steps < 0 {
26 Direction::Backward
27 } else {
28 Direction::Forward
29 }
30}
31
32#[derive(Debug)]
61pub struct FourWire<A, B, C, Dp, D> {
62 coils: (A, B, C, Dp),
63 delay: D,
64 sequencer: Sequencer,
65 position: Position,
66 step_micros: u32,
67}
68
69impl<A, B, C, Dp, D> FourWire<A, B, C, Dp, D> {
70 pub fn new(coils: (A, B, C, Dp), delay: D, drive: Drive) -> Self {
82 FourWire {
83 coils,
84 delay,
85 sequencer: Sequencer::new(drive),
86 position: Position::new(),
87 step_micros: DEFAULT_STEP_MICROS,
88 }
89 }
90
91 pub fn with_step_interval(mut self, micros: u32) -> Self {
101 self.step_micros = micros;
102 self
103 }
104
105 pub fn position(&self) -> i32 {
107 self.position.steps()
108 }
109
110 pub fn drive(&self) -> Drive {
112 self.sequencer.drive()
113 }
114
115 pub fn release(self) -> ((A, B, C, Dp), D) {
121 (self.coils, self.delay)
122 }
123}
124
125impl<A, B, C, Dp, D, E> FourWire<A, B, C, Dp, D>
126where
127 A: OutputPin<Error = E>,
128 B: OutputPin<Error = E>,
129 C: OutputPin<Error = E>,
130 Dp: OutputPin<Error = E>,
131 D: DelayNs,
132{
133 pub fn step(&mut self, direction: Direction) -> Result<(), DriverError<E>> {
143 let coils = self.sequencer.step(direction);
144 self.energize(coils)?;
145 self.position.step(direction);
146 self.delay.delay_us(self.step_micros);
147 Ok(())
148 }
149
150 pub fn steps(&mut self, count: i32) -> Result<(), DriverError<E>> {
161 let direction = direction_of(count);
162 for _ in 0..count.unsigned_abs() {
163 self.step(direction)?;
164 }
165 Ok(())
166 }
167
168 pub fn idle(&mut self) -> Result<(), DriverError<E>> {
174 self.energize(0)
175 }
176
177 fn energize(&mut self, coils: u8) -> Result<(), DriverError<E>> {
178 let (a, b, c, d) = &mut self.coils;
179 a.set_state((coils & 0b1000 != 0).into())
180 .map_err(DriverError::Bus)?;
181 b.set_state((coils & 0b0100 != 0).into())
182 .map_err(DriverError::Bus)?;
183 c.set_state((coils & 0b0010 != 0).into())
184 .map_err(DriverError::Bus)?;
185 d.set_state((coils & 0b0001 != 0).into())
186 .map_err(DriverError::Bus)
187 }
188}
189
190impl<A, B, C, Dp, D, E> Actuator for FourWire<A, B, C, Dp, D>
191where
192 A: OutputPin<Error = E> + Send,
193 B: OutputPin<Error = E> + Send,
194 C: OutputPin<Error = E> + Send,
195 Dp: OutputPin<Error = E> + Send,
196 D: DelayNs + Send,
197 E: core::fmt::Debug,
198{
199 type Command = i32;
200
201 async fn apply(&mut self, count: i32) -> pamoja_core::Result<()> {
202 self.steps(count).map_err(pamoja_core::Error::from)
203 }
204}
205
206#[derive(Debug)]
233pub struct StepDir<S, R, D> {
234 step: S,
235 direction: R,
236 delay: D,
237 position: Position,
238 pulse_micros: u32,
239 step_micros: u32,
240}
241
242impl<S, R, D> StepDir<S, R, D> {
243 pub fn new(step: S, direction: R, delay: D) -> Self {
256 StepDir {
257 step,
258 direction,
259 delay,
260 position: Position::new(),
261 pulse_micros: DEFAULT_PULSE_MICROS,
262 step_micros: DEFAULT_STEP_MICROS,
263 }
264 }
265
266 pub fn with_pulse_width(mut self, micros: u32) -> Self {
277 self.pulse_micros = micros;
278 self
279 }
280
281 pub fn with_step_interval(mut self, micros: u32) -> Self {
291 self.step_micros = micros;
292 self
293 }
294
295 pub fn position(&self) -> i32 {
297 self.position.steps()
298 }
299
300 pub fn release(self) -> ((S, R), D) {
306 ((self.step, self.direction), self.delay)
307 }
308}
309
310impl<S, R, D, E> StepDir<S, R, D>
311where
312 S: OutputPin<Error = E>,
313 R: OutputPin<Error = E>,
314 D: DelayNs,
315{
316 pub fn step(&mut self, direction: Direction) -> Result<(), DriverError<E>> {
326 self.direction
327 .set_state((direction == Direction::Forward).into())
328 .map_err(DriverError::Bus)?;
329 self.delay.delay_us(self.pulse_micros);
330 self.step.set_high().map_err(DriverError::Bus)?;
331 self.delay.delay_us(self.pulse_micros);
332 self.step.set_low().map_err(DriverError::Bus)?;
333 self.position.step(direction);
334 self.delay.delay_us(self.step_micros);
335 Ok(())
336 }
337
338 pub fn steps(&mut self, count: i32) -> Result<(), DriverError<E>> {
349 let direction = direction_of(count);
350 for _ in 0..count.unsigned_abs() {
351 self.step(direction)?;
352 }
353 Ok(())
354 }
355}
356
357impl<S, R, D, E> Actuator for StepDir<S, R, D>
358where
359 S: OutputPin<Error = E> + Send,
360 R: OutputPin<Error = E> + Send,
361 D: DelayNs + Send,
362 E: core::fmt::Debug,
363{
364 type Command = i32;
365
366 async fn apply(&mut self, count: i32) -> pamoja_core::Result<()> {
367 self.steps(count).map_err(pamoja_core::Error::from)
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374 use alloc::vec::Vec;
375 use core::cell::RefCell;
376 use core::convert::Infallible;
377 use embedded_hal::digital::{ErrorType, PinState};
378 use pamoja_hal::digital::PinState::{High, Low};
379 use pamoja_hal::script::{block_on, DelayLog, PinScript};
380
381 #[derive(Debug, PartialEq)]
382 enum Event {
383 Direction(PinState),
384 Step(PinState),
385 Wait(u32),
386 }
387
388 struct Line<'a> {
389 log: &'a RefCell<Vec<Event>>,
390 event: fn(PinState) -> Event,
391 }
392
393 impl ErrorType for Line<'_> {
394 type Error = Infallible;
395 }
396
397 impl OutputPin for Line<'_> {
398 fn set_low(&mut self) -> Result<(), Infallible> {
399 self.log.borrow_mut().push((self.event)(PinState::Low));
400 Ok(())
401 }
402
403 fn set_high(&mut self) -> Result<(), Infallible> {
404 self.log.borrow_mut().push((self.event)(PinState::High));
405 Ok(())
406 }
407 }
408
409 struct Timeline<'a>(&'a RefCell<Vec<Event>>);
410
411 impl DelayNs for Timeline<'_> {
412 fn delay_ns(&mut self, ns: u32) {
413 self.0.borrow_mut().push(Event::Wait(ns));
414 }
415 }
416
417 fn pins() -> (PinScript, PinScript, PinScript, PinScript) {
418 (
419 PinScript::new([]),
420 PinScript::new([]),
421 PinScript::new([]),
422 PinScript::new([]),
423 )
424 }
425
426 #[test]
427 fn full_steps_energize_the_datasheet_pairs_in_order() {
428 let mut motor =
429 FourWire::new(pins(), DelayLog::new(), Drive::FullStep).with_step_interval(1_500);
430 motor.steps(4).unwrap();
431 assert_eq!(motor.position(), 4);
432 assert_eq!(motor.drive(), Drive::FullStep);
433 let ((a, b, c, d), delay) = motor.release();
434 assert_eq!(a.driven(), [Low, Low, High, High]);
435 assert_eq!(b.driven(), [High, Low, Low, High]);
436 assert_eq!(c.driven(), [High, High, Low, Low]);
437 assert_eq!(d.driven(), [Low, High, High, Low]);
438 assert_eq!(delay.waits_ns(), [1_500_000; 4]);
439 }
440
441 #[test]
442 fn backward_steps_walk_the_pattern_the_other_way_and_count_down() {
443 let mut motor = FourWire::new(pins(), DelayLog::new(), Drive::Wave);
444 motor.steps(-2).unwrap();
445 assert_eq!(motor.position(), -2);
446 motor.idle().unwrap();
447 let ((a, _, _, d), _) = motor.release();
448 assert_eq!(a.driven(), [Low, Low, Low]);
449 assert_eq!(
450 d.driven(),
451 [High, Low, Low],
452 "wave drive backward starts at coil D"
453 );
454 }
455
456 #[test]
457 fn a_four_wire_motor_is_an_actuator_taking_signed_steps() {
458 let mut motor = FourWire::new(pins(), DelayLog::new(), Drive::HalfStep);
459 block_on(motor.apply(3)).unwrap();
460 block_on(motor.apply(-1)).unwrap();
461 assert_eq!(motor.position(), 2);
462 }
463
464 #[test]
465 fn step_dir_pulses_the_step_line_with_the_direction_set_first() {
466 let mut motor = StepDir::new(PinScript::new([]), PinScript::new([]), DelayLog::new())
467 .with_pulse_width(5)
468 .with_step_interval(1_000);
469 motor.steps(2).unwrap();
470 motor.steps(-1).unwrap();
471 assert_eq!(motor.position(), 1);
472 let ((step, direction), delay) = motor.release();
473 assert_eq!(direction.driven(), [High, High, Low]);
474 assert_eq!(step.driven(), [High, Low, High, Low, High, Low]);
475 assert_eq!(delay.waits_ns()[..3], [5_000, 5_000, 1_000_000]);
476 }
477
478 #[test]
479 fn step_dir_settles_the_direction_before_the_rising_edge_as_the_timing_diagram_asks() {
480 let log = RefCell::new(Vec::new());
481 let step = Line {
482 log: &log,
483 event: Event::Step,
484 };
485 let direction = Line {
486 log: &log,
487 event: Event::Direction,
488 };
489 let mut motor = StepDir::new(step, direction, Timeline(&log))
490 .with_pulse_width(2)
491 .with_step_interval(1_000);
492 motor.step(Direction::Backward).unwrap();
493 assert_eq!(
494 *log.borrow(),
495 [
496 Event::Direction(PinState::Low),
497 Event::Wait(2_000),
498 Event::Step(PinState::High),
499 Event::Wait(2_000),
500 Event::Step(PinState::Low),
501 Event::Wait(1_000_000),
502 ],
503 "the A4988 wants 200 ns of setup and 1 us high; the DRV8825 650 ns and 1.9 us"
504 );
505 }
506
507 #[test]
508 fn a_step_dir_motor_is_an_actuator_taking_signed_steps() {
509 let mut motor = StepDir::new(PinScript::new([]), PinScript::new([]), DelayLog::new());
510 block_on(motor.apply(-4)).unwrap();
511 assert_eq!(motor.position(), -4);
512 }
513}