High-performance stepper motor driver for MicroPython with smooth acceleration/deceleration profiles.
Copyright (c) 2025 Ned Konz ned@metamagix.tech
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.
- 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
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)
- MicroPython source code (clone at
../micropythonrelative to this repo) - ARM GCC toolchain for cross-compilation
- Make
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 cleanBuild artifacts are created in build-PYBV11/ or build-NUCLEO_H743ZI2/ directories.
# Verbose build output
BOARD=PYBV11 ./build.sh V=1
# Debug build with symbols
BOARD=PYBV11 ./build.sh DEBUG=1After 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.dfuNucleo H743:
# Copy to mounted ST-LINK drive, or use:
st-flash write build-NUCLEO_H743ZI2/firmware.bin 0x8000000from 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()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())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)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())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
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
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.
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"
)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.
- 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)
Tests are located in flash_fs/stepper/:
# On the device REPL:
import stepper.test_motion_profile
import stepper.async_accelstepper_testEnable debug output:
motor = Stepper(..., debug=True, debug_pin=Pin.board.X1)
motor.debug("current state") # Print comprehensive state infoThe debug pin will pulse when motion completion flags are set.
Contributions are welcome! Areas for improvement:
- Additional board support
- Microstepping configuration
- Home/limit switch integration
- Motion queue/buffer system
This project is licensed under the MIT License - see the LICENSE file for details.
Copyright (c) 2025 Ned Konz ned@metamagix.tech
- Austin, David. "Generate stepper-motor speed profiles in real time"
- Eiderman, Aryeh. "Real Time Stepper Motor Linear Ramping Just by Addition and Multiplication" I just ran across this paper but it was David Austin's paper that provided my algorithm.
- MicroPython Documentation
- STM32 Timer Documentation