Monday, 22 August 2011

Driving a stepper motor directly from a PIC

Much has been written about driving stepper motors. Some sites say it's easy. Some people say it's hard. Some people say you have to get the timing and the sequence right, some people say it's more hassle than it's worth and you should just buy a stepper driver board!

We've spent just a few hours today playing about with our (quite beefy) stepper motor (borrowed from Matt at 18robots.com until we can buy/salvage some 1.8 degree steppers) and found it's quite straight forward - provided you follow each step slowly and carefully.

The first thing to do is understand how the stepper motor works.



We're just going to stick to the common 4-phase stepper motor. It has four coils and we need to energise each coil in a particular sequence, in order to get the shaft to turn.

For the sake of simplicity, we'll only use one form of stepping (you can use whole-step, half-step and micro-stepping for different levels of precision). For whole stepping, you energise one coil at a time. With micro-stepping, you vary the amount of current going into one or two coils at a time. We're going to make all our movements using half-stepping, which means activating one or two coils at any one time.

To get the above stepper to move anti-clockwise, we need to energise:
  • coil 1 only
  • coils 1+2
  • coil 2 only
  • coils 2+3
  • coil 3 only
  • coils 3+4
  • coil 4 only
  • coils 4+1

As you can see, it takes 8 pulse combinations then we're back at the start. This sort of makes sense, since we have four coils, and we're using half-stepping (twice as many steps needed as for whole-stepping).

Now we know how a stepper motor works, we need to identify each wire in our bunch of six coloured wires. Sadly, there's no common colour chart to refer to - different manufacturers use different coloured wires so don't just blindly follow this set-up: find out how your own stepper is wired using this technique



Create a chart listing all six colours of your stepper motor in rows and columns.
With a multi-meter, measure the resistance between each coloured wire and the five other wires. Write down these values - each coloured wire should be connected to two other wires. It doesn't matter what these values are, what we're looking for is which values are twice the others.



Where you have the double-resistance values (in our case, black + green and red + blue) this is where you've just measured across two coils. These are the outer-most coil wires (in our example, black=A, green=C, red=D, blue=F)

Where you have single resistance values (in our case black + yellow, green + yellow and red + white, blue + white) you've just measured across a single coil so one of these pairs of wires must be the "one in the middle" - or common wire.
In our example, yellow is between black and green, so yellow=B and white=E

With this information, we're ready to wire everything up and make our motors move!



We've created a USB interface which allows us to send movement commands to the PIC 18F4550 microcontroller as well as a "jog" button. We can send a command to the PIC to say "move the motor through X number of steps", or we can "jog" the motor - i.e. make it rotate for as long as the button is pressed.

Here's a snippet of the Oshonsoft Basic code for controlling the single stepper motor:


loop:

     UsbService
     
     'every time we take an input pin high, pulse the stepper
     If PORTB.7 = 0 Then
           'jog the x axis motor
           jog_x = 1
           steps_to_move_x = 0
           Gosub stepmotor
           Gosub delay_after_pulse
     Else
     
           'update the X axis motor
           If steps_to_move_x > 0 Then
                 Gosub stepmotor
                 steps_to_move_x = steps_to_move_x - 1
                 Gosub delay_after_pulse
           Endif
     Endif
     
Goto loop
End



stepmotor:
     If stepdir = 0 Then
           step_pos = step_pos + 1
           If step_pos > 7 Then step_pos = 0
     Else
           If step_pos = 0 Then
                 step_pos = 7
           Else
                 step_pos = step_pos - 1
           Endif
     Endif
     
     Gosub energizecoil
Return



energizecoil:

     'our test stepper is wired with
     'red-white-blue and green-yellow-black connected
     '(white is common, yellow is common) so we need to drive them
     'in the pattern red, black, blue, green
     'to turn in one direction and red, green, blue, black to
     'turn in the opposite direction

     Select Case step_pos
                 
           Case 0
           Low x_coil4pin
           High x_coil1pin

           Case 1
           High x_coil1pin
           High x_coil2pin
                             
           Case 2
           Low x_coil1pin
           High x_coil2pin
                       
           Case 3
           High x_coil2pin
           High x_coil3pin
                 
           Case 4
           Low x_coil2pin
           High x_coil3pin

           Case 5
           High x_coil3pin
           High x_coil4pin
                                   
           Case 6
           Low x_coil3pin
           High x_coil4pin
                       
           Case 7
           High x_coil4pin
           High x_coil1pin
                             
     EndSelect

Return



delay_after_pulse:
     If stepper_delay_us > 0 Then
           WaitUs stepper_delay_us
     Else
           WaitMs stepper_delay_ms
     Endif
Return



We've split the current position of the motor into 8 segments (numbered 0-7) so we always know which is the next combination of coils to energise to continue rotation. So wherever the motor stops, it can start again from exactly the same position - we don't have to return to a "known rest-state" as we should always know the next step in the sequence.

To move the motor, we can either hold down the jog button, or set a value in the two-byte variable called steps_to_move_x (this motor will form the basis of our x-axis). If this variable contains a value, we energise the necessary coils, move onto the next coil "state" and reduce the value in steps_to_move_x by one.
When this value reaches zero, we can send a message back to the PC to say we've arrived at our destination. This way, the PC can use a "fire-and-forget" messaging system: it tells the device how far it wants each motor on each axis to move, then gets on with other work. It doesn't have to keep asking for an update report, the device will inform the PC when the task has been performed (the PC fires off the request, then can forget about it!)



Here's a quick video demonstrating single-axis rotation using a 4-phase stepper motor. You can press (and hold) a button to "jog" the motor along, or send a value to the microcontroller, to tell it to move through a specific number of steps.


Sorry about the bad light! This was taken in the early hours of the morning (when most of the coolest development gets done) and it seems the camera doesn't like our artificial light)