A Python class to move the stepper motor

To properly control the stepper motor from the Raspberry Pi we need a class to represent it. This is one of the most direct ways of understanding object oriented programming (OOP): from the class you make an “object” and the object represents and controls an object in the real world (the stepper motor).

The class lets us remember (and control) all the information we need:

  • the pins the motor is connected on;
  • the speed of the motor;
  • the angle the motor is currently pointing to.

The motor seems to have 4096 positions (steps) it can point to.  These don’t correspond exactly to degrees. The class takes an angle we want the motor to point to, converts that into a step position and works out how many steps to turn it by and in what direction to get there.

Actually, it doesn’t move to the step position that is closest to the angle, it chooses step positions that are divisible by 8 because the control sequence has 8 steps in it and I didn’t want to bother starting or stopping in the middle of the sequence.

#!/usr/bin/env python

# This code is written by Stephen C Phillips.
# It is in the public domain, so you can do what you like with it
# but a link to http://scphillips.com would be nice.

# It works on the Raspberry Pi computer with the standard Debian Wheezy OS and
# the 28BJY-48 stepper motor with ULN2003 control board.

from time import sleep
import RPi.GPIO as GPIO

class Motor(object):
    def __init__(self, pins):
        self.P1 = pins[0]
        self.P2 = pins[1]
        self.P3 = pins[2]
        self.P4 = pins[3]
        self.deg_per_step = 5.625 / 64
        self.steps_per_rev = int(360 / self.deg_per_step)  # 4096
        self.step_angle = 0  # Assume the way it is pointing is zero degrees
        for p in pins:
            GPIO.setup(p, GPIO.OUT)
            GPIO.output(p, 0)

    def _set_rpm(self, rpm):
        """Set the turn speed in RPM."""
        self._rpm = rpm
        # T is the amount of time to stop between signals
        self._T = (60.0 / rpm) / self.steps_per_rev

    # This means you can set "rpm" as if it is an attribute and
    # behind the scenes it sets the _T attribute
    rpm = property(lambda self: self._rpm, _set_rpm)

    def move_to(self, angle):
        """Take the shortest route to a particular angle (degrees)."""
        # Make sure there is a 1:1 mapping between angle and stepper angle
        target_step_angle = 8 * (int(angle / self.deg_per_step) / 8)
        steps = target_step_angle - self.step_angle
        steps = (steps % self.steps_per_rev)
        if steps > self.steps_per_rev / 2:
            steps -= self.steps_per_rev
            print "moving " + `steps` + " steps"
            self._move_acw(-steps / 8)
        else:
            print "moving " + `steps` + " steps"
            self._move_cw(steps / 8)
        self.step_angle = target_step_angle

    def _move_acw(self, big_steps):
        GPIO.output(self.P1, 0)
        GPIO.output(self.P2, 0)
        GPIO.output(self.P3, 0)
        GPIO.output(self.P4, 0)
        for i in range(big_steps):
            GPIO.output(self.P1, 0)
            sleep(self._T)
            GPIO.output(self.P3, 1)
            sleep(self._T)
            GPIO.output(self.P4, 0)
            sleep(self._T)
            GPIO.output(self.P2, 1)
            sleep(self._T)
            GPIO.output(self.P3, 0)
            sleep(self._T)
            GPIO.output(self.P1, 1)
            sleep(self._T)
            GPIO.output(self.P2, 0)
            sleep(self._T)
            GPIO.output(self.P4, 1)
            sleep(self._T)

    def _move_cw(self, big_steps):
        GPIO.output(self.P1, 0)
        GPIO.output(self.P2, 0)
        GPIO.output(self.P3, 0)
        GPIO.output(self.P4, 0)
        for i in range(big_steps):
            GPIO.output(self.P3, 0)
            sleep(self._T)
            GPIO.output(self.P1, 1)
            sleep(self._T)
            GPIO.output(self.P4, 0)
            sleep(self._T)
            GPIO.output(self.P2, 1)
            sleep(self._T)
            GPIO.output(self.P1, 0)
            sleep(self._T)
            GPIO.output(self.P3, 1)
            sleep(self._T)
            GPIO.output(self.P2, 0)
            sleep(self._T)
            GPIO.output(self.P4, 1)
            sleep(self._T)

if __name__ == "__main__":
    GPIO.setmode(GPIO.BOARD)
    m = Motor([18,22,24,26])
    m.rpm = 5
    print "Pause in seconds: " + `m._T`
    m.move_to(90)
    sleep(1)
    m.move_to(0)
    sleep(1)
    m.move_to(-90)
    sleep(1)
    m.move_to(-180)
    sleep(1)
    m.move_to(0)
    GPIO.cleanup()

If you save it as “move.py” you can run it using “sudo python move.py”. It will then move the motor round by 90 degrees, pause for 1 second, move it back again and so on according to the instructions at the end of the program. Alternatively, you can import the file and use the class from another program which is what I’ll be doing soon…

  • Pingback: A simple Python webserver | TODO: change title

  • Michael Horne

    Thank you for this – worked first time with my motor :-)

    • scp93ch

      Great!

  • Mehdi HAMIDA

    This post seems great.
    I got just 1 question :

    did you tryed to use an IO Extender such as the MCP23017 ?
    I’m using it and I don’t know how to adapt your code in order to make it work.

    Any help please?

    • scp93ch

      Sorry, I don’t even know what an IO Extender is!

  • emcee

    which stepper motor did you use? THis by any chance 28BYJ-48 28BYJ48
    Stepper Motor with ULN2003 Driver Board

    • scp93ch

      Yes, that’s the one.

  • sreenath v

    It did worked for ULN2003 perfectly without any code modification. When I tried 4 step sequence (by modifying code), motor did not move, but it was struggling to move…

    http://www.techiesinfo.com/robotics

    • scp93ch

      Interesting. It would be good to have all the working sequences in the code so if you get a different sequence working then let me know.

  • Imran Kasani

    Hi,

    I tried to compile the above code and I keep on getting this error message
    File “smotor.py”, line 13
    def __init__(self, pins):

    Syntax Error: Invalid Syntax

    I can not seem to figure out why it is is doing this. Could be be due to the version of python I am using?

    • scp93ch

      I expect it is because there is some issue caused by copying the script and space getting messed up. Python requires precise indentation using white-space. Try the code here https://raw.github.com/scp93ch/where-clock/master/Stepper.py

      • Imran Kasani

        Hi,
        Thanks for that. The code in the link worked exactly, I kept on getting lot of syntax errors which I thought was strange.

        Thanks for your greatly appreciated help and prompt response Now I can seriously get on with rest of my project.

        Thanks.

  • Chris

    Hi Stephen,

    Thank you for this… it is the most comprehensive script / guidance that I have
    found yet and (the relatively finite control it offers) is perfect for my
    application.

    I was hoping to pick your brains regarding the script
    though?

    The script as is does not generate the kind of torque from
    the motor that I was hoping for.

    I have found that after a bit of research on the www that
    people have managed to generate more torque through adapting the timings/ the pin
    configuration used; even to a point that they were unable to stop the drive
    shaft with their fingers.

    Any advice on this? Plausibility? How this could best be
    achieved in terms of your script?

    I am obviously aware that there is a limitation to the
    motors physical capability and that a solution would be to ‘overclock’ the
    motor by increasing the voltage to it but I am hoping to avoid this.

    Again thank you… this is a great blog to have come across…. Especially
    since my next project was to use Python to setup a small webserver on my Pi!!!

    • scp93ch

      Hi Chris, thanks for the kind comments.

      There are various control sequences that can be used. I coded up a the one from the datasheet but I always intended to extend it to include other sequences.

      There is a good document about stepper motors I’ve just found : http://www.solarbotics.net/library/pdflib/pdf/motorbas.pdf

      According to that, the sequence I have coded is “half step drive” and it shows a sequence for “full step drive” which should have more torque and also go faster. You’d have to change the code so that it did:
      Step 1: P4 off, P2 on
      Step 2: P1 off, P3 on
      Step 3: P2 off, P4 on
      Step 4: P3 off, P1 on

      See if you can change it and let me know. Otherwise I’ll get round to having a go myself when I’m at home.

      • scp93ch

        I’ve added the full step drive pattern to the latest version of this class. You can find it on GitHub: https://github.com/scp93ch/where-clock/blob/master/Stepper.py
        The latest version of the simple webserver is also there (I need to write it up in another post really).

      • Chris

        Stephen,

        Gave it a go last night after work myself. It was actually incredibly easy to adapt your current script to operate a ‘full step drive’ sequence. The amendments were made as per your suggestion above for both the clockwise and anti-clockwise rotations (obviously appropriately altered to reverse the direction). It has had really promising results for both the torque and the speed that the motor can generate.

        Just an FYI for anyone else interested in this; I also tried operating the motor from a 5v power adapter rather than the 5v pin of the Raspberry Pi. This also made a difference in terms of the torque/ speed that could be generated from the motor….. I would assume that the Pi provided less current to the motor.

        Thank you for your help!!! Always appreciated!!!

        • scp93ch

          Thanks for sharing the info Chris. We’re all learning here!

  • Matt Shepherd

    Thanks for the helpful script/blog

    I’ve used it to successfully control two motors to make a 360 panoramic image..

    http://360-degree-pi.tumblr.com/

    I’ve adapted it to allow for the gear on one of my lego turntables (My hack might not be the best/most efficient code but it works!)

    I’ve credited you in the blog and python code,
    Thanks again.
    Matt

    • http://scphillips.com/ Stephen C Phillips

      That’s great Matt!

  • George Pilch

    Hi Stephen, I would also like to say that your code and it’s explanation is very much appreciated.
    May I ask however, the code assumes and caters for control of steppers using the gpio pins. A lot of stepper driver modules that are available today require only step and direction. Do you have any sample python code to control such modules as these.
    Many thanks

    • http://scphillips.com/ Stephen C Phillips

      Sorry, I haven’t tried any other motors yet. Have you looked in the forums in the main RPi website?

  • jonatasdp

    Hi Stephen! it works for me! I put it to work in a rotative timelapse :)

    Thank you!

    https://github.com/jonatas/motolapse

    • http://scphillips.com/ Stephen C Phillips

      Great! I’m glad it was useful to you.