Steppers, or stepping motors, are brushless DC motors. In the PyHack workshop we first started with brushed DC motors (2 wires), then using H bridges to make them rotate both ways, and used pulse width modulation (PWM) to vary their speed. We followed with servos and then we went into steppers.
Unfortunately, I've not posted on some of these aspects yet (I thought I had), but I'll definitely post the explanations (servo, PWM), diagrams and python code for these too.
Now, let's get back to our steppers. They do come in all kinds of shape and size:
For our discussion right now however, we'll focus on just one thing, the number of wires. Typically, you will have a stepper with 4, 5 or 6 wires:
|5 wire unipolar, 4 wire bipolar|
|6 wire unipolar|
As can be seen, 5 or 6 wires are fundamentally the same. They are what is called a Unipolar stepper. 4 wire steppers are Bipolar steppers. A different type of driver is needed depending if you have to drive one or the other.
|5 wire unipolar stepper with a 6 position connector|
We are focusing today on the unipolar kind. Last summer, I got a hold of a good quantity of some very small steppers (28BYJ-48). Their spec was 5V, 5 wire unipolars, originally designed to control louvers on air conditioning units.
The neat thing with them is they have a built-in gearhead with a 64:1 ratio, allowing for some torque and precision, and allowing for instant on full speed sequencing (no need to ramp up):
I had this as spec:
Later I found this PDF spec, which included the sequence and wire colors:
What is interesting to note is that we basically have 4 square waves driving the motor, with 2/3 offset.
If you don't have the specs like above (even with your google-fu being strong), you will need a multimeter to measure the resistance between the different wires to figure out which wire is the center tap and which wires are part of the same coil.
Let's say you measure 72 ohm between 2 wires, and 144 between 2 others, then you know that one of the wires that had 72 ohm is a center tap. The two giving 144 ohm (whatever the value, we are looking for ratios of 2:1) are the extremities of a coil.
With the above motor, we will need 4 GPIOs. The 28BYJ-48 is often sold with a small board that includes a ULN2003 darlington array (I got those with some subsequent buys of the steppers):
It is convenient because it even includes the headers. All you need are 6 jumper wires. 4 for the GPIOs, one for ground and one for 5V, and you are in business. That is sufficient to start playing with Python code, and you might be able to skip the next section.
If however you are like me, your odyssey is just beginning.
The ULN2003 were bread and butter circuits in the days of 7 segment led displays (remember those calculators with red led segment displays or even vacuum displays)? They had 7 Darlington pairs in the array. However, 7 is an inconvenient number. Our steppers need 4 channels (4 Darlington pairs). So we need 2 ULN2003 if we are going to drive 2 motors.
A better choice is the ULN 2803. The specs can be found here (Texas Instrument). Similar design to the ULN2003, it has 8 channels (8 Darlington pairs), hence a perfect match for 2 motors!
The ULN2803 has several built in features that will make our life oh so simple:
- inputs all on one side
- outputs all on the other
- each output is protected by a free wheeling diode
- each channel is an inverting amplifier
So, let's get this on a breadboard:
I added LEDs between each input and the ground (-) rail. No resistors limiting current, but these are though. I've run them for months like that without burning them. If you run GPIOs always on, then I'd add a resistor, but if not, why bother?
The LEDs provides an easy way to visually debug code and connections when stepping through the code line by line.
As for the chip, we simply connect pin 9 to ground, pin 10 to 5V and GPIOs 17, 18, 21 and 22 to pins 8, 7, 6 and 5 (a second motor on GPIOs 4, 25, 24, 23 would connect to pins 4, 3, 2, 1). Then we connect the motor to pins 11, 12, 13 and 14 and the center tap wire to 5V. And that is it.
If there is interest, I can post a fritzing layout of the breadboard. Just let me know in comment, tweet or email.
The PythonLet's first start with the basic 4 step sequence (the complete one is 8) with one motor connected to GPIOs 17, 18, 21 and 22.
I've seen a lot of code out there that just doesn't take advantage of Python and lists. Stuff like:
if step == 1: gpio.output(22, gpio.HIGH) gpio.output(21, gpio.LOW) gpio.output(18, gpio.LOW) gpio.output(17, gpio.LOW) elif step == 2: gpio.output(22, gpio.LOW) gpio.output(21, gpio.HIGH) gpio.output(18, gpio.LOW) gpio.output(17, gpio.LOW) elif step == 3: gpio.output(22, gpio.LOW) gpio.output(21, gpio.LOW) gpio.output(18, gpio.HIGH) gpio.output(17, gpio.LOW) elif step == 4: gpio.output(22, gpio.LOW) gpio.output(21, gpio.LOW) gpio.output(18, gpio.LOW) gpio.output(17, gpio.HIGH)
So instead, what we need to do is define a stepper function that will take a sequence. With 4 steps and 4 pins, this could be really simplified, but I'm building this to transition to the much more complicated 8 step sequence (all the code on this post was written during PyHack Workshop #02)..
import RPi.GPIO as gpio import time PINS = [22,21,18,17] SEQA = [(22,),(21,),(18,),(17,)] DELAY = 0.05 gpio.setmode(gpio.BCM) for pin in PINS: gpio.setup(pin, gpio.OUT) def stepper(sequence, pins): for step in sequence: for pin in pins: if pin in step: gpio.output(pin, gpio.HIGH) else: gpio.output(pin, gpio.LOW) time.sleep(DELAY) try: while True: for _ in range(512): stepper(SEQA, PINS) for _ in range(512): stepper(SEQA[::-1], PINS) except KeyboardInterrupt: gpio.cleanup()
So, let's get to the 8 step sequence. Plus we can continue improving this code.
import RPi.GPIO as gpio import time PINS = [22,21,18,17] SEQA = [(22,),(22,21),(21,),(21,18),(18,),(18,17),(17,),(17,22)] RSEQA = [(17,),(17,18),(18,),(18,21),(21,),(21,22),(22,),(22,17)] DELAY = 0.002 gpio.setmode(gpio.BCM) for pin in PINS: gpio.setup(pin, gpio.OUT) def stepper(sequence, pins): for step in sequence: for pin in pins: gpio.output(pin, gpio.HIGH) if pin in step else gpio.output(pin, gpio.LOW) time.sleep(DELAY) try: while True: for _ in xrange(512): stepper(SEQA,PINS) # forward for _ in xrange(512): stepper(RSEQA,PINS) # reverse except KeyboardInterrupt: gpio.cleanup()
We used the ternary operator on the stepper function, but mostly, we extended the sequence to have 8 steps. This is where Python shines, and doing this in pure C is not very elegant. Python is fast enough that we do have to introduce a delay, else we would stall the stepper.
The last piece of Python code we are showing today is something that makes for a bit better of a demo than just turning one way, then the other. We introduce a bit of a random element and add support for two motors (and tweak the stepper function one more time):
import RPi.GPIO as gpio import time import random DELAY = 0.001 PINS_A = [4,25,24,23] PINS_B = [22,21,18,17] #PINS = PINS_A + PINS_B PINS = PINS_B SEQA = [(4,),(4,25),(25,),(25,24),(24,),(24,23),(23,),(23,4)] RSEQA = [(23,),(23,24),(24,),(24,25),(25,),(25,4),(4,),(4,23)] SEQB = [(22,),(22,21),(21,),(21,18),(18,),(18,17),(17,),(17,22)] RSEQB = [(17,),(17,18),(18,),(18,21),(21,),(21,22),(22,),(22,17)] print SEQA RSEQA = SEQA[::-1] print RSEQA gpio.setmode(gpio.BCM) for pin in PINS: gpio.setup(pin, gpio.OUT) def stepper(sequence, pins, delay=DELAY): for step in sequence: for pin in pins: gpio.output(pin, gpio.HIGH if pin in step else gpio.LOW) time.sleep(delay) try: while True: rate = DELAY * random.randint(1,3) for _ in range(random.randint(10,64)): stepper(SEQB,PINS_B,rate) for _ in range(random.randint(10,64)): stepper(RSEQB,PINS_B,rate) except KeyboardInterrupt: gpio.cleanup()
For the reverse sequence, we normally would use
RSEQA = SEQA[::-1]
But unfortunately, that would stall the stepper. We have to set a specific offset of the reverse. Here we simply enumerate. If you look at the piasketch.py code, you'll see that there is a better way to do this using slices.
How does patterns.py look in operation?
A bit more permanent
Once I'm happy with my testing on a breadboard, I typically transfer to something a little bit more permanent. Since I only use 1 chip, a great protoboard is the 276-159B that can be picked up at your local Radio Shack:
A slightly fancier solution is to use an Adafruit proto plate:
In my case, I took an old serial adapter, cut the cable, soldered it along with an IC socket and added some ribbon cable with headers and plugged that into the Raspberry Pi:
The wiring folds and once the cover is closed, the only thing sticking out are the two ribbon cables with headers for connecting the steppers.
You are never doneOr why the Pi-A-Sketch ended up with LEDs afterall.
So here you are, just before a workshop, with your project ready to demo. You check your mailbox, and you get something unexpected.
I had bought a keypad board for the Pi, yet when I opened the box, I found a different board and 1 motor.
I didn't really need that since I had a much more compact version on a permanent protoboard, yet, I started measuring to see if I could make this fit inside the case, so that I would have LEDs sticking out.
Because blikenlights are good for you... So out came the knife, the dremel, the snipers etc. I ended up having to cut the whole area where the IC and the jumper were, not just the headers and LEDs.
It didn't look as neat as I was hoping. And time was running out, no time to order a new case and go back to my previous design. In the end, a little raspberry colored foamboard saved the day, with hours to spare before the workshop :)
With all this background, we are now ready to review the Pi-A-Sketch Python code. But that will have to wait until another day.