Skip to content

bikeNomad/stepper-driver

Repository files navigation

MicroPython Stepper Motor Driver

High-performance stepper motor driver for MicroPython with smooth acceleration/deceleration profiles.

Copyright (c) 2025 Ned Konz ned@metamagix.tech

Current Status

This is old code that was once working. Because of interest from several people, I'm publishing it here. However, I haven't recently tested it, so no guarantees of it working. I do know that it compiles with MicroPython v1.26.1 though.

Features

  • Smooth Motion Control: Implements acceleration/deceleration algorithm for smooth stepper motor motion
  • Hybrid Python/C Implementation: Critical ISR code in C for maximum performance, high-level API in Python
  • Async/Await Support: Built-in asyncio integration for coordinated multi-motor control
  • Trapezoidal Motion Profiles: Automatic generation of acceleration, constant speed, and deceleration phases
  • Multi-Axis Coordination: Synchronize multiple motors to reach target speeds simultaneously
  • Configurable Speed & Acceleration: Runtime adjustable parameters within hardware limits
  • Position Tracking: Accurate step counting and target position support

Supported Hardware

I've only tested it with these boards, but it's likely that it would work with other STM32 boards that have the pyb library.

  • PyBoard v1.1 (STM32F405)
  • STM32 Nucleo-144 H743ZI (STM32H743)

Prerequisites

  • MicroPython source code (clone at ../micropython relative to this repo)
  • ARM GCC toolchain for cross-compilation
  • Make

Building

Build firmware for your target board:

# Build for PyBoard v1.1
make build-pyb

# Build for Nucleo H743
make build-nucleo

# Build both targets and create zip archive
make zip

# Clean builds
make clean

Build artifacts are created in build-PYBV11/ or build-NUCLEO_H743ZI2/ directories.

Advanced Build Options

# Verbose build output
BOARD=PYBV11 ./build.sh V=1

# Debug build with symbols
BOARD=PYBV11 ./build.sh DEBUG=1

Flashing Firmware

After building, flash the firmware to your board:

PyBoard v1.1:

# Put board in DFU mode (hold BOOT button, press RESET)
dfu-util -a 0 -D build-PYBV11/firmware.dfu

Nucleo H743:

# Copy to mounted ST-LINK drive, or use:
st-flash write build-NUCLEO_H743ZI2/firmware.bin 0x8000000

Usage

Basic Single Motor Control

from stepper.accelstepper import Stepper
from pyb import Pin

# Initialize stepper motor
motor = Stepper(
    timer_num=2,
    step_pin=Pin.board.Y1,
    direction_pin=Pin.board.Y2,
    name="motor1"
)

# Configure motion parameters
motor.max_speed(5000)      # steps/sec
motor.acceleration(1000)   # steps/sec²

# Move to speed asynchronously
motor.speed(3000)          # Start accelerating to 3000 steps/sec
motor.wait_until_motion_done()  # Block until at target speed

# Stop
motor.speed(0)
motor.wait_until_motion_done()

Async Multi-Motor Coordination

import asyncio
from stepper.multi_stepper import MultiStepper

async def coordinated_motion():
    # Initialize multi-stepper controller
    ms = MultiStepper(debug=False)
    ms.acceleration(500)

    # All motors reach target speeds simultaneously
    ms.speeds([1000, 2000, 1500])  # speeds for motor 1, 2, 3
    await ms.wait_for_acceleration()

    # Stop all motors
    ms.stop()

asyncio.run(coordinated_motion())

Motion Profiles

from stepper.accelstepper import Stepper
from stepper.motion_profile import MotionProfile

motor = Stepper(...)
motor.max_speed(5000)
motor.acceleration(1000)

# Create a trapezoidal motion profile
profile = MotionProfile()
profile.position_move(
    acceleration=1000,
    max_speed=4000,
    initial_position=0,
    target_position=50000  # Move 50,000 steps
)

# Execute the profile
motor.run_motion_profile(profile)

Async Motion with Multiple Motors

import asyncio
from stepper.accelstepper import Stepper
from stepper.motion_profile import Motion

async def move_two_motors():
    motor1 = Stepper(...)
    motor2 = Stepper(...)

    # Create motion objects
    m1 = Motion()
    m1._target_speed = 2000
    m1._acceleration = 800

    m2 = Motion()
    m2._target_speed = 3000
    m2._acceleration = 1200

    # Run both motors concurrently
    await asyncio.gather(
        motor1.async_run_motion(m1),
        motor2.async_run_motion(m2)
    )

asyncio.run(move_two_motors())

Project Structure

stepper-driver/
├── Makefile                    # Build system entry point
├── build.sh                    # Main build script
├── boards/                     # Board-specific configurations
│   ├── PYBV11/                # PyBoard v1.1 config
│   └── NUCLEO_H743ZI2/        # Nucleo H743 config
├── c_extension/               # C extension modules
│   └── accelstepper_isr/      # ISR implementation in C
│       ├── accelhelper_main.c # C ISR callbacks
│       └── micropython.mk     # Module build config
├── flash_fs/                  # Python code deployed to flash
│   └── stepper/
│       ├── accelstepper.py    # Main Stepper class
│       ├── motion_profile.py  # Motion planning
│       ├── multi_stepper.py   # Multi-axis coordination
│       └── motor_config.py    # Hardware pin configuration
└── utilities/
    └── period_generator.py    # Timer period calculation tool

How It Works

Acceleration Algorithm

The driver implements the algorithm from the paper "Generate stepper-motor speed profiles in real time" by David Austin, which allows calculation of step timing in real-time without pre-computation.

Key equations:

  • First step delay: c0 = 0.676 × √(2/α) × f_timer
  • Subsequent delays: c_n = c_(n-1) - (2×c_(n-1))/(4n+1)

Where:

  • α = acceleration (steps/sec²)
  • f_timer = timer frequency (Hz)
  • c_n = timer period for step n

Hybrid Python/C Architecture

Python Layer (accelstepper.py):

  • Configuration and initialization
  • Motion planning and profile generation
  • Async/await interface
  • High-level control flow

C Extension Layer (accelhelper_main.c):

  • Interrupt service routines (ISRs)
  • Direct hardware manipulation (GPIO, timers)
  • Step timing calculations
  • Minimal latency for real-time performance

Shared State: A uctypes.struct allows Python and C to share state variables safely across contexts.

Configuration

Edit flash_fs/stepper/motor_config.py to configure:

  • Timer assignments
  • GPIO pin mappings
  • Motor enable pins
  • Debug pins
MOTOR_CONFIG = dict(
    timer_num=2,
    step_pin=Pin.cpu.B8,
    direction_pin=Pin.cpu.C5,
    name="motor1"
)

Timer Selection

Read the micropython documentation about pyb.Timer for more information about timer selection. Some of these timers may be used for other purposes, so avoid these other usages.

  • Prefer 32-bit timers for maximum speed range. On the STM32H743 and STM32F405 these are timers 2 and 5.

Limitations

  • Only STM32 boards
  • Maximum speed limited by timer clock frequency and prescaler settings
  • Minimum acceleration constrained by maximum timer period
  • Direction reversals require deceleration to zero first
  • ISR callbacks must execute quickly (no memory allocation)

Development

Running Tests

Tests are located in flash_fs/stepper/:

# On the device REPL:
import stepper.test_motion_profile
import stepper.async_accelstepper_test

Debugging

Enable debug output:

motor = Stepper(..., debug=True, debug_pin=Pin.board.X1)
motor.debug("current state")  # Print comprehensive state info

The debug pin will pulse when motion completion flags are set.

Contributing

Contributions are welcome! Areas for improvement:

  • Additional board support
  • Microstepping configuration
  • Home/limit switch integration
  • Motion queue/buffer system

License

This project is licensed under the MIT License - see the LICENSE file for details.

Copyright (c) 2025 Ned Konz ned@metamagix.tech

References

About

C Extension module for driving stepper motors.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors