use core::marker::PhantomData;
use embedded_hal::delay::DelayNs;
use embedded_hal::digital::OutputPin;
use crate::config::units::{Degrees, Steps};
use crate::config::MechanicalConstraints;
use crate::error::{Error, MotorError, Result};
use crate::motion::{Direction, MotionExecutor, MotionPhase, MotionProfile};
use super::position::Position;
use super::state::{Idle, MotorState, Moving, StateName};
pub struct StepperMotor<STEP, DIR, DELAY, STATE = Idle>
where
STEP: OutputPin,
DIR: OutputPin,
DELAY: DelayNs,
STATE: MotorState,
{
step_pin: STEP,
dir_pin: DIR,
delay: DELAY,
position: Position,
current_direction: Option<Direction>,
constraints: MechanicalConstraints,
name: heapless::String<32>,
invert_direction: bool,
backlash_steps: i64,
executor: Option<MotionExecutor>,
_state: PhantomData<STATE>,
}
impl<STEP, DIR, DELAY, STATE> StepperMotor<STEP, DIR, DELAY, STATE>
where
STEP: OutputPin,
DIR: OutputPin,
DELAY: DelayNs,
STATE: MotorState + StateName,
{
#[inline]
pub fn name(&self) -> &str {
self.name.as_str()
}
#[inline]
pub fn position_steps(&self) -> Steps {
self.position.steps()
}
#[inline]
pub fn position_degrees(&self) -> Degrees {
self.position.degrees()
}
#[inline]
pub fn constraints(&self) -> &MechanicalConstraints {
&self.constraints
}
#[inline]
pub fn state_name(&self) -> &'static str {
STATE::name()
}
}
impl<STEP, DIR, DELAY> StepperMotor<STEP, DIR, DELAY, Idle>
where
STEP: OutputPin,
DIR: OutputPin,
DELAY: DelayNs,
{
pub(crate) fn new(
step_pin: STEP,
dir_pin: DIR,
delay: DELAY,
constraints: MechanicalConstraints,
name: heapless::String<32>,
invert_direction: bool,
backlash_steps: i64,
) -> Self {
Self {
step_pin,
dir_pin,
delay,
position: Position::new(constraints.steps_per_degree),
current_direction: None,
constraints,
name,
invert_direction,
backlash_steps,
executor: None,
_state: PhantomData,
}
}
pub fn move_to(
mut self,
target: Degrees,
) -> core::result::Result<StepperMotor<STEP, DIR, DELAY, Moving>, (Self, Error)> {
let target_steps = Steps::from_degrees(target, self.constraints.steps_per_degree);
let delta_steps = target_steps.0 - self.position.steps().0;
if delta_steps == 0 {
return Err((self, Error::Motion(crate::error::MotionError::MoveTooShort {
steps: 0,
minimum: 1,
})));
}
let limit_check = self.constraints.limits.as_ref().and_then(|limits| {
if limits.apply(target_steps.0).is_none() {
Some(if delta_steps > 0 {
limits.max_steps
} else {
limits.min_steps
})
} else {
None
}
});
if let Some(limit) = limit_check {
return Err((
self,
Error::Motor(MotorError::LimitExceeded {
position: target_steps.0,
limit,
}),
));
}
let profile = MotionProfile::symmetric_trapezoidal(
delta_steps,
self.constraints.max_velocity_steps_per_sec,
self.constraints.max_acceleration_steps_per_sec2,
);
let direction = profile.direction;
if self.set_direction(direction).is_err() {
return Err((self, Error::Motor(MotorError::PinError)));
}
let executor = MotionExecutor::new(profile);
Ok(StepperMotor {
step_pin: self.step_pin,
dir_pin: self.dir_pin,
delay: self.delay,
position: self.position,
current_direction: self.current_direction,
constraints: self.constraints,
name: self.name,
invert_direction: self.invert_direction,
backlash_steps: self.backlash_steps,
executor: Some(executor),
_state: PhantomData,
})
}
pub fn move_by(
self,
delta: Degrees,
) -> core::result::Result<StepperMotor<STEP, DIR, DELAY, Moving>, (Self, Error)> {
let target = Degrees(self.position.degrees().0 + delta.0);
self.move_to(target)
}
pub fn set_origin(&mut self) {
self.position.set_origin();
}
pub fn set_position(&mut self, degrees: Degrees) {
self.position.set_degrees(degrees);
}
pub fn execute(
self,
trajectory_name: &str,
registry: &crate::trajectory::TrajectoryRegistry,
) -> core::result::Result<Self, (Self, Error)> {
let trajectory = match registry.get(trajectory_name) {
Some(t) => t,
None => {
let mut msg: heapless::String<64> = heapless::String::new();
let _ = msg.push_str("trajectory '");
let _ = msg.push_str(trajectory_name);
let _ = msg.push_str("' not found");
return Err((
self,
Error::Trajectory(crate::error::TrajectoryError::InvalidName(msg)),
));
}
};
if trajectory.motor.as_str() != self.name.as_str() {
let mut msg: heapless::String<64> = heapless::String::new();
let _ = msg.push_str("trajectory '");
let _ = msg.push_str(trajectory_name);
let _ = msg.push_str("' is for motor '");
let _ = msg.push_str(trajectory.motor.as_str());
let _ = msg.push_str("'");
return Err((
self,
Error::Trajectory(crate::error::TrajectoryError::InvalidName(msg)),
));
}
let target = trajectory.target_degrees;
self.move_to_blocking(target)
}
pub fn move_to_blocking(
self,
target: Degrees,
) -> core::result::Result<Self, (Self, Error)> {
match self.move_to(target) {
Ok(moving) => {
match moving.run_to_completion() {
Ok(idle) => Ok(idle),
Err(e) => {
panic!("Motor step error during move: {:?}", e);
}
}
}
Err(e) => Err(e),
}
}
fn set_direction(&mut self, direction: Direction) -> core::result::Result<(), ()> {
if self.current_direction == Some(direction) {
return Ok(());
}
let pin_high = match direction {
Direction::Clockwise => !self.invert_direction,
Direction::CounterClockwise => self.invert_direction,
};
if pin_high {
self.dir_pin.set_high().map_err(|_| ())?;
} else {
self.dir_pin.set_low().map_err(|_| ())?;
}
self.current_direction = Some(direction);
Ok(())
}
}
impl<STEP, DIR, DELAY> StepperMotor<STEP, DIR, DELAY, Moving>
where
STEP: OutputPin,
DIR: OutputPin,
DELAY: DelayNs,
{
pub fn step(&mut self) -> Result<bool> {
let executor = self.executor.as_mut().ok_or(MotorError::NotInitialized)?;
if executor.is_complete() {
return Ok(true);
}
self.step_pin.set_high().map_err(|_| MotorError::PinError)?;
self.delay.delay_us(2);
self.step_pin.set_low().map_err(|_| MotorError::PinError)?;
let direction = executor.profile().direction;
self.position.move_steps(direction.sign());
let interval_ns = executor.current_interval_ns();
let has_more = executor.advance();
if has_more {
let delay_ns = interval_ns.saturating_sub(2000);
if delay_ns > 0 {
self.delay.delay_ns(delay_ns);
}
}
Ok(!has_more)
}
#[inline]
pub fn is_complete(&self) -> bool {
self.executor
.as_ref()
.map(|e| e.is_complete())
.unwrap_or(true)
}
#[inline]
pub fn progress(&self) -> f32 {
self.executor.as_ref().map(|e| e.progress()).unwrap_or(1.0)
}
#[inline]
pub fn phase(&self) -> MotionPhase {
self.executor
.as_ref()
.map(|e| e.phase())
.unwrap_or(MotionPhase::Complete)
}
pub fn finish(self) -> StepperMotor<STEP, DIR, DELAY, Idle> {
StepperMotor {
step_pin: self.step_pin,
dir_pin: self.dir_pin,
delay: self.delay,
position: self.position,
current_direction: self.current_direction,
constraints: self.constraints,
name: self.name,
invert_direction: self.invert_direction,
backlash_steps: self.backlash_steps,
executor: None,
_state: PhantomData,
}
}
pub fn run_to_completion(mut self) -> Result<StepperMotor<STEP, DIR, DELAY, Idle>> {
while !self.is_complete() {
self.step()?;
}
Ok(self.finish())
}
}
#[cfg(test)]
mod tests {
}