Stepper Motor Examples - Raspberry Pi Pico

The following short Python programs will demonstrate essential operation of the Raspberry Pi Pico board. These assume one or more binary input or output circuits are externally attached. Each can be run by copying the program into code.py on the CIRCUITPY drive offered by the board. The text can be pasted directly from this page, or each file can be downloaded from the CircuitPython sample code folder on this site.

Related Pages

Sample A4988 Stepper Driver Circuit

../_images/Pico-A4988-example.png

Sample stepper driver circuit using an A4988 module. Note that any two GPIO pins may be used to control the driver. Please be careful with the power wiring, motor voltages can destroy the Pico.

A4988 Stepper Driver Demo

Direct download: a4988_demo.py.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# a4988.py
#
# Raspberry Pi Pico - stepper motor driver support
#
# This module provides a class for controlling an Allegro 4988 stepper motor
# driver.  This device can drive one bipolar stepper motor up to 2A per coil
# using microstepping current control.

# A typical usage requires two digital outputs.  The defaults assumes a Pololu
# A4988 stepper driver has been wired up to the Pico as follows:
#
#   Pico pin 21, GPIO16   -> DIR
#   Pico pin 22, GPIO17   -> STEP
#   any Pico GND          -> GND

# A4988 carrier board: https://www.pololu.com/product/1182

# This implementation bit-bangs the step line and so is limited to about 1000
# steps/sec as a result of CircuitPython execution speed.  This solution is is
# only suitable for low-speed stepper motion.

# Likely a better long-term solution will be to use the RP2040 programmable IO
# peripheral (PIO) to cycle the step output.

################################################################
# CircuitPython module documentation:
# time       https://circuitpython.readthedocs.io/en/latest/shared-bindings/time/index.html
# board      https://circuitpython.readthedocs.io/en/latest/shared-bindings/board/index.html
# digitalio  https://circuitpython.readthedocs.io/en/latest/shared-bindings/digitalio/index.html
#
# Driver lifecycle documentation:
# https://circuitpython.readthedocs.io/en/latest/docs/design_guide.html#lifetime-and-contextmanagers
#
################################################################################
# load standard Python modules
import time

# load the CircuitPython hardware definition module for pin definitions
import board

# load the CircuitPython GPIO support
import digitalio

#--------------------------------------------------------------------------------
class A4988:
    def __init__(self, DIR=board.GP16, STEP=board.GP17):
        """This class represents an A4988 stepper motor driver.  It uses two output pins
        for direction and step control signals."""

        self._dir  = digitalio.DigitalInOut(DIR)
        self._step = digitalio.DigitalInOut(STEP)

        self._dir.direction  = digitalio.Direction.OUTPUT
        self._step.direction = digitalio.Direction.OUTPUT

        self._dir.value = False
        self._step.value = False

    def step(self, forward=True):
        """Emit one step pulse, with an optional direction flag."""
        self._dir.value = forward

        # Create a short pulse on the step pin.  Note that CircuitPython is slow
        # enough that normal execution delay is sufficient without actually
        # sleeping.
        self._step.value = True
        # time.sleep(1e-6)
        self._step.value = False

    def move_sync(self, steps, speed=1000.0):
        """Move the stepper motor the signed number of steps forward or backward at the
        speed specified in steps per second.  N.B. this function will not return
        until the move is done, so it is not compatible with asynchronous event
        loops.
        """

        self._dir.value = (steps >= 0)
        time_per_step = 1.0 / speed
        for count in range(abs(steps)):
            self._step.value = True
            # time.sleep(1e-6)
            self._step.value = False
            time.sleep(time_per_step)

    def deinit(self):
        """Manage resource release as part of object lifecycle."""
        self._dir.deinit()
        self._step.deinit()
        self._dir  = None
        self._step = None

    def __enter__(self):
        return self

    def __exit__(self):
        # Automatically deinitializes the hardware when exiting a context.
        self.deinit()

#--------------------------------------------------------------------------------
# Stepper motor demonstration.

stepper = A4988()
print("Starting stepper motor test.")

speed = 200

while True:
    print(f"Speed: {speed} steps/sec.")
    stepper.move_sync(800, speed)
    time.sleep(1.0)

    stepper.move_sync(-800, speed)
    time.sleep(1.0)

    speed *= 1.2
    if speed > 2000:
        speed = 100