stepper-motion

Crates.iostepper-motion
lib.rsstepper-motion
version0.1.1
created_at2025-11-27 13:02:10.605215+00
updated_at2025-11-27 14:31:09.744006+00
descriptionConfiguration-driven stepper motor motion control with embedded-hal 1.0 support
homepage
repositoryhttps://github.com/FrenchPOC/stepper-motion-rs
max_upload_size
id1953709
size195,719
axoulc (axoulc)

documentation

README

stepper-motion

A configuration-driven stepper motor motion control library for Rust, designed for embedded systems with no_std support.

Crates.io Documentation License

Features

  • 📁 Configuration-Driven: Define motor parameters and trajectories in TOML files
  • 🔧 Embedded-Ready: Full no_std support with embedded-hal 1.0 integration
  • ⚡ Asymmetric Motion Profiles: Independent acceleration and deceleration rates
  • 🎯 Named Trajectories: Execute movements by name with registry-based lookup
  • 📐 Type-Safe Units: Physical quantities with compile-time unit checking
  • 🛡️ Mechanical Constraints: Automatic validation against hardware limits
  • 📍 Absolute Position Tracking: i64 step-based position management
  • 🔄 Backlash Compensation: Configurable mechanical play compensation

Installation

Add to your Cargo.toml:

[dependencies]
stepper-motion = "0.1"

Feature Flags

Feature Default Description
std Standard library support, TOML file loading
alloc Heap allocation without full std
defmt defmt formatting for embedded debugging
async Async executor support (planned)

For no_std environments:

[dependencies]
stepper-motion = { version = "0.1", default-features = false, features = ["alloc"] }

Quick Start

1. Create a Configuration File

# motion.toml
[motors.pan_axis]
name = "Pan Axis"
steps_per_revolution = 200
microsteps = 16
gear_ratio = 4.0
max_velocity_deg_per_sec = 180.0
max_acceleration_deg_per_sec2 = 360.0
invert_direction = false
backlash_compensation_deg = 0.5

[motors.pan_axis.limits]
min_degrees = -180.0
max_degrees = 180.0
policy = "reject"

[trajectories.home]
motor = "pan_axis"
target_degrees = 0.0
velocity_percent = 50

[trajectories.quarter_turn]
motor = "pan_axis"
target_degrees = 90.0
velocity_percent = 100
acceleration_deg_per_sec2 = 360.0
deceleration_deg_per_sec2 = 180.0  # Asymmetric: slower decel

2. Load and Use in Your Application

use stepper_motion::{
    SystemConfig, 
    motor::StepperMotorBuilder,
    trajectory::TrajectoryRegistry,
    config::units::Degrees,
};

// Load configuration (requires `std` feature)
fn main() -> Result<(), stepper_motion::Error> {
    // Parse TOML configuration
    let config: SystemConfig = toml::from_str(include_str!("motion.toml"))?;
    
    // Get motor configuration
    let motor_config = config.motor("pan_axis").expect("Motor not found");
    
    // Your hardware pins (implement embedded-hal 1.0 traits)
    let step_pin = MyStepPin::new();
    let dir_pin = MyDirPin::new();
    let delay = MyDelay::new();
    
    // Build motor from configuration
    let motor = StepperMotorBuilder::new()
        .step_pin(step_pin)
        .dir_pin(dir_pin)
        .delay(delay)
        .from_motor_config(motor_config)
        .build()?;
    
    // Load trajectory registry for named lookups
    let registry = TrajectoryRegistry::from_config(&config);
    
    // Get trajectory by name
    let trajectory = registry.get_or_error("quarter_turn")?;
    println!("Target: {}°", trajectory.target_degrees.0);
    
    Ok(())
}

3. Manual Motor Control (Builder Pattern)

use stepper_motion::{
    motor::StepperMotorBuilder,
    config::units::{Degrees, DegreesPerSec, DegreesPerSecSquared, Microsteps},
};

// Create motor with explicit parameters
let motor = StepperMotorBuilder::new()
    .name("demo_motor")
    .step_pin(step_pin)
    .dir_pin(dir_pin)
    .delay(delay)
    .steps_per_revolution(200)
    .microsteps(Microsteps::SIXTEENTH)
    .gear_ratio(1.0)
    .max_velocity(DegreesPerSec(360.0))
    .max_acceleration(DegreesPerSecSquared(720.0))
    .backlash_steps(10)  // Optional: backlash compensation
    .build()?;

println!("Motor: {}", motor.name());
println!("Position: {} steps ({} degrees)", 
    motor.position_steps().0, 
    motor.position_degrees().0);

// Move to absolute position
let moving_motor = motor.move_to(Degrees(90.0))?;

// Execute step-by-step
while moving_motor.is_moving() {
    moving_motor.step()?;
}

let idle_motor = moving_motor.finish();

Architecture

┌─────────────────────────────────────────────────────┐
│                    stepper-motion                   │
├─────────────────────────────────────────────────────┤
│  config/          │ TOML parsing, validation        │
│  ├── motor.rs     │ MotorConfig, limits             │
│  ├── trajectory.rs│ TrajectoryConfig (asymmetric)   │
│  ├── mechanical.rs│ MechanicalConstraints           │
│  ├── limits.rs    │ SoftLimits, LimitPolicy         │
│  └── units.rs     │ Degrees, Steps, Microsteps      │
├─────────────────────────────────────────────────────┤
│  motor/           │ Hardware abstraction            │
│  ├── driver.rs    │ StepperMotor<STEP,DIR,DELAY,S>  │
│  ├── builder.rs   │ Builder pattern construction    │
│  ├── state.rs     │ Type-state: Idle, Moving, etc.  │
│  └── position.rs  │ Position tracking (i64 steps)   │
├─────────────────────────────────────────────────────┤
│  motion/          │ Motion planning                 │
│  ├── profile.rs   │ MotionProfile (trapezoidal)     │
│  └── executor.rs  │ Step pulse generation           │
├─────────────────────────────────────────────────────┤
│  trajectory/      │ Named trajectory management     │
│  └── registry.rs  │ TrajectoryRegistry              │
└─────────────────────────────────────────────────────┘

Motion Profiles

Symmetric Trapezoidal

velocity
    ▲
max ┤    ┌───────┐
    │   /         \
    │  /           \
    └─/─────────────\────► time
      accel  cruise  decel
      (same rate for both)

Asymmetric Trapezoidal

velocity
    ▲
max ┤    ┌───────┐
    │   /│       │\
    │  / │       │ \
    └─/──┴───────┴──\───► time
     fast         slow
     accel        decel

Set different rates in TOML:

[trajectories.gentle_stop]
motor = "pan_axis"
target_degrees = 90.0
velocity_percent = 100
acceleration_deg_per_sec2 = 720.0   # Fast acceleration
deceleration_deg_per_sec2 = 180.0   # Gentle deceleration

Or using percent-based values (relative to motor max):

[trajectories.smooth_move]
motor = "pan_axis"
target_degrees = 45.0
velocity_percent = 75      # 75% of motor's max velocity
acceleration_percent = 100 # 100% of motor's max acceleration

Mechanical Constraints

Define hardware limits to prevent damage:

[motors.servo]
name = "Servo Axis"
steps_per_revolution = 200
microsteps = 32
gear_ratio = 5.0  # 5:1 reduction gearbox
max_velocity_deg_per_sec = 360.0
max_acceleration_deg_per_sec2 = 720.0
backlash_compensation_deg = 0.5  # Compensate 0.5° backlash on reversal

[motors.servo.limits]
min_degrees = -360.0
max_degrees = 360.0
policy = "reject"  # or "clamp"

Limit Policies

  • reject: Return error if target position exceeds limits
  • clamp: Automatically constrain target to nearest limit

Unit Conversions

The library automatically handles conversions:

let constraints = motor.constraints();

// Configuration values → internal steps
println!("Steps/revolution: {}", constraints.steps_per_revolution);
println!("Steps/degree: {:.4}", constraints.steps_per_degree);
println!("Max velocity: {:.0} steps/s", constraints.max_velocity_steps_per_sec);

Examples

Run the included examples:

# Basic motor control with mechanical constraints demonstration
cargo run --example basic_motor

# Configuration-driven operation with named trajectories
cargo run --example config_driven

# Multi-motor system demonstration
cargo run --example multi_motor

Type-State Safety

The motor uses Rust's type system to enforce valid state transitions:

// Motor starts in Idle state
let motor: StepperMotor<_, _, _, Idle> = builder.build()?;

// move_to() transitions to Moving state
let moving: StepperMotor<_, _, _, Moving> = motor.move_to(Degrees(90.0))?;

// Can only call step() or finish() on Moving motor
while moving.is_moving() {
    moving.step()?;
}

// finish() transitions back to Idle
let motor: StepperMotor<_, _, _, Idle> = moving.finish();

Minimum Supported Rust Version (MSRV)

Rust 1.70.0 or later (required for embedded-hal 1.0).

no_std Usage

For embedded systems without standard library:

#![no_std]
#![no_main]

use stepper_motion::{SystemConfig, motor::StepperMotorBuilder};

// Embed configuration at compile time
const CONFIG_TOML: &str = include_str!("../motion.toml");

fn setup() {
    // Parse embedded configuration
    let config: SystemConfig = toml::from_str(CONFIG_TOML).unwrap();
    
    let motor_config = config.motor("servo").unwrap();
    
    // Build motor with your embedded-hal pins
    let motor = StepperMotorBuilder::new()
        .step_pin(gpioa.pa0.into_push_pull_output())
        .dir_pin(gpioa.pa1.into_push_pull_output())
        .delay(timer.delay_us())
        .from_motor_config(motor_config)
        .build()
        .unwrap();
}

Contributing

Contributions are welcome! Please read the CHANGELOG for version history.

Development

# Run all tests (46 tests: 25 unit + 21 integration)
cargo test --all-features

# Check no_std compatibility
cargo build --no-default-features
cargo build --no-default-features --features alloc

# Run clippy
cargo clippy --all-features

# Format code
cargo fmt

# Run examples
cargo run --example basic_motor
cargo run --example config_driven
cargo run --example multi_motor

License

Licensed under either of:

at your option.

Commit count: 0

cargo fmt