Using RPi.GPIO to drive stepper motors


29 posts   Page 1 of 2   1, 2
by MadCow42 » Sat Jul 07, 2012 3:43 am
It's running! I have my Pi now hooked up to a breadboarded 2-motor unipolar stepper driver, and it's running seamlessly! (Thanks Ben Croston for RPi.GPIO!)

The layout is a bit ugly, and I was fighting some EMI problems, but that seems to be sorted out now. I'll be soldering this up into something more final over the next few days. It has one limit switch input, two sets of 4-coil outputs for unipolar motors, and one extra GPIO for flashy-bleepy-lights if you so desire.

Video: http://www.cazabon.com/temp/Raspberry%2 ... Driver.mp4

I'm sure there are better/easier ways to do this, but I'm driving two 8-bit shift registers (only using the first 4 bits on each) through a separate clock pin, data pin, and latch pin for each register (6 GPIO's total). The other two GPIO's are for the limit switch and status LED. I'm currently maxed out about 600 steps per second, but I'm also going to try wiringPy to see if that helps (some testing done by someone else here showed it might be up to 6x faster - but I'm sure I'd hit other bottlenecks too).

Fun fun fun! :)

Kevin.
Posts: 89
Joined: Sun Jul 01, 2012 12:48 am
by Baystray » Sat Jul 07, 2012 8:09 am
Why yes, the plans would be nice. :-)
Posts: 8
Joined: Tue May 22, 2012 1:24 pm
by MadCow42 » Sat Jul 07, 2012 3:52 pm
Baystray wrote:Why yes, the plans would be nice. :-)


I'll see what I can do - I have to draw it out myself anyway to find the best way to put it on a soldered PCB. Technically I have the IC's in place to drive 4 steppers too... but I'm only using the first 4 bits on each chip for speed purposes. 2 more motors are an easy addition if needed.

Kevin.
Posts: 89
Joined: Sun Jul 01, 2012 12:48 am
by MadCow42 » Sat Jul 07, 2012 7:25 pm
http://www.cazabon.com/temp/driver_sche ... azabon.pdf

2-motor unipolar step motor driver, controlled by Raspberry Pi.

Well... here it is in all it's analog glory. :) Sorry for the mess, but I hope it's understandable.

a couple notes:
- I used IRF830 MOSFETs, but you can use others depending on your power needs
- The power supply for the MOSFETs should share a ground with the RPi (pin 6), and +5v sources (if you're using a separate source that is).
- You could add 8 additional MOSFETs to drive up to four motors in total if you need - just connect them to pins 4-7 on the 8-bit shift registers. I've only done one per IC for speed reasons.
- The limit switch and simple LED circuits can be changed as necessary for your other needs of course - but there were 2 standard GPIO's open so I used them. :)
- You can add LEDs in parallel to the stepper motor coil circuits (with appropriate resistors) so you can visually see the stepping pattern too.
- I chose +5V / 1A per phase steppers, and am using a single +5V supply to drive them and the IC's... but you could drive the IC's off of the RPi's +5V circuit if you wanted to use a higher voltage for the steppers.

Good luck - no warranty, use at your own risk, etc! :)

When I get the software cleaned up I can post some of that too if needed.

Kevin.
Posts: 89
Joined: Sun Jul 01, 2012 12:48 am
by Baystray » Mon Jul 09, 2012 12:47 am
Excellent and I appreciate the effort. As it turns out, I haven't gotten the time to work with mine since my last post, but I plan to wire this up once I can pick up a couple components that I'm short on. I will hopefully get to wire everything up this weekend for a test run!
Posts: 8
Joined: Tue May 22, 2012 1:24 pm
by MadCow42 » Mon Jul 09, 2012 3:00 am
Baystray wrote:Excellent and I appreciate the effort. As it turns out, I haven't gotten the time to work with mine since my last post, but I plan to wire this up once I can pick up a couple components that I'm short on. I will hopefully get to wire everything up this weekend for a test run!


Have fun! Make sure to do a sanity check before plugging it in... I've been as careful as I can to ensure its accurate to my working breadboard as possible, but can't guarantee it. I'll be converting my breadboard to a soldered board this week too and will reply if I find anything concerning.

I have some Python classes for driving this thing too, and they should be clean in the next couple days too if you're interested... But maybe thats the fun part. :). Let me know.

Kevin
Posts: 89
Joined: Sun Jul 01, 2012 12:48 am
by lynnfredwoods » Tue Jul 10, 2012 12:41 pm
Very nice work on the steppers. I don't suppose there's any chance for the python code?
Lynn
Posts: 1
Joined: Tue Jul 10, 2012 12:38 pm
by MadCow42 » Wed Jul 11, 2012 3:19 pm
lynnfredwoods wrote:Very nice work on the steppers. I don't suppose there's any chance for the python code?
Lynn


Yes of course. I'm just building up my soldered board, and having a couple minor issues... so I want to figure that out first to ensure I don't lead anyone down the wrong path. Hopefully tonight or tomorrow night I'll get all that done.

Kevin.
Posts: 89
Joined: Sun Jul 01, 2012 12:48 am
by MattHawkinsUK » Wed Jul 11, 2012 10:32 pm
I've just added a page to my site with details on my initial stepper motor experiments.

http://www.raspberrypi-spy.co.uk/2012/07/stepper-motor-control-in-python/

It includes some Python code. The stepper motor I use is a 5V 28BYJ-48 which is readily available from eBay from a number of sellers. I got two from from 4tronix_uk for £13. No connection to me but I was impressed with their lightning fast delivery.

Just need to get the motor to actually move/rotate something now.
My Raspberry Pi blog and home of the BerryClip Add-on board : http://www.raspberrypi-spy.co.uk/
Follow me on Google+, Facebook, Pinterest and Twitter (@RPiSpy)
User avatar
Posts: 532
Joined: Tue Jan 10, 2012 8:48 pm
Location: UK
by MadCow42 » Thu Jul 12, 2012 1:36 am
lynnfredwoods wrote:Very nice work on the steppers. I don't suppose there's any chance for the python code?
Lynn


Here's the code so far - it's fairly complete, but has a few things left to do (commented in the code). It's certainly usable as-is. Enjoy, and let me know if you have any comments! It's currently written for use with wiringpi.py, but can easily work with RPi.GPIO as well (also see comments).

Kevin.

(in case the paste below doesn't work, file is also here: http://www.cazabon.com/temp/KCazabon_StepperDriver.py


Code: Select all

# Stepper Driver Module
# Copyright (c) 2012 Kevin Cazabon
# kevin@cazabon.com

# This is intended for driving multiple stepper motors simultaneously through two 4-bit shift registers (or only using 4 bits on each of two 8-bit registers)
# Contact Kevin Cazabon for schematics on the driver board

import time
import math

import wiringpi
wiringpi.wiringPiSetup()

# NOTE:  if you want to use RPi.GPIO instead of wiringpi, just modify the GPIO_OUT and GPIO_IN classes below... all else should be fine after that.
#       However, this was only tested with v. 0.2 of RPi.GPIO, so double check it!
#
#
#from RPi.GPIO import *  # ideally this should just be tacked at the bottom of the GPIO module itself
#from RPi.GPIO import _GetValidId, _ExportedIds

#class GPIO_OUT:
#    """To attain higher write performance on the RPi GPIO (up to about 5x), keep the device open as a file
#instead of opening/closing it each time you write to it.  Contributed by Kevin Cazabon"""
#   
#    def __init__(self, channel):
#        self.channel = channel
#        setup(self.channel, OUT)
#       
#        self.id = _GetValidId(self.channel)
#
#        if self.id not in _ExportedIds or _ExportedIds[self.id] != OUT:
#            raise WrongDirectionException
#       
#        self.f = open('/sys/class/gpio/gpio%s/value'%self.id, 'w')
#
#        # try to guarantee that we don't leave the file open by mistake
#        atexit.register(self.__del__)
#
#    def __del__(self):
#        if not self.f.closed:
#            self.f.flush()
#            self.f.close()
#           
#    def output(self, value):
#        self.f.write('1' if value else '0')
#        self.f.flush()
#
#    def close(self):
#        self.__del__()

#class GPIO_IN:
#    def __init__(self, channel):
#        self.channel = channel
#       
#        setup(self.channel, IN)
#
#    def __del__(self):
#        pass
#           
#    def input(self):
#        # High = True, Low = False
#        if input(self.channel) == 1:
#            return True
#        else:
#            return False
#
#    def close(self):
#        self.__del__()


# a few constants
INITIAL_RAMP_TIMEPERSTEP = 0.1
FORWARD = 1
REVERSE = -1


class GPIO_OUT:
    """To attain higher write performance on the RPi GPIO (up to about 10x), keep the device open as a file
instead of opening/closing it each time you write to it.  Contributed by Kevin Cazabon"""
   
    def __init__(self, channel, postSleep = None):
        self.channel = channel
        self.postSleep = postSleep
       
        wiringpi.pinMode(self.channel, 1)
       
    def __del__(self):
        pass
           
    def output(self, value):
        wiringpi.digitalWrite(self.channel, 1 if value else 0)
        if self.postSleep:
            time.sleep(self.postSleep)

    def close(self):
        self.__del__()

class GPIO_IN:
    def __init__(self, channel):
        self.channel = channel
       
        wiringpi.pinMode(self.channel, 2)

    def __del__(self):
        pass
           
    def input(self):
        # High = True, Low = False
        if wiringpi.digitalRead(self.channel) == 1:
            return True
        else:
            return False

    def close(self):
        self.__del__()


# FIXME - things left to do for this class:
#
#   - fix the timing for steps/second - it's close, but there's loop/etc. overhead that results in fewer steps than intended.
#   - some sort of calibration of that may be needed if I can't figure out a better way.
#
class stepper:
    def __init__(self, dataPin, clockPin, latchPin, limitPin, startLocked = False, latchSleep = None):
        self.d = GPIO_OUT(dataPin)
        self.c = GPIO_OUT(clockPin)
        self.l = GPIO_OUT(latchPin)
        self.limit = GPIO_IN(limitPin)
        self.latchSleep = latchSleep
       
        self.stepPattern = [True, True, False, False]
        self.halfStepPattern = [[True, False, False, False], [True, False, False, True], [False, False, False, True], [False, False, True, True], [False, False, True, False], [False, True, True, False], [False, True, False, False], [True, True, False, False]]
       
        self.currentPhase = [False, False, False, False]    # not used yet...
       
        self.stepCount = 0
       
        if startLocked:
            self.lock()
   
    def lock(self):
        self.step(FORWARD, 1)

    def deEnergize(self, motor1 = True, motor2 = True):
        self.d.output(False)
        self.c.output(False)
        self.l.output(False)
       
        # shift 8 blank bits onto the shift register, then latch it.
        for i in range(8):
            self.c.output(True)
            self.c.output(False)
       
        self.l.output(True)
        self.l.output(False)

    def step(self, steps = 1, stepsPerSecond = None, direction = FORWARD, overrideLimit = False):
        # forward can be done by simply cascading one bit at a time through the register - much faster!
       
        if (direction != FORWARD and steps > 0) or (direction == FORWARD and steps < 0):
            return self.stepReverse(abs(steps), stepsPerSecond, overrideLimit)
           
        self.c.output(False)
        self.l.output(False)
       
        for i in range(int(self.stepCount + 1), int(self.stepCount + 1) + steps, 1):
            start = time.time()
            if overrideLimit:
                print "*** Limit Switch Over-Ridden! Careful! ***"
            else:
                if not self.limit.input():
                    print "*** Limit Sensed - cannot move further ***"
                    self.stepCount = i
                    return -1
                           
            self.c.output(False)
            self.d.output(self.stepPattern[i%len(self.stepPattern)])
            self.c.output(True)

            # data should be loaded... latch it to the output!
            self.l.output(True)
            self.l.output(False)
           
            # ensure we're going the right-ish speed
            duration = time.time() - start
            if stepsPerSecond > 0:
                if duration < 1.0/float(stepsPerSecond):
                    time.sleep((1.0/float(stepsPerSecond)) - duration)

        # we must have completed all steps without hitting the limit switch
        self.stepCount = i
        return 0
   
    def complexStep(self, pattern, stepCounter, overrideLimit):
        # load all pattern bits into the shift register before latching
        # this only does one step - you'll have to call it multiple times
        if overrideLimit:
            print "*** Limit Switch Over-Ridden! Careful! ***"
        else:
            if not self.limit.input():
                print "*** Limit Sensed - cannot move further ***"
                return -1

        self.c.output(False)
        self.l.output(False)
       
        for i in range(len(pattern)):
            self.d.output(pattern[i])
            self.c.output(True)
            self.c.output(False)
       
        self.l.output(True)
       
        self.stepCount = self.stepCount + stepCounter
       
        return 0
   
    def stepReverse(self, steps = 1, stepsPerSecond = None, overrideLimit = False):
        for i in range(abs(steps)):
            start = time.time()
            # load the four data points into motor1
            stepNumber = int(self.stepCount - 1)%len(self.stepPattern)
           
            # stepNumber will be counting down.  Shift the step pattern left accordingly
            pattern = self.stepPattern[stepNumber:] + self.stepPattern[:stepNumber]
               
            if self.complexStep(pattern, -1, overrideLimit) != 0:
                # Limit sensed during movement, bail.
                return -1
           
            # ensure we're going the right-ish speed
            duration = time.time() - start
            if stepsPerSecond > 0:
                if duration < 1.0/float(stepsPerSecond):
                    time.sleep((1.0/float(stepsPerSecond)) - duration)
           
        return 0

    def halfStep(self, steps = 1, stepsPerSecond = None, direction = FORWARD, overrideLimit = False):
        if (direction != FORWARD and steps > 0) or (direction == FORWARD and steps < 0):
            return self.halfStepReverse(abs(steps))
       
        for i in range(steps):
            start = time.time()
            stepNumber = int(((float(self.stepCount) + 0.5)*2.0) % len(self.halfStepPattern))
            pattern = self.halfStepPattern[stepNumber]
            if self.complexStep(pattern, 0.5, overrideLimit) != 0:
                # Limit sensed during movement, bail.
                return -1
           
            # ensure we're going the right-ish speed
            duration = time.time() - start
            if stepsPerSecond > 0:
                if duration < 1.0/float(stepsPerSecond):
                    time.sleep((1.0/float(stepsPerSecond)) - duration)
           
        return 0

    def halfStepReverse(self, steps = 1, stepsPerSecond = None, overrideLimit = False):
        for i in range(abs(steps)):
            start = time.time()
            stepNumber = int(((float(self.stepCount) - 0.5)*2.0) % len(self.halfStepPattern))
            pattern = self.halfStepPattern[stepNumber]
            if self.complexStep(pattern, -0.5, overrideLimit) != 0:
                # Limit sensed during movement, bail.
                return -1
           
            # ensure we're going the right-ish speed
            duration = time.time() - start
            if stepsPerSecond > 0:
                if duration < 1.0/float(stepsPerSecond):
                    time.sleep((1.0/float(stepsPerSecond)) - duration)
           
        return 0

    def stepTime(self, startTime, normalTime, rampSteps, thisStepNumber):
        # this needs some work for constant acceleration - but it's a start.
        x = float(thisStepNumber)/float(rampSteps)
        x2 = 1 - math.sin(x * math.pi/2.0)
       
        timeThisStep =  normalTime + (x2 * (startTime - normalTime))
           
        return timeThisStep
   
    def ramp(self, function, startSpeed, finalSpeed, rampSteps):
        if startSpeed == 0:
            startSpeed = 50
        if finalSpeed == 0:
            finalSpeed = 50
        for i in range(abs(rampSteps)):
            start = time.time()
            if startSpeed > finalSpeed:
                thisStepTime = self.stepTime(1.0/float(startSpeed), 1.0/float(finalSpeed), rampSteps, i)
            else:
                thisStepTime = self.stepTime(1.0/float(finalSpeed), 1.0/float(startSpeed), rampSteps, rampSteps - i)
               
            if function(1, None) != 0:
                # Limit sensed during movement, bail.
                return -1
           
            # ensure we're going the right-ish speed
            # do that here because of the extra overhead of the math for stepTime
            duration = time.time() - start
            if thisStepTime > 0:
                if duration < thisStepTime:
                    time.sleep(thisStepTime - duration)
           

       
if __name__ == "__main__":
    # a whole series of testing...
    print "#" *80
    print "Stepper Motor Driver Test - using WiringPi setups"
    print "#" *80
    print

    # define your stepper and the outputs/limit switch to use
    s = stepper(6, 4, 5, 0)
    #s2 = stepper(1,0,4,7)
   
   
    print"Motor 1 100 steps / 0.01"
    for i in range(100):
        s.step(1)
        time.sleep(0.01)
   
    time.sleep(2)
    s.deEnergize()
    time.sleep(2)
   
    #print "Motor 2 100 steps / 0.01"
    #for i in range(100):
    #    s2.step(1)
    #    time.sleep(0.01)   
   
    #time.sleep(2)
    #s2.deEnergize()
    #time.sleep(2)
   
    print"Motor 1 100 steps reverse / 0.01"
    for i in range(100):
        s.stepReverse(1)
        time.sleep(0.01)
   
    time.sleep(2)
    s.deEnergize()
    time.sleep(2)
   
    #print "Motor 2 100 steps reverse / 0.01"
    #for i in range(100):
    #    s2.stepReverse(1)
    #    time.sleep(0.01)   
   
    #time.sleep(2)
    #s2.deEnergize()
    #time.sleep(2) 

    print"Motor 1 100 half-steps / 0.01"
    for i in range(100):
        s.halfStep(1)
        time.sleep(0.01)
   
    time.sleep(2)
    s.deEnergize()
    time.sleep(2)
   
    #print "Motor 2 100 half-steps / 0.01"
    #for i in range(100):
    #    s2.halfStep(1)
    #    time.sleep(0.01)   
   
    #time.sleep(2)
    #s2.deEnergize()
    #time.sleep(2)
   
   
    print"Motor 1 100 half-steps reverse / 0.01"
    for i in range(100):
        s.halfStepReverse(1)
        time.sleep(0.01)
   
    time.sleep(2)
    s.deEnergize()
    time.sleep(2)
   
    #print "Motor 2 100 half-steps reverse / 0.01"
    #for i in range(100):
    #    s2.halfStepReverse(1)
    #    time.sleep(0.01)   
   
    #time.sleep(2)
    #s2.deEnergize()
    #time.sleep(2)
   
    print "*"*80
    print "ramping up to max speed forward"
    print "*"*80
    print
   
    print "100/sec"
    s.step(100,100)
    print "150/sec"
    s.step(150,150)
    print "200/sec"
    s.step(200,200)
    print "250/sec"
    s.step(250,250)
    print "300/sec"
    s.step(300,300)
    print "350/sec"
    s.step(350,350)
    print "400/sec"
    s.step(400,400)
    print "450/sec"
    s.step(450,450)
    print "500/sec"
    s.step(500,500)
    print "550/sec"
    s.step(550,550)
    print "600/sec"
    s.step(600,600)
    print "650/sec"
    s.step(650,650)
    print "700/sec"
    s.step(700,700)
    print "750/sec"
    s.step(750,750)
    print "800/sec"
    s.step(800,800)
   
    s.deEnergize()
    time.sleep(2)
   
    print "trying reverse (complex step)"
    print "100/sec"
    s.stepReverse(100,100)
    print "150/sec"
    s.stepReverse(150,150)
    print "200/sec"
    s.stepReverse(200,200)
    print "250/sec"
    s.stepReverse(250,250)
    print "300/sec"
    s.stepReverse(300,300)
    print "350/sec"
    s.stepReverse(350,350)
    print "400/sec"
    s.stepReverse(400,400)
    print "450/sec"
    s.stepReverse(450,450)
    print "500/sec"
    s.stepReverse(500,500)
   
    s.deEnergize()
    time.sleep(2)
   
    print "trying half-step (complex step)"
    print "100/sec"
    s.halfStep(100,100)
    print "150/sec"
    s.halfStep(150,150)
    print "200/sec"
    s.halfStep(200,200)
    print "250/sec"
    s.halfStep(250,250)
    print "300/sec"
    s.halfStep(300,300)
    print "350/sec"
    s.halfStep(350,350)
    print "400/sec"
    s.halfStep(400,400)
    print "450/sec"
    s.halfStep(450,450)
    print "500/sec"
    s.halfStep(500,500)

    s.deEnergize()
    time.sleep(2)
   
    print "testing step timing accuracy over 1000 steps in multiple modes"
    start = time.time()
    s.step(1000, 200)
    print "1000 steps @ 200/sec, actually: %s /sec" %(1000.0/((time.time()-start)))
   
    start = time.time()
    s.step(1000, 400)
    print "1000 steps @ 400/sec, actually: %s /sec" %(1000.0/((time.time()-start)))
   
    start = time.time()
    s.stepReverse(1000, 200)
    print "1000 steps reverse @ 200/sec, actually: %s /sec" %(1000.0/((time.time()-start)))
   
    start = time.time()
    s.stepReverse(1000, 400)
    print "1000 steps reverse @ 400/sec, actually: %s /sec" %(1000.0/((time.time()-start)))
   
    start = time.time()
    s.halfStep(1000, 200)
    print "1000 halfSteps @ 200/sec, actually: %s /sec" %(1000.0/((time.time()-start)))
   
    start = time.time()
    s.halfStepReverse(1000, 400)
    print "1000 halfSteps reverse @ 400/sec, actually: %s /sec" %(1000.0/((time.time()-start)))
   
   
    s.deEnergize()
    time.sleep(2)
   
    print"*"*80
    print
   
    print "testing ramp"
    s.ramp(s.step, 0, 200, 100)
    s.step(100, 200)
    s.ramp(s.step, 200, 0, 100)
   
    s.deEnergize()
    time.sleep(2)
   
    print "testing ramp in halfStepReverse"
    s.ramp(s.halfStepReverse, 0, 200, 100)
    s.halfStepReverse(100, 200)
    s.ramp(s.halfStepReverse, 200, 0, 100)
   
    s.deEnergize()
    time.sleep(2)

    print "multi-ramp"
    s.ramp(s.step, 0, 200, 200)
    s.step(400,200)
    s.ramp(s.step, 200,400,400)
    s.step(800,400)
    s.ramp(s.step, 400,600,600)
    s.step(1200,600)
    s.ramp(s.step, 600,300,300)
    s.step(600,300)
    s.ramp(s.step, 300,0,300)
 
    s.deEnergize()
    time.sleep(2)

    print "multi-ramp to reverse"
    s.ramp(s.step, 0, 200, 200)
    s.step(400,200)
    s.ramp(s.step, 200,400,400)
    s.step(800,400)
    s.ramp(s.step, 400,200,200)
    s.step(200,200)
    s.ramp(s.step, 200,0,200)
    s.ramp(s.stepReverse, 0,200,200)
    s.stepReverse(300, 200)
 
    s.deEnergize()
    time.sleep(2)
Posts: 89
Joined: Sun Jul 01, 2012 12:48 am
by MadCow42 » Thu Jul 12, 2012 1:53 am
And here's the soldered-up version of my driver board... half complete (driving one motor for now only). It'll be capable of up to four motors with some software changes, but I'm only implementing two for now.

It'll also have channels for a limit switch and status light when it's done. Man, manually soldering up a board is a pain in the butt. :)

Kevin.
Attachments
stepperDriver.jpg
stepperDriver.jpg (40.61 KiB) Viewed 16868 times
Posts: 89
Joined: Sun Jul 01, 2012 12:48 am
by Casalor » Thu Jul 12, 2012 3:02 pm
That's great work, Kevin!

As soon as my 2nd Pi turns up stepper control is exactly the sort of thing I want to experiment with so I'll be watching this thread with interest. :)
User avatar
Posts: 67
Joined: Wed Feb 15, 2012 11:14 am
Location: Nantwich, UK
by MadCow42 » Fri Jul 13, 2012 3:40 am
Casalor wrote:That's great work, Kevin!

As soon as my 2nd Pi turns up stepper control is exactly the sort of thing I want to experiment with so I'll be watching this thread with interest. :)


Thanks! There's still a lot of improvements to make, but it's going well. The board design is working well too, although I'll be adding a few features as I go. Make sure to touch base before you start digging too deep with what's already posted - it's changing/improving daily.

Kevin.
Posts: 89
Joined: Sun Jul 01, 2012 12:48 am
by MadCow42 » Sat Jul 14, 2012 3:36 pm
Interesting update: I've been playing with controlling two steppers simultaneously, and to my surprise it runs exceptionally well with threading - controlling each motor separately in a different thread. I was expecting timing issues, but operation has been very smooth! The Raspberry Pi is a pretty capable little machine!

I'm probably 60% done with the board now - the two stepper drivers are fully done and running. Now I have to do the limit switches and other inputs/outputs.

short video too (still uploading...): www.cazabon.com/temp/P1020935.MOV

Kevin.
Attachments
Board_Back.jpg
Board_Back.jpg (38.53 KiB) Viewed 16746 times
Board_Front.jpg
Board_Front.jpg (59.17 KiB) Viewed 16746 times
Posts: 89
Joined: Sun Jul 01, 2012 12:48 am
by irving » Tue Jul 17, 2012 10:58 pm
Hi, what sort of reliable step rate are you achieving?
Posts: 1
Joined: Sat Jul 14, 2012 11:23 am
by MadCow42 » Wed Jul 18, 2012 2:01 am
irving wrote:Hi, what sort of reliable step rate are you achieving?


It's not a chopper drive, so I'm only able to get about 700 steps/second before it starts stalling. Software-wise I can drive to about 900 or so I believe, but haven't fully tested that since converting to wiringpi. The good news is that I'm able to run two motors completely independently in two different Python threads at 400-500 steps/second with no issues - but I haven't pushed the limits there to see when it'd start having problems.

I've also improved the timing routines to get smoother stepping since the code I posted - I'll post better/cleaner code when it's more complete, unless someone needs it urgently.

Kevin.
Posts: 89
Joined: Sun Jul 01, 2012 12:48 am
by MadCow42 » Wed Jul 18, 2012 4:03 am
irving wrote:Hi, what sort of reliable step rate are you achieving?


Update: I just upgraded to the Raspbian OS, and am able to get 1350 steps/second before the motor starts stalling! I am doing a bunch of floating point math for each step, so I'm not surprised it's faster, but I am surprised how MUCH faster it is (almost double!).

But... I'm using a much better SD card too: Class 10 vs. Class 4. I'm sure that has at least something to do with it all.

Kevin.
Posts: 89
Joined: Sun Jul 01, 2012 12:48 am
by jardiamj » Wed Jul 18, 2012 5:25 am
Nice!!

Here is my set up:

Image

I already got it working with Python using the RPI.GPIO libary. I also would like to solder it into a more permanent board, but I am really a newbie in these world soldering, I bought a tool kit form SparkFun and have already tried to get some practice, but I'm having some trouble, my iron tip always seems to be dirty and as harder as I try I can't get it clean and I think that's why it takes longer to hit up the thing I want to solder, any tips, or any sources on how to properly solder would be really appreciated.
Posts: 14
Joined: Thu Jan 19, 2012 7:11 pm
by MadCow42 » Wed Jul 18, 2012 12:10 pm
jardiamj wrote:any tips, or any sources on how to properly solder would be really appreciated.


Awesome! I'm guessing from the picture that your stepper is very low power, because it looks like you're driving it right off of the chip's outputs?

For soldering, I use a wet rag to clean off the tip from time to time. Before I solder a piece, I melt a little bit of solder on the tip, then make contact with the part I'm soldering with the already-melted solder on the tip. That transfers heat quicker and easier. Don't use too much of course though.

Good luck - I'll clean up some code and try to post it tonight for you. What I posted above works fine, but the timing system is much improved now.

Kevin.
Posts: 89
Joined: Sun Jul 01, 2012 12:48 am
by exartemarte » Wed Jul 18, 2012 12:46 pm
jardiamj wrote:any tips, or any sources on how to properly solder would be really appreciated.

There are lots of "how to solder" tutorials on line. This one has stood the test of time and seems to be well thought of by Everyday Practical Electronics readers.

If you haven't already got one, a soldering iron stand with a sponge is a good buy - the heavier the better. Soak the sponge and squeeze out the surplus water before you start to solder, then before you make a joint, clean the tip of the iron on the damp sponge and tin it as described by MadCow42, above.
User avatar
Posts: 359
Joined: Sat Mar 03, 2012 3:51 pm
Location: Middle England
by jardiamj » Wed Jul 18, 2012 6:31 pm
MadCow42 wrote:Awesome! I'm guessing from the picture that your stepper is very low power, because it looks like you're driving it right off of the chip's outputs?

For soldering, I use a wet rag to clean off the tip from time to time. Before I solder a piece, I melt a little bit of solder on the tip, then make contact with the part I'm soldering with the already-melted solder on the tip. That transfers heat quicker and easier. Don't use too much of course though.

Good luck - I'll clean up some code and try to post it tonight for you. What I posted above works fine, but the timing system is much improved now.

Kevin.


This is a stepper I took off and old matrix point printer, it's 6.4V and I'm running it through and H-bridge (http://www.sparkfun.com/datasheets/IC/SN754410.pdf) with 4 AA batteries, you can see them there in the picture right by the motor. Then there are 6 loose jumper cables that go to the RPi: 1 black (ground), 1 red (5V supply for the H-bridge IC), 2 yellow and 2 blue (to control the steps).

Thanks for the tip on soldering, I'll try it next time. I'll be looking forward to your clean code.
Posts: 14
Joined: Thu Jan 19, 2012 7:11 pm
by jardiamj » Wed Jul 18, 2012 6:43 pm
exartemarte wrote:If you haven't already got one, a soldering iron stand with a sponge is a good buy - the heavier the better. Soak the sponge and squeeze out the surplus water before you start to solder, then before you make a joint, clean the tip of the iron on the damp sponge and tin it as described by MadCow42, above.


Thanks for the link, I'm reading through it right now. I got a stand from SparkFun as well, it is quite light so I had to stick a heavy round magnet to it because it was running me crazy.

Thanks for the tips guys!
Posts: 14
Joined: Thu Jan 19, 2012 7:11 pm
by MadCow42 » Sat Jul 21, 2012 2:42 am
Here's my cleaned-up code for driving the stepper motor(s). The timing circuit is now accurate to within about 0.000003%, and on Raspbian I'm getting amazing performance and smoothness in stepping (up to 1350 steps per second on my setup).

It currently uses wiringpi for output to the GPIOs, but it's trivial to modify for use with croston's RPi.GPIO as well (see notes in the code).

Enjoy! If anyone uses it, send me a note - I'd love to hear what you're doing with it.
Kevin.

Code: Select all
# Stepper Driver Module
# Copyright (c) 2012 Kevin Cazabon
# kevin@cazabon.com

import time
import math
import wiringpi

# if using RPi.GPIO instead of wiringpi, modify the imports and GPIO_OUT and GPIO_IN classes accordingly - the rest should be fine as-is.
wiringpi.wiringPiSetup()

FORWARD = 1
REVERSE = -1

class GPIO_OUT:
    def __init__(self, channel, postSleep = None):
        # postSleep can be used if the OS is writing faster than your driver board can take data... probably not needed though.
        self.channel = channel
        self.postSleep = postSleep
       
        wiringpi.pinMode(self.channel, 1)
 
    def output(self, value):
        wiringpi.digitalWrite(self.channel, 1 if value else 0)
        if self.postSleep:
            time.sleep(self.postSleep)

class GPIO_IN:
    def __init__(self, channel):
        self.channel = channel
       
        wiringpi.pinMode(self.channel, 2)
           
    def input(self):
        if wiringpi.digitalRead(self.channel) == 1:
            return False
        else:
            return True

class stepper:
    def __init__(self, dataPin, clockPin, latchPin, limitPin, startLocked = False, callback = None, callbackArg = None, callbackFreqSteps = 20):
        # callback(callbackArg) is called every callbackFreqSteps number of steps - allowing you to time other processes if needed.
        #   however, be careful on how long that function takes to return - it can cause hiccups in your stepping.
       
        self.d = GPIO_OUT(dataPin)
        self.c = GPIO_OUT(clockPin)
        self.l = GPIO_OUT(latchPin)
        self.limit = GPIO_IN(limitPin)
        self.callback = callback
        self.callbackArg = callbackArg
        self.callbackFreqSteps = callbackFreqSteps
       
        self.stepPattern = [True, True, False, False]
        self.halfStepPattern = [[True, False, False, False], [True, False, False, True], [False, False, False, True], [False, False, True, True], [False, False, True, False], [False, True, True, False], [False, True, False, False], [True, True, False, False]]
       
        self.stepCount = 0
        self.lastStepTime = time.time()
       
        # to get accurate step speeds, the timing circuit needs a fudge-factor to account for overhead in the sleep call itself as well as the GPIO latch call
        # 0.00018 seconds is good as a starting point, tested with a stock Raspberry pi, Debian Wheezy - confirmed on Raspbian too
        # test it with the .calibrate() method to ensure it's accurate on your system
        self.stepCalibration = 0.00018 
       
        if startLocked:
            self.lock()
   
    def lock(self):
        self.step(FORWARD, 1)

    def deEnergize(self, motor1 = True, motor2 = True):
        self.d.output(False)
        self.c.output(False)
        self.l.output(False)
       
        # shift 8 blank bits onto the shift register, then latch it. wiringpi.shiftOut() would be better
        for i in range(8):
            self.c.output(True)
            self.c.output(False)
       
        self.l.output(True)
        self.l.output(False)

    def step(self, steps = 1, stepsPerSecond = None, direction = FORWARD, overrideLimit = False):
        # forward can be done by simply shifting one bit at a time through the register - much faster!
       
        if (direction != FORWARD and steps > 0) or (direction == FORWARD and steps < 0):
            return self.stepReverse(steps, stepsPerSecond, overrideLimit)
           
        self.c.output(False)
        self.l.output(False)
       
        if stepsPerSecond:
            stepTime = 1.0/float(stepsPerSecond)
        else:
            stepTime = 0.0
       
        for i in range(int(self.stepCount + 1), int(self.stepCount + 1) + steps, 1):
            if overrideLimit:
                print "*** Limit Switch Over-Ridden! Careful! ***"
            else:
                if self.limit.input():
                    print "*** Limit Sensed - cannot move further ***"
                    self.stepCount = i
                    return -1
                           
            self.c.output(False)
            self.d.output(self.stepPattern[i%len(self.stepPattern)])
            self.c.output(True)

            # ensure we're going the right-ish speed
            if stepTime:
                time.sleep(max(0.0, self.lastStepTime + stepTime - self.stepCalibration - time.time()))

            # data should be loaded... latch it to the output!
            self.l.output(True)
            self.lastStepTime = time.time()
            self.l.output(False)
           
            if self.callback != None and i % self.callbackFreqSteps == 0:
                self.callback(self.callbackArg)
           
        # we must have completed all steps without hitting the limit switch
        self.stepCount = i
        return 0
   
    def complexStep(self, pattern, stepCounter, stepsPerSecond, overrideLimit):
        # load all pattern bits into the shift register before latching
        # this only does one step - you'll have to call it multiple times
        if overrideLimit:
            print "*** Limit Switch Over-Ridden! Careful! ***"
        else:
            if self.limit.input():
                print "*** Limit Sensed - cannot move further ***"
                return -1

        self.c.output(False)
        self.l.output(False)
       
        if stepsPerSecond:
            stepTime = 1.0/float(stepsPerSecond)
        else:
            stepTime = 0.0

        for i in range(len(pattern)):
            # this should probably be converted to wiringpi.shiftOut()
            self.d.output(pattern[i])
            self.c.output(True)
            self.c.output(False)

        # ensure we're going the right-ish speed
        if stepTime:
            time.sleep(max(0.0, self.lastStepTime + stepTime - self.stepCalibration - time.time()))
           
        self.l.output(True)
        self.lastStepTime = time.time()
        self.l.output(False)

        if self.callback != None and self.stepCount % self.callbackFreqSteps == 0:
            self.callback(self.callbackArg)

        self.stepCount = self.stepCount + stepCounter
       
        return 0
   
    def stepReverse(self, steps = 1, stepsPerSecond = None, overrideLimit = False):
        for i in range(steps):
            # load the four data points into motor1
            stepNumber = int(self.stepCount - 1)%len(self.stepPattern)
           
            # stepNumber will be counting down.  Shift the step pattern left accordingly
            pattern = self.stepPattern[stepNumber:] + self.stepPattern[:stepNumber]
               
            if self.complexStep(pattern, -1, stepsPerSecond, overrideLimit) != 0:
                # Limit sensed during movement, bail.
                return -1
        return 0

    def halfStep(self, steps = 1, stepsPerSecond = None, direction = FORWARD, overrideLimit = False):
        if (direction != FORWARD and steps > 0) or (direction == FORWARD and steps < 0):
            return self.halfStepReverse(steps)
       
        for i in range(steps):
            stepNumber = int(((float(self.stepCount) + 0.5)*2.0) % len(self.halfStepPattern))
            pattern = self.halfStepPattern[stepNumber]
            if self.complexStep(pattern, 0.5, stepsPerSecond, overrideLimit) != 0:
                # Limit sensed during movement, bail.
                return -1
        return 0

    def halfStepReverse(self, steps = 1, stepsPerSecond = None, overrideLimit = False):
        for i in range(steps):
            stepNumber = int(((float(self.stepCount) - 0.5)*2.0) % len(self.halfStepPattern))
            pattern = self.halfStepPattern[stepNumber]
            if self.complexStep(pattern, -0.5, stepsPerSecond, overrideLimit) != 0:
                # Limit sensed during movement, bail.
                return -1
        return 0

    def stepTime(self, startTime, normalTime, rampSteps, thisStepNumber):
        # this needs some work for constant acceleration - but it's a start.
        x = float(thisStepNumber)/float(rampSteps)
        x2 = 1 - math.sin(x * math.pi/2.0)
       
        timeThisStep =  normalTime + (x2 * (startTime - normalTime))
           
        return timeThisStep
   
    def ramp(self, function, startSpeed, finalSpeed, rampSteps, callbackDuringRamp = True, overrideLimit = False):
        # DEPRECIATED - timeRamp is better, unless you need to take a specific number of steps during the ramp phase
        # ramps from startSpeed to finalSpeed in rampStep number of steps, following a Sine curve for timing
        if startSpeed == 0:
            startSpeed = min(50, finalSpeed)
        if finalSpeed == 0:
            finalSpeed = min(50, startSpeed)
           
        for i in range(rampSteps):
            if startSpeed > finalSpeed:
                stepTime = self.stepTime(1.0/float(startSpeed), 1.0/float(finalSpeed), rampSteps, i)
            else:
                stepTime = self.stepTime(1.0/float(finalSpeed), 1.0/float(startSpeed), rampSteps, rampSteps - i)
                             
            if function(1, 1.0/stepTime,overrideLimit = overrideLimit) != 0:
                # Limit sensed during movement, bail.
                return -1
           
            if self.callback != None and callbackDuringRamp and self.stepCount % self.callbackFreqSteps == 0:
                self.callback(self.callbackArg)
               
    def timeRamp(self, function, startSpeed, finalSpeed, rampTime, callbackDuringRamp = True, overrideLimit = False):
        # this is a true linear ramp, but you have to specify the TIME over which you want to ramp, not the number of steps
        if startSpeed == 0:
            startSpeed = min(50, finalSpeed)
        if finalSpeed == 0:
            finalSpeed = min(50, startSpeed)
         
        rampTime = float(rampTime)
        startTime = time.time()
       
        # take the first step
        function(1, startSpeed, overrideLimit = overrideLimit)
       
        while 1:
            nowSpeed = startSpeed + (((time.time() - startTime)/rampTime) * (finalSpeed - startSpeed))
           
            if startSpeed <= finalSpeed and nowSpeed >= finalSpeed:
                return 0
            elif startSpeed >= finalSpeed and nowSpeed <= finalSpeed:
                return 0
           
            if function(1, nowSpeed, overrideLimit = overrideLimit) != 0:
                # Limit sensed during movement, bail.
                return -1
           
            if self.callback != None and callbackDuringRamp and self.stepCount % self.callbackFreqSteps == 0:
                self.callback(self.callbackArg)
       
    def calibrate(self):
        # CAREFUL - we're going to dis-engage the motor to do this... don't let anything crash by de-energizing your motor!
        # we probably could shorten the calibration quite a bit and get the same result...
       
        stepPattern = self.stepPattern
        halfStepPattern = self.halfStepPattern
       
        self.stepPattern = [False, False, False, False]
        self.halfStepPattern = [[False, False, False, False],[False, False, False, False],[False, False, False, False],[False, False, False, False],[False, False, False, False],[False, False, False, False],[False, False, False, False],[False, False, False, False]]
       
        errors = []
       
        # run through a series of speeds and calculate the error
        for i in range(50, 750, 50):
            start = time.time()
            s.step(i*10, i)
            duration = time.time() - start
            actual = float(i*10)/duration
            errorPerStep = (duration - 10.0)/ float(i*10)
            errorPct = errorPerStep/actual
            print "%s steps/sec requested = \t%s steps/sec real = error of \t%s sec per step or \t%s %%" %(i, actual, errorPerStep, errorPct)
            errors.append(errorPerStep)
        print
       
        for i in range(50, 750, 50):
            start = time.time()
            s.stepReverse(i*10, i)
            duration = time.time() - start
            actual = float(i*10)/duration
            errorPerStep = (duration - 10.0)/ float(i*10)
            errorPct = errorPerStep/actual
            print "%s steps/sec requested = \t%s steps/sec real = error of \t%s sec per step or \t%s %%" %(i, actual, errorPerStep, errorPct)
            errors.append(errorPerStep)
        print
       
        for i in range(50, 750, 50):
            start = time.time()
            s.halfStep(i*10, i)
            duration = time.time() - start
            actual = float(i*10)/duration
            errorPerStep = (duration - 10.0)/ float(i*10)
            errorPct = errorPerStep/actual
            print "%s steps/sec requested = \t%s steps/sec real = error of \t%s sec per step or \t%s %%" %(i, actual, errorPerStep, errorPct)
            errors.append(errorPerStep)
           
        s.deEnergize()
           
        # Average the errors and apply the correction
        total = 0.0
        for e in errors:
            total = total + e
        errorValue = total / flloat(len(e))
       
        self.stepCalibration = self.stepCalibration - errorValue
       
        print "Error corrected by: %s (self.stepCalibration = %s)" %(errorValue, self.stepCalibration)
   
        self.stepPattern = stepPattern
        self.halfStepPattern = halfStepPattern

if __name__ == "__main__":
   
    # make a stepper class
    s = stepper(1,7,0,3)
   
    # test calibration - so that the stepping rate is accurate, despite system overhead (likely dependent on OS, SD card speed, etc.)
    s.calibrate()
   
    # testing
    s.timeRamp(s.step, 0, 400, 2.0)
    s.step(1200, 400)
    s.timeRamp(s.step, 400,800,2.0)
    s.step(2400,800)
    s.timeRamp(s.step, 800,1200,2.0)
    s.step(2400,1200)
    s.timeRamp(s.step, 1200,600,2.0)
   
    #switch to halfsteps
    s.halfStep(2400,1200)
    s.timeRamp(s.halfStep, 1200,200,4.0)
    s.halfStep(200,200)
    s.timeRamp(s.halfStep, 200, -200, 2.0)
    s.halfStepReverse(200, 200)
    s.timeRamp(s.halfStepReverse, 200, 600, 3.0)
    s.halfStepReverse(600,600)
    s.timeRamp(s.halfStepReverse, 600,0,2.0)
   
    print "Finished!"
Posts: 89
Joined: Sun Jul 01, 2012 12:48 am
by h121 » Fri Jul 27, 2012 8:44 pm
Hi and thanks for this info! It'll be very useful when I get my motors from Ebay :)

Anyway, I tried to use your code and ran into some minor problems. First of all, I think there are a few typos in the average error calculation, line 308 or so. However, Python allows us to calculate the average without using a loop to add the error values. You can simply replace lines 305-308 with this:

Code: Select all
        errorValue = sum(errors) / len(errors)

I'll post more comments after I get to use it with some actual hardware.
Posts: 2
Joined: Fri Jul 27, 2012 8:32 pm
by FX4 » Wed Aug 01, 2012 5:00 pm
Very cool. The beginning of a robotics project. :D
Posts: 64
Joined: Fri Jul 27, 2012 2:39 pm