Tutorial #3

Control of a Stepping Motor and Use of EEPROM for Data Arrays
copyright, Peter H. Anderson, Baltimore, MD, Dec, '97

For Stamp enthusiasts who do not wish to purchase the Starter Package, a copy of this 18 page tutorial is $2.00, ULN2803A is $1.00, L293D is $2.50, heatsink is $0.50 and AirPax 7.5 degree stepper (12VDC, 33 Ohm coil) is $3.50.

Introduction.

This tutorial illustrates how to interface with and control a stepping motor using such devices as the ULN2803 Octal Driver and L293 Quad Push-Pull Driver. It also extends the discussion of Tutorial #2 to include the use of EEPROM to realize data arrays.

The tutorial begins with a brief discussion of the theory of a unipolar stepper and open collector transistor drivers such as the ULN2803. Although the stepper provided with the Starter Package is a unipolar stepper, we will then configure it as a bipolar motor and drive it with a L293 push pull driver.

All of the stepping motor routines in Tutorial #2 (PATT_1.BS2 - PATT_7.BS2) may be used to control the stepper in either of these configurations.

The tutorial also illustrates how task sequences may be defined in EEPROM and the various task sequences may then be selected using the DIP switches.

General Theory of Stepping Motors - Unipolar.

Refer to Figure #1 titled "Layout of Unipolar Stepping Motor ".

Assume that when Phi 0 is at ground, point A is North. However, as the opposite pole is wound in reverse, point C is South. The rotor is then positioned as shown in Figure #1A.

Now assume that Phi 1 is also grounded. Thus, as shown in Figure #1B, points A and B are North and C and D are south. Note that the change in field causes the rotor to move slightly clockwise.

Now assume only Phi 1 is grounded. As shown in Figure #1C, point B is North and point C is south. Note that the motor has advanced further clockwise.

Thus, the sequence started with Phi 0 grounded, then Phi 0 and Phi 1, and then Phi 1. One should be able to verify the operation would be similar if the sequence had been Phi 1 alone, then Phi 1 and Phi 2 and then Phi 2 only, etc.

Thus, the motor may be turned by successively outputting the following patterns to the motor windings in sequence.

          Phi3  Phi2  Phi1  Phi0      

	  OPEN	OPEN  OPEN  GRD
          OPEN  OPEN  GRD   GRD
	  OPEN  OPEN  GRD   OPEN          
          OPEN  GRD   GRD   OPEN

          OPEN  GRD   OPEN  OPEN
          GRD   GRD   OPEN  OPEN
          GRD	OPEN  OPEN  OPEN
          GRD	OPEN  OPEN  GRD

          OPEN  OPEN  OPEN  GRD
          etc

     Table #1 - Unipolar Stepper Sequence
Note than in moving down through the patterns drives the rotor in one direction, while moving up drives it in the opposite direction. The distance may be controlled by the number of steps that are output and the speed of the rotor may be controlled by varying the amount of time between outputting patterns.

Refer to the Figure #2 titled "Stepping Motor Control". The motor windings are driven using a ULN2803A Octal Inverting Driver which interfaces TTL to the motor winding. As this IC inverts the data, the above patterns are output from the Basic Stamp in inverted form; that is,

          P3   P2   P1   P0   (Value in HEX) 

          0    0    0    1    $1
          0    0    1    1    $3
          0    0    1    0    $2
          0    1    1    0    $6

          0    1    0    0    $4
          1    1    0    0    $C
          1    0    0    0    $8
          1    0    0    1    $9

          0    0    0    1    $1
          etc

          Table #2 - Stepping Motor Patterns
          Output by the Stamp
Note that these are the familiar patterns which were used in Tutorial #2 and you may wish to configure the circuitry as shown in Figure #2 and run the routines. The LEDs may be left in place and indeed they are a good debugging tool in assuring the Stamp is outputting the correct states. However, once you are satisfied, you can adjust the various pauses to 1, 2, 3 etc so as to speed up the motor. Note that in doing so, the LEDs will appear as a blur.

It is worthy of note, that with a unipolar stepper, the voltage source is provided at the center of two windings; for example, at the center of the coil between PHI2 and PHI0 and between PHI3 and PHI1. This permits the use of a transistor driver such as the ULN2803 where the output states are open and near ground. When open, no current flows in the coil. When near ground, current is pulled through the coil. The ULN2803 is not capable of sourcing current. Rather, it's capability is limited to sinking current. Thus current is always "pulled" through the coils and this gives rise to the term "unipolar".

Bi-Polar Stepper.

Figure #3 titled "Bi-Polar Stepper" illustrates another arrangement. Note that in this case, there is no battery feed at the center of the windings.

Assume, near +12 is applied to PHI0 and near ground to PHI2. Note that this has the same effect as grounding PHI0 in Figure #1. (PHI3 and PHI1 are bith at ground). Assume, this makes point A North. Point D is then South.

Assume, now that, in addition, PHI1 is near +12 and PHI3 is near ground The motor advances clockwise as shown in Figure #3B.

Assume this condition on PHI1 and PHI3 is maintained, but PHI0 and PHI2 are at ground. The rotor advances as shown in Figure #3C.

You should be able to convince yourself that this is very similar to the theory of the unipolar;

          Phi3 Phi2 Phi1 Phi0      

          GRD  GRD  GRD  +12
          GRD  GRD  +12  +12
          GRD  GRD  +12  GRD
          GRD  +12  +12  GRD

          GRD  +12  GRD  GRD
          +12  +12  GRD  GRD
          +12  GRD  GRD  GRD
          +12  GRD  GRD  +12

          GRD  GRD  GRD  +12
          GRD  GRD  +12  +12
          etc

     Table #3 - Bi-polar Stepper Sequence
Note that in this arrangement a transistor in not only required to sink (or pull) current when at ground, but also to source (or push) current when near +12.

The L293 shown in Figure #4 is a quad push-pull driver. That is, each section is capable of sourcing (pushing) or sinking current. Note that when the input is at a TTL logic 1, transistor Q_up is on and Q_down is off and current is sourced. When the input is at a TTL logic zero, transistor Q_up is off and transistor Q_down is on. Thus, in this state, the driver is capable of pulling current.

[Note. Many users and manufacturers refer to the L293 as a dual "H" bridge and indeed, this is one configuration which will be treated in controlling a DC motor in Tutorial #5. In fact, the device is more appropriately termed a quad push-pull driver.]

You should be able to convince yourself that the above table is realized by the Stamp exerting the familiar TTL states shown in Table #2.

[Note. The transistor - transistor logic or TTL family of logic is inherently linked to +5 VDC and ground. In fact, the Stamp uses a CMOS technology. However, the generic term TTL logic states has taken on a wider meaning to indicate logic that defines a one as being near +5V and a logic zero as being near ground.]

Unlike the unipolar, where current is only "pulled" though a winding, current is may either be pushed or pulled through a winding. Thus, the term "bipolar".

Although the stepper provided with the Starter Package is a unipolar stepper, it may be configured as a bipolar stepper by simply ignoring the battery feed at the center taps as shown in Figure #5.

Here again, you may wish to wire the circuitry and run the various routines (PATT_1 - PATT_7) from Tutorial #2.

Unipolar vs Bi-Polar.

A natural question is "why bi-polar and uni-polar". Isn't one better than the other. I'm not entirely sure, but can speculate.

Magnetic field is proportional to the number of turns times current (NI). Thus with the bi-polar arrangement, where the full coil between PHI2 and PHI0 is used, a manufacturer can implement a stepper with half the number of turns assuming the current is the same or operate the stepper with half the current if the number of turns is the same. In operating with half the current, the diameter of the wire may be smaller, leading to a more compact design.

On the flip side, the bi-polar arrangement requires a more complex driver; one that is capable of both pushing and pulling current. However, if the current is halved over the unipolar, one might argue that the driver need not employ such high gain transistors. With the unipolar arrangement there is only one Vce transistor drop, and with the bipolar, there are two, which causes the effective voltage across the motor winding to be somewhat less. Thus, there are a good many arguments on both sides.

In taking apart some 100 floppy disk drives, my experience as been that about seven out of eight are unipolar leading me to believe that not all that much current is required to control a disk drive. That is, the cost of doubling of the required current over the bipolar approach is masked by the simplification of the driver. After all, a PC requires power for far more than a floppy drive and if a halving of the current to run a floppy is but a two percent reduction, there is really no cost saving in going with the bipolar.

However, in looking at bigger motors I tend to see a lot more bipolar. I conclude that the halving of the required current over the unipolar approach becomes a factor. Extra current drawn from a power supply is free provided the supply has the reserve. However, it becomes mighty expensive when the designer is forced to go with a supply having twice the capacity.

Programs.

The following programs build on the stepping motor routines presented in Tutorial #2. All may be run with either motor configuration or with simply the four LEDs. I debugged them using the LEDs and thus you may wish to shorten the pauses if you are driving a motor.

The earlier PATT routines progressed from the very simple to adding bi-directional, mode and speed control. In these routines I have added distance, that is the number of revolutions. In the interest of not becoming unnecessarily complex, the mode option was deleted. All of these routines are run in the half step mode.

Program PATT_8.BS2 is an extension of PATT_7 which adds the capability to specify a distance the motor is to travel. PATT_9.BS2 extends this by executing a task sequence using an array in RAM. However, RAM is quite limited and PATT_10.BS2 illustrates how the same may be implemented using the data section of the Stamp's EEPROM. Finally, PATT_11.BS2 extends this to show how several selectable task sequences may be implemented.

If you are relatively new to programming, you may find there comes a point where you get lost. Don't worry. Skip the rest. After-all, it's a hobby!

I have found that in teaching C to college freshmen that about the best I can do is conditional branches (IF and BRANCH), looping (FOR-NEXT), functions (GOSUB) and arrays, and if you have gotten to this point, you have done pretty much all of that. I have found that pointers and structures are best left for when I again see them in their third or fourth year and the material in the last two routines is of this nature.

I have included the material as pointers are very powerful and the ability to use pointers on the Basic Stamp is in my experience totally undocumented and there may be readers who readily grasp the concepts and can use the material and avoid the countless hours I have spent 'fiddling'.

Program PATT_8.BS2.

The following program is very much like PATT_7.BS2 from Tutorial #2, except that a distance parameter is introduced. Note that the switch that was formerly used to control the mode; either half or full step is not used.

' PATT_8.BS2
'
' Outputs stepping motor patterns to four LEDs on lowest four bits.
' Bidirectional control (S15).  Variable speed (S13 and S12). 
' 
' Motor turns 9 turns when PB11 is depressed.  
'
' Note that S14 is not used.  This routine uses half step mode.
'
' P. H. Anderson, Dec 13, '97

REVS      VAR BYTE
STEPS     VAR WORD
SPEED     VAR BYTE  
T_VAR     VAR BYTE
DIR       VAR BYTE
INDEX     VAR BYTE
PATT      VAR BYTE

     DIRA=$F        ' low four bits are outputs
     
TOP:
     IF (IN11=1) THEN TOP     ' wait for depression of PB11
     DEBUG "WORKING", CR

     REVS = 9       ' nine complete revolutions
     STEPS = REVS * 48   ' number of steps
     SPEED=(IND & %0011) ' S13 and S12
     LOOKUP SPEED, [50, 75, 100, 200], T_VAR
     DIR= IN15      ' read the direction
     BRANCH DIR, [OTHER_W_H, OTHER_W_H]

OTHER_W_H:     
     FOR INDEX=7 TO 0
        LOOKUP INDEX, [$1, $3, $2, $6, $4, $C, $8, $9], PATT
        OUTA = PATT
        PAUSE T_VAR
        STEPS = STEPS - 1     ' decrement number of steps left
        IF (STEPS = 0) THEN DONE
     NEXT
     GOTO OTHER_W_H 

ONE_W_H:  
     FOR INDEX=0 TO 7
        LOOKUP INDEX, [$1, $3, $2, $6, $4, $C, $8, $9], PATT
        OUTA = PATT
        PAUSE T_VAR
        STEPS=STEPS-1    ' decrement number of steps left
        IF (STEPS=0) THEN DONE
     NEXT
     GOTO ONE_W_H   

DONE:
     DEBUG "DONE"
     OUTA = 0  ' turn off all windings
DONE_1: GOTO DONE_1
Discussion.

A new variable REVS has been introduced which defines the number of complete revolutions. In this program, it is hard coded to 9.

The stepping motor provided with the Basic Starter Package advances 7.5 degrees with each half step. Thus, a full revolution requires 48 steps and thus STEPS is 48 * REVS.

STEPS is decremented and tested to determine if it is zero, in each pass through both OTHER_W_H and ONE_W_H.

Note that at DONE, all outputs are brought to zero which cuts current to all stepping motor windings. In practice, one may or may not want to do this. If the stepper is controlling a robotic device and the stepper is under load, one may wish to keep a winding operated so as to maintain it in its current position.

Program PATT_9.BS2.

Aside from pushbutton PB11, this program does not read information from the switches. Rather the action to be taken as to direction and speed is determined by variable ACTION as shown.

     7 6 5 4 3 2 1 0 
           ^ ^ determine speed     
     ^ determines direction
Note that bits 6, and the entire lower nibble of ACTION are not used.

The number of revolutions is determined by variable REVS.

Note that two arrays, each consisting of five elements are declared, so as to implement a task sequence. After pushbutton PB11 is depressed, the program reads ACTION and REVS from the zeroth element in ACT_ARRAY and REV_ARRAY and acts on this, turning the motor the desired number of turns, in the specified direction at the specified speed. Upon completion of each task, the program returns to fetch the next value of REVS and ACTION and proceeds to execute that task. This process continues until the ACTION is %1111 1111 which is used to indicate the end of the task sequence.

' PATT_9.BS2
'
' Outputs stepping motor patterns to four LEDs on lowest four bits.
'
' Performs a task sequence which is stored in arrays ACT_ARRAY and 
' REV_ARRAY. 
'
' Bit 7 of elements in ACT_ARRAY determines direction. Bits 5 and 4 
' determine speed.  Turns motor in specified direction at specified
' speed the number of turns identified in the corresponding element
' of REV_ARRAY.  If element in ACT_ARRAY is $FF, the sequence is 
' terminated.
'
' Intent of the program is to illustrate arrays.   
'
' P. H. Anderson, Dec 13, '97

ACT_ARRAY VAR BYTE (5)   ' up to five task in the sequence
REV_ARRAY VAR BYTE (5)

N         VAR BYTE  ' array index
ACTION    VAR BYTE  ' action to take; direction and speed 
REVS      VAR BYTE  ' number of revs to turn with defined action
STEPS     VAR WORD  ' number of steps = 48 * revs

SPEED     VAR BYTE  
T_VAR     VAR BYTE
DIR       VAR BYTE
INDEX     VAR BYTE
PATT      VAR BYTE

     DIRA=$F        ' low four bits are outputs

     'initialize the array

     ACT_ARRAY(0)=%10000000   ' one way at high speed
     REV_ARRAY(0)=3

     ACT_ARRAY(1)=%10010000   ' same way, slower
     REV_ARRAY(1)=1

     ACT_ARRAY(2)=%00000000   ' other way, high speed
     REV_ARRAY(2)=2

     ACT_ARRAY(3)=%11111111   ' end of the sequence
     ' note that other elements are not initialized
TOP:
     IF (IN11=1) THEN TOP
     DEBUG "WORKING"
     
     FOR N = 0 TO 4
         ACTION = ACT_ARRAY(N)     ' read action
         DEBUG HEX?ACTION
         IF ACTION = %11111111 THEN DONE
         REVS = REV_ARRAY(N)       ' read revs
         STEPS = REVS * 48         ' number of steps to execute
         SPEED= (ACTION >> 4) & $03     ' get spped bits
         LOOKUP SPEED, [50, 75, 100, 200], T_VAR
         DIR= ACTION.BIT7          ' get the direction
         BRANCH DIR, [OTHER_W_H, OTHER_W_H]

OTHER_W_H:     
        FOR INDEX=7 TO 0
           LOOKUP INDEX, [$1, $3, $2, $6, $4, $C, $8, $9], PATT
           OUTA = PATT
           PAUSE T_VAR
           STEPS = STEPS - 1
           IF (STEPS = 0) THEN NEXT_TASK     ' if done with this 
                              ' task, advance to next
                              ' one
        NEXT
        GOTO OTHER_W_H   

ONE_W_H:  
        FOR INDEX=0 TO 7
           LOOKUP INDEX, [$1, $3, $2, $6, $4, $C, $8, $9], PATT
           OUTA = PATT
           PAUSE T_VAR
           STEPS=STEPS-1
           IF (STEPS=0) THEN NEXT_TASK  ' if done with this 
                              ' task, advance to next
                              ' one
        NEXT
        GOTO ONE_W_H     

NEXT_TASK:
     NEXT

DONE:     
     DEBUG "DONE"
     OUTA = 0  ' turn off all windings
DONE_1: GOTO DONE_1
Discussion.

Note that an array is declared in the following fashion

     ACT_ARRAY VAR BYTE (5)
indicating five contiguous bytes. The elements are ACT_ARRAY(0), (1), (2), (3) and (4).

Thus on each pass through the outer FOR NEXT N, the ACTION and REVS are read from the two arrays. If the action is $FF (%1111 1111), the program terminates. Otherwise, the direction and the speed bits are extracted from ACTION, the number of STEPS is calculated and the task is executed in the same manner as PATT_8.BS2.

However, in this case, a task sequence is being executed and thus when the number of STEPS has been decremented to zero, the program again loops within the outer FOR NEXT loop, thus reading the next task and acting on it and this continues until the ACTION indicates it is the last or until the last element of the arrays have been read

In most PC programming, arrays are very common, but then one has at a minimum of some 640K of RAM less that required to run the program. With the Basic Stamp, there are 16 words, which you might wish to examine using the alt-m command. Three words, DIRS, INS and OUTS are dedicated to input and output control, leaving a total of 13 words or 26 bytes. Thus, in practice, an array is not too practical.

But, a PC has another storage resource; disk drives, and if indeed one were limited to 26 bytes of RAM, which is of course quite foolish, one would most probably manipulate data in a file. The analogy with the Stamp is EEPROM. Much as data on a PC "can" be manipulated in a file, data on the Stamp can also be manipulated in EEPROM.

Program PATT_10.BS2.

This program uses EEPROM to perform the same function as PATT_9.BS2. The reader might spend a few moments with the tutorial relating to EEPROM for a more complete understanding of the following routines.

' PATT_10.BS2
'
' Outputs stepping motor patterns to four LEDs on lowest four bits.
'
' Performs a task sequence which is stored as consecutive entries 
' in EEPROM quite like an array.  Elements 0, 2, 4, etc identify
' the action.  Elements 1, 3, 5 etc identify the number of
' revolutions. 
'
' Otherwise, program is the same as PATT_9.BS2
'
' Intent of the program is to illustrate implementation of arrays in 
' EEPROM.   
'
' P. H. Anderson, Dec 13, '97

TASK_SEQ_0 DATA %10000000, 3, %10010000, 1, %00000000, 2, %11111111
' 3 turns in one direction at high speed
' 1 turn at lower speed
' 2 turns in the other direction

TASK_ADR_TABLE DATA WORD TASK_SEQ_0     ' address of 0th element of 
                                   ' TASK_SEQ_0

TASK_PTR  VAR WORD       ' current EEPROM address
N         VAR BYTE       ' loop index
ACTION    VAR BYTE       ' action to be performed
REVS      VAR BYTE  
STEPS     VAR WORD       ' number of steps (48 X revs)

SPEED     VAR BYTE  
T_VAR     VAR BYTE
DIR       VAR BYTE
INDEX     VAR BYTE
PATT      VAR BYTE

     DIRA=$F        ' low four bits are outputs
     
TOP:
     IF (IN11=1) THEN TOP
     DEBUG "WORKING"

     READ TASK_ADR_TABLE, TASK_PTR.BYTE0     ' get the address of
     READ TASK_ADR_TABLE+1, TASK_PTR.BYTE1' the task seq
     ' note that the address is stored, low byte and then high byte 

     FOR N = 0 TO 255
        READ TASK_PTR+(2*N), ACTION     ' in C; action = *(p+2*n);

         DEBUG HEX?ACTION
         IF ACTION = %11111111 THEN DONE
         READ TASK_PTR+(2*N)+1, REVS    ' in C; revs = *(p+2*n+1) 
         STEPS = REVS * 48         ' calculate the number of steps
         SPEED= (ACTION >> 4) & $03     ' get speed bits
         LOOKUP SPEED, [50, 75, 100, 200], T_VAR
         DIR= ACTION.BIT7     ' read the direction
         BRANCH DIR, [OTHER_W_H, OTHER_W_H]

OTHER_W_H:     
        FOR INDEX=7 TO 0
           LOOKUP INDEX, [$1, $3, $2, $6, $4, $C, $8, $9], PATT
           OUTA = PATT
           PAUSE T_VAR
           STEPS = STEPS - 1
           IF (STEPS = 0) THEN NEXT_TASK     ' proceed to next task
        NEXT
        GOTO OTHER_W_H   

ONE_W_H:  
        FOR INDEX=0 TO 7
           LOOKUP INDEX, [$1, $3, $2, $6, $4, $C, $8, $9], PATT
           OUTA = PATT
           PAUSE T_VAR
           STEPS=STEPS-1
           IF (STEPS=0) THEN NEXT_TASK  ' proceed to next task
        NEXT
        GOTO ONE_W_H     

NEXT_TASK:
     NEXT

DONE:     
     DEBUG "DONE"
     OUTA = 0  ' turn off all windings
DONE_1: GOTO DONE_1

Discussion.

Note that the array as now been defined in EEPROM as DATA. Bytes 0, 2, 4, etc refer to the action and bytes 1, 3 and 5 refer to the number of revolutions. (One might think of this as an array of structures, each element consisting of a .ACTION and a .REVS byte).

Note that in C, one declares an array of such structures;

     struct task
     {
        unsigned char ACTION
        unsigned char REVS
     };

     struct task task_seq_0[10]
Thus task_seq_0 consists of ten tasks, which may be referred to as task_seq0], task_seq[1], etc and also a pointer task_seq_0 which contains the address of where the array begins. That is, the address of element task_seq_0[0].

I have been unsuccessful in finding a similarly quick way to refer to the address of the zeroth element using PBASIC.

However, I found a work around;

     TASK_ADR_TABLE DATA WORD TASK_SEQ_0.  
Note that by fetching the word at TASK_ADR_TABLE, one is able to determine the zeroth element of the array.

There is however, one very critical point. A word appears to be stored as low byte and then high byte. Thus, in the following code, note that the first byte is read and placed in the low byte of the pointer, and the next byte is placed in the high byte.

     
     READ TASK_ADR_TABLE, TASK_PTR.BYTE0     ' get the address of
     READ TASK_ADR_TABLE+1, TASK_PTR.BYTE1   ' the task seq
     ' note that the address is stored, low byte and then high byte 
Thus, now having a TASK_PTR, the ACTION and REVS portions of the nth element of the array is at address;
     TASK_PTR + (2*N)
and  TASK_PTR + (2*N) + 1
[Note that those parenthesis are critical. Recall that lacking the parenthesis PBASIC evaluates left to right. Thus lacking the above parenthesis, the above will evaluate to;
     (TASK_PTR+2) * N
which most certainly is incorrect.]

The ACTION and REVS are read from EEPROM;

     READ TASK_PTR+(2*N), ACTION   ' in C; action = *(p+2*n);
     READ TASK_PTR+(2*N)+1, REVS   ' in C; revs = *(p+2*n+1) 
From here, the program is the same as PATT_9.BS2.

Program PATT_11.BS2.

This program defines four task sequences. Switches S15 and S14 determine the specific sequence to be performed.

' PATT_11.BS2
'
' Outputs stepping motor patterns to four LEDs on lowest four bits.
'
' Performs a task sequence which is stored as consecutive entries 
' in EEPROM quite like an array.  Elements 0, 2, 4, etc identify
' the action.  Elements 1, 3, 5 etc identify the number of revs. 
'
' The specific task sequence is identified by switches S15 and S14.
' The state of these two switches is mapped into an index in the
' TASK_ADR_TABLE which may be thought of as an array of pointers. 
' The program then executes the specified task sequence.
'
' Otherwise, program is the same as PATT_10.BS2
'
' Intent of the program is to illustrate implementation of arrays of
' arrays in EEPROM.   
'
' P. H. Anderson, Dec 13, '97

TASK_SEQ_0 DATA %10000000, 3, %10010000, 1, %00000000, 2, %11111111
' task_0: 3 turns high speed, 1 turn lower speed, 2 turns other way
TASK_SEQ_1 DATA %00110000, 1, %10110000, 3, %11111111
' task_1: 1 turn other way (slow) and then 3 turns one way 
TASK_SEQ_2 DATA %10110000, 10, %11111111
' task_2: 10 slow turns
TASK_SEQ_3 DATA %00000000, 5, %10000000, 5, %11111111
' task_3: five quick turns in each direction

TASK_ADR_TABLE DATA WORD TASK_SEQ_0, WORD TASK_SEQ_1, 
                   WORD TASK_SEQ_2, WORD TASK_SEQ_3
'the above two lines must all be on one line.

TASK_SW   VAR BYTE  ' state of switches S15, S14
TASK_PTR  VAR WORD  ' address of current task
N         VAR BYTE
ACTION    VAR BYTE
REVS      VAR BYTE
STEPS     VAR WORD
SPEED     VAR BYTE  
T_VAR     VAR BYTE
DIR       VAR BYTE
INDEX     VAR BYTE
PATT      VAR BYTE

     DIRA=$F        ' low four bits are outputs
     
TOP:
     IF (IN11=1) THEN TOP
     DEBUG "WORKING"

     TASK_SW = (IND >> 2)     ' S15 and S14
     READ TASK_ADR_TABLE+(2*TASK_SW), TASK_PTR.BYTE0
     READ TASK_ADR_TABLE+(2*TASK_SW)+1, TASK_PTR.BYTE1
     DEBUG HEX4 ? TASK_PTR
     ' map switch setting to start address of task sequence

     FOR N = 0 TO 255    ' now execute the task sequence
         READ TASK_PTR+(2*N), ACTION

         DEBUG HEX?ACTION
         IF ACTION = %11111111 THEN DONE
         READ TASK_PTR+(2*N)+1, REVS
         STEPS = REVS * 48
         SPEED= (ACTION >> 4) & $03
         LOOKUP SPEED, [50, 75, 100, 200], T_VAR
         DIR= ACTION.BIT7     ' read the direction
         BRANCH DIR, [OTHER_W_H, OTHER_W_H]

OTHER_W_H:     
        FOR INDEX=7 TO 0
           LOOKUP INDEX, [$1, $3, $2, $6, $4, $C, $8, $9], PATT
           OUTA = PATT
           PAUSE T_VAR
           STEPS = STEPS - 1
           IF (STEPS = 0) THEN NEXT_TASK
        NEXT
        GOTO OTHER_W_H   

ONE_W_H:  
        FOR INDEX=0 TO 7
           LOOKUP INDEX, [$1, $3, $2, $6, $4, $C, $8, $9], PATT
           OUTA = PATT
           PAUSE T_VAR
           STEPS=STEPS-1
           IF (STEPS=0) THEN NEXT_TASK
        NEXT
        GOTO ONE_W_H     

NEXT_TASK:
     NEXT

DONE:
     DEBUG "DONE"
     OUTA = 0  ' turn off all windings
DONE_1: GOTO DONE_1
Discussion.

Note that four sequences of varying length have been defined.

In addition, a table of addresses of the zeroth element of each task sequence is generated;

          TASK_ADR_TABLE DATA WORD TASK_SEQ_0, WORD TASK_SEQ_1, 
                              WORD TASK_SEQ_2, WORD TASK_SEQ_3
Thus, on entering the program, the switches S15 and S14 are read, resulting in a number 0, 1, 2 or 3.

The TASK_PTR is then initialized to the appropriate task sequence;

     TASK_SW = (IND >> 2)     ' S15 and S14
     READ TASK_ADR_TABLE+(2*TASK_SW), TASK_PTR.BYTE0
     READ TASK_ADR_TABLE+(2*TASK_SW)+1, TASK_PTR.BYTE1
Again, note the low byte first and the importance of the parenthesis.

The balance of the program is implemented precisely the same as program PATT_10.BS2.