Stepping Motor Control Software

Old Part 5 of Stepping Motors by Douglas W. Jones

Here's the code to make your motor run as if you had one of those fancy stepper controllers. I've used Pascal for no particular reason. This code assumes only one motor, and it assumes it's attached to the least significant bits of one parallel output port. In practice, it's nice to have one parallel output port per motor, although with a bit of care, you can use the high bits of a port for another motor or other applications, and you can multiplex one port to handle multiple motors. (The July 1993 issue of Model Railroader has plans for a parallel port multiplexer circuit for IBM PC systems in it).

Assume these declarations and values for a three winding variable reluctance motor:

     const maxstep = 2;
           steps = 3;
     var   steptab: array [0..maxstep] of integer;
           step: integer;
           motor: file of integer; { this is the I/O port for the motor }
     begin
           step := 0;
           steptab[0] = 1; { binary 001 }
           steptab[1] = 2; { binary 010 }
           steptab[2] = 4; { binary 100 }
           write( motor, steptab[step] );
Assume these declarations and values for a permanent magnet motor, either unipolar, with center tapped windings, or bipolar, with H-bridge drive circuits:
     const maxstep = 3;
           steps = 4;
     var   steptab: array [0..maxstep] of integer;
           step: integer;
           motor: file of integer; { this is the I/O port for the motor }
     begin
           step := 0;
           steptab[0] = 1; { binary 0001 }
           steptab[1] = 4; { binary 0100 }
           steptab[2] = 2; { binary 0010 }
           steptab[3] = 8; { binary 1000 }
           write( motor, steptab[step] );
Assume these declarations and values for half-step control of a permanent magnet motor:
     const maxstep = 7;
           steps = 8;
     var   steptab: array [0..maxstep] of integer;
           step: integer;
           motor: file of integer; { this is the I/O port for the motor }
     begin
           step := 0;
           steptab[0] = 1;  { binary 0001 }
           steptab[1] = 5;  { binary 0101 }
           steptab[2] = 4;  { binary 0100 }
           steptab[3] = 6;  { binary 0110 }
           steptab[4] = 2;  { binary 0010 }
           steptab[5] = 10; { binary 1010 }
           steptab[6] = 8;  { binary 1000 }
           steptab[7] = 9;  { binary 1001 }
           write( motor, steptab[step] );
Assume these declarations and values for control of a 5-phase motor, with an H-bridge on each of the 5 leads to the motor:
     const maxstep = 9;
           steps = 10;
     var   steptab: array [0..maxstep] of integer;
           step: integer;
           motor: file of integer; { this is the I/O port for the motor }
     begin
           step := 0;
           steptab[0] = 13;  { binary 01101 }
           steptab[1] = 9;   { binary 01001 }
           steptab[2] = 11;  { binary 01011 }
           steptab[3] = 10;  { binary 01010 }
           steptab[4] = 26;  { binary 11010 }
           steptab[5] = 18;  { binary 10010 }
           steptab[6] = 22;  { binary 10110 }
           steptab[7] = 20;  { binary 10100 }
           steptab[8] = 21;  { binary 10101 }
           steptab[9] = 5;   { binary 00101 }
           write( motor, steptab[step] );
The remainder of the code is the same and doesn't depend on the motor. The following procedure will advance the motor one step in either direction, where the direction parameter must be either +1 or -1 to indicate the direction.
     procedure onestep( direction: integer );
     begin
         step := step + direction;
         if step > maxstep then step := 0
         else if step < 0 then step := maxstep;
         write( motor, steptab[step] );
     end;
Software control of a stepping motor is a real-time task, and you need at least a bit of feedback. One bit is enough; typically, this will be a bit indicating that a cam on the turntable (or whatever the motor is driving) is interrupting a light beam or closing a microswitch. To avoid hysteresis problems in reading the position from this cam, you should only read zero to one transitions as indicating the home position when the motor is spinning in one direction. Especially with switches or where gear trains are involved between the motor and the turntable, the one to zero transition in the other direction won't usually occur at exactly the same position.

Given that you can read the sense bit and that you have a programmable interval timer interrupt on your system, it is easy to make the timer interrupt service routine operate the motor as follows:

     const maxpos = 11111; { maxpos + 1 is calls to onestep per rev }
     var position: integer; { current position of motor }
         destination: integer; { desired position of motor }
         direction: integer; { direction motor should rotate }
         last: integer; { previous value from position sensor }
         sensor: file of integer; { parallel input port }
     begin
         read( sensor, last );
         position := 1;
         setdest( 0, 1 ); { force turntable to spin on power-up until
                            it finds it's home position }

     procedure timer; { interval timer interrupt service routine }
     var sense: integer;
     begin
         read( sensor, sense );
         if (direction = 1) and (last = 0) and (sense = 1)
           then position = 0;
         last := sense;

         if position <> destination then begin
             onestep( direction );
             position := position + direction;
             if position > maxpos then position := 0
             else if position < 0 then position := maxpos;
         end;

         if position <> destination
           then settimer( interval_until_next_step );
     end;
The following procedure is the only procedure that user code should call. This procedure sets the destination position of the turntable and sets the direction of rotation, then sets the interval timer to force an immediate interrupt and lets the timer routine finish rotating the turntable while the applications program does whatever else it wants.
     procedure setdest( dst,dir: integer );
     begin
         destination := dst;
         direction := dir;
         if position <> destination
           then settimer( min_interval ); { force a timer interrupt }
     end;
If you want to control multiple stepping motors, it is easiest if you have one interval timers and one parallel port per motor. If you hardware has only one timer, then you can use it to simulate multiple interval timers, but this is most of the way to the job of writing a real-time executive.

A final note: If you try to step a motor too fast, it will slip and your software will lose track of the motor position. Motors typically come with a rating that indicates a maximum number of steps per second, but you may not be able to accelerate the motor to that number of steps per second from a dead start without running it at a lower speed first. This is especially true if the inertia of the load is fairly large, and in fact, with appropriate acceleration sequences, you can usually excede the maximum rated speed.

In the above code, interval_until_next_step is shown as a constant. If you are dealing with high-inertia loads or very short intervals, you'll have to make this a variable, using longer intervals during starting and stopping to take care of accelerating and decelerating the motor.