This guide assumes that you have a Raspberry Pi connected to the Internet with CircuitPython installed.

Install Python Libraries 

Your Raspberry Pi has Python already.

Visit the Motor HAT setup page to setup your HAT and make sure that works first!

Then, installing the library to control the Pi Motor Hat kit is fairly simple:

From a terminal, enter the following to install the CircuitPython_MotorKit library

sudo pip3 install adafruit-circuitpython-motorkit

To communicate with Adafruit IO, you'll need to install the Adafruit IO Python library:

sudo pip3 install adafruit-io

Code Configuration

Before you run the code, you'll need to configure it for your Adafruit IO account.

Open whichever text editor you'd like from the Raspberry Pi's command line (this example uses the nano editor) :

nano adafruit_io_steppers.py

To edit the file, set the ADAFRUIT_IO_KEY variable to the secret Adafruit IO key you saved earlier.

Set the ADAFRUIT_IO_USERNAME to your Adafruit IO username.

Then, save the file (CTRL+X, followed by Enter)

"""
'adafruit_io_steppers.py'
==================================
Example of using CircuitPython and
Adafruit IO to control two stepper
motors over the internet.

Dependencies:
    - Adafruit_Blinka
        (https://github.com/adafruit/Adafruit_Blinka)
    - Adafruit_CircuitPython_MotorKit
        (https://github.com/adafruit/Adafruit_CircuitPython_MotorKit)
    - Adafruit_IO_Python
        (https://github.com/adafruit/Adafruit_IO_Python)
"""
# Import Python Libraries
import time
import atexit
import threading

# import Adafruit IO REST client.
from Adafruit_IO import Client, RequestError

# Import CircuitPython Libraries
from adafruit_motor import stepper as STEPPER
from adafruit_motorkit import MotorKit

# Set to your Adafruit IO key.
# Remember, your key is a secret,
# so make sure not to publish it when you publish this code!
ADAFRUIT_IO_KEY = 'YOUR_IO_KEY'

# Set to your Adafruit IO username.
# (go to https://accounts.adafruit.com to find your username)
ADAFRUIT_IO_USERNAME = 'YOUR_IO_USERNAME'

# Create an instance of the REST client.
aio = Client(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY)

# Delay between checking for `go` button press on Adafruit IO, in seconds
ADAFRUIT_IO_DELAY = 1

# Stepper 1 Adafruit IO Feeds
feed_step_1_steps = aio.feeds('stepper1steps')
feed_step_1_direction = aio.feeds('stepper1direction')
feed_step_1_step_size = aio.feeds('stepper1stepsize')
# Stepper 2 Adafruit Feeds
feed_step_2_steps = aio.feeds('stepper2steps')
feed_step_2_direction = aio.feeds('stepper2direction')
feed_step_2_step_size = aio.feeds('stepper2stepsize')
# Steppers start button
feed_steppers_status = aio.feeds('stepperstart')

# create a default object, no changes to I2C address or frequency
kit = MotorKit()

# create empty threads (these will hold the stepper 1 and 2 threads)
# pylint: disable=bad-thread-instantiation
st1 = threading.Thread()
st2 = threading.Thread()

# recommended for auto-disabling motors on shutdown!
def turnOffMotors():
    kit.stepper1.release()
    kit.stepper2.release()

atexit.register(turnOffMotors)
stepstyles = [STEPPER.SINGLE, STEPPER.DOUBLE, STEPPER.INTERLEAVE, STEPPER.MICROSTEP]

def stepper_worker(stepper, numsteps, direction, stepper_name, style, show_steps=False):
    print("Steppin!")
    stepper_steps = numsteps
    print(stepper_steps)
    for _ in range(numsteps):
        stepper.onestep(direction=direction, style=style)
        if show_steps: # print out the steps and send to IO stepper slider
            stepper_steps -= 1
            print('Steps: ', stepper_steps)
            aio.send(feed_step_1_steps.key, stepper_steps)
            time.sleep(0.5)
    print("{0} Done Stepping".format(stepper_name))
    # Reset slider on dashboard
    if stepper_name == "Stepper 1":
        aio.send(feed_step_1_steps.key, 0)
    elif stepper_name == "Stepper 2":
        aio.send(feed_step_2_steps.key, 0)


while True:
    try: # attempt to poll the stepper status feed
        print('checking for GO button press...')
        stepper_start = aio.receive(feed_steppers_status.key)
    except RequestError.ThrottlingError:
        print('Exceeded the limit of Adafruit IO requests, delaying 30 seconds...')
        time.sleep(30)

    # Stepper 1
    if not st1.isAlive() and int(stepper_start.value):
        stepper_1_steps = aio.receive(feed_step_1_steps.key)
        stepper_1_steps = int(stepper_1_steps.value)
        if stepper_1_steps > 0: # stepper slider is set
            # Get stepper configuration from io feeds
            stepper_1_direction = aio.receive(feed_step_1_direction.key)
            stepper_1_step_size = aio.receive(feed_step_1_step_size.key)
            print('Stepper 1 Configuration')
            print('\t%d steps' % stepper_1_steps)
            print('\tStep Size: ', stepper_1_step_size.value)
            print('\tStepper Direction: ', stepper_1_direction.value)

            # Set Stepper Direction
            if stepper_1_direction.value == 'Forward':
                move_dir = STEPPER.FORWARD
            elif stepper_1_direction.value == 'Backward':
                move_dir = STEPPER.BACKWARD
            # Stepper 1 Thread
            st1 = threading.Thread(target=stepper_worker, args=(kit.stepper1,
                                                                stepper_1_steps,
                                                                move_dir,
                                                                "Stepper 1",
                                                                stepstyles[STEPPER.SINGLE],))
            st1.start()

    # Stepper 2
    if not st2.isAlive() and int(stepper_start.value):
        stepper_2_steps = aio.receive(feed_step_2_steps.key)
        stepper_2_steps = int(stepper_2_steps.value)
        if stepper_2_steps > 0: # stepper slider is set
            # Get stepper configuration from io feeds
            stepper_2_direction = aio.receive(feed_step_2_direction.key)
            stepper_2_step_size = aio.receive(feed_step_2_step_size.key)
            print('Stepper 2 Configuration')
            print('\t%d steps' % stepper_2_steps)
            print('\tStep Size: ', stepper_2_step_size.value)
            print('\tStepper Direction: ', stepper_2_direction.value)
            # Set Stepper Direction
            if stepper_2_direction.value == 'Forward':
                move_dir = STEPPER.FORWARD
            elif stepper_2_direction.value == 'Backward':
                move_dir = STEPPER.BACKWARD
            # Stepper 2 Thread
            st2 = threading.Thread(target=stepper_worker, args=(kit.stepper2,
                                                                stepper_2_steps,
                                                                move_dir,
                                                                "Stepper 2",
                                                                stepstyles[STEPPER.SINGLE],))
            st2.start()

    # delay polling the `go button` to avoid Adafruit IO timeouts
    print('Delaying for {0} seconds...'.format(ADAFRUIT_IO_DELAY))
    time.sleep(ADAFRUIT_IO_DELAY)

Code Usage

From the Raspberry Pi terminal, run the code by typing the following:

python3 Adafruit_IO_Stepper_Control/code.py

and press enter

The terminal should output that it's checking the GO button, and delaying (to avoid sending too many fetch requests to Adafruit IO)

[email protected]:~ $ python3 dual_steppers.py
checking for GO button press...
Delaying for 1 seconds...
checking for GO button press...
Delaying for 1 seconds...

Next, navigate over to the Adafruit IO Dashboard to configure the first stepper.

Set the amount of steps by dragging the slider. The slider was configured to step in 10 step increments. You can re-configure to step more or less it by editing the block element.

Set the stepper direction by toggling the switch. The stepper can either move forward (default) or backward.

Set the step style by pressing one of the four momentary push-buttons on the dashboard. 

There are four essential types of steps you can use with your Motor HAT. All four kinds will work with any unipolar or bipolar stepper motor.

  1. Single Steps - this is the simplest type of stepping, and uses the least power. It uses a single coil to 'hold' the motor in place, as seen in the animated GIF above
  2. Double Steps - this is also fairly simple, except instead of a single coil, it has two coils on at once. For example, instead of just coil #1 on, you would have coil #1 and #2 on at once. This uses more power (approx 2x) but is stronger than single stepping (by maybe 25%)
  3. Interleaved Steps - this is a mix of Single and Double stepping, where we use single steps interleaved with double. It has a little more strength than single stepping, and about 50% more power. What's nice about this style is that it makes your motor appear to have 2x as many steps, for a smoother transition between steps
  4. Microstepping - this is where we use a mix of single stepping with PWM to slowly transition between steps. It's slower than single stepping but has much higher precision. We recommend 8 microstepping which multiplies the # of steps your stepper motor has by 8.

After the first stepper is configured, play with the controls for the second stepper. 

When you're ready, press the green Go Button to send the presets to the Raspberry Pi and move the stepper motors. 

The Raspberry Pi should configure and step the first stepper, then the second (if you configured it).

The terminal will also display the values it receives from Adafruit IO as it's stepping.

Stepper 1 Configuration
	70 steps
	Step Size:  STEPPER.DOUBLE
	Stepper Direction:  Forward
Steppin!
Stepper 1 Done Stepping

Stepper 2 Configuration
	60 steps
	Step Size:  STEPPER.DOUBLE
	Stepper Direction:  Backward
Steppin!
Stepper 2 Done Stepping

When it's done stepping, the stepper driver's code resets the step amount feed to zero. The slider, connected to the step amount feed, resets too.

Code Overview

Adafruit IO can receive up to 120 data points a minute, the code aims to minimize the amount of data polling which occurs in the while True loop.

First, it checks the stepper status feed, which is linked to the Go push-button on the dashboard. If the code exceeds the amount of requests it can send, it will wait thirty seconds for Adafruit IO to clear the timeout.

try: # attempt to poll the stepper status feed
	print('checking for GO button press...')
	stepper_start = aio.receive(feed_steppers_status.key)
except ThrottlingError:
	print('Exceeded the limit of Adafruit IO requests, delaying 30 seconds...')
	time.sleep(30)

The code for controlling both stepper motors is similar - but doesn't happen simultaneously (code for the first stepper occurs first). 

First, the code checks if the stepper thread is alive (if the stepper is moving or not) and if the stepper status feed (stepper_start) was selected (the push-button was pressed). Then, it fetches the value from the Stepper Slider:

if not st1.isAlive() and int(stepper_start.value):
	stepper_1_steps = aio.receive(feed_step_1_steps.key)
	stepper_1_steps = int(stepper_1_steps.value)

If the value from the stepper slider is set, the code will fetch the stepper's direction and step size from Adafruit IO. Then, it'll call stepper_worker and pass in the arguments from the configuration.

        if stepper_1_steps > 0: # stepper slider is set
            # Get stepper configuration from io feeds
            stepper_1_direction = aio.receive(feed_step_1_direction.key)
            stepper_1_step_size = aio.receive(feed_step_1_step_size.key)
            print('Stepper 1 Configuration')
            print('\t%d steps' % stepper_1_steps)
            print('\tStep Size: ', stepper_1_step_size.value)
            print('\tStepper Direction: ', stepper_1_direction.value)
            # Set Stepper Direction
            if stepper_1_direction.value == 'Forward':
                move_dir = STEPPER.FORWARD
            elif stepper_1_direction.value == 'Backward':
                move_dir = STEPPER.BACKWARD
            # Stepper 1 Thread
            st1 = threading.Thread(target=stepper_worker, args=(kit.stepper1,
                                                                stepper_1_steps,
                                                                move_dir,
                                                                "Stepper 1",
                                                                stepstyles[STEPPER.SINGLE],))
            st1.start()

This guide was first published on Feb 18, 2019. It was last updated on Feb 18, 2019.

This page (Python Code) was last updated on Dec 08, 2021.

Text editor powered by tinymce.