================================= Unipolar stepper motor controller ================================= by Art Prewitt, N4PT, Aprewitt@prodigy.net The controller was designed to test/experiment with some of the surplus stepper motors available from old floppy drives and old dot matrix printers. There is a lot of information available on the web related to these motors (http://www.doc.ic.ac.uk/~ih/doc/stepper/). I am driving this circuit from the parallel port of a laptop computer. It only takes 4 bits for all the controls. I have the mode selection implemented in switches. It was necessary to add buffers between the parallel port and the PIC inputs. The laptop program was written in Borland Delphi using a free LPT component obtained from the Web. Circuit: The 555 timer supplies an almost square wave pulse to RB0 of the PIC. The 555 components allow a range from about 1 Ms to 50 Ms. this could be done in the PIC but I wanted to be able to test different motors. I found that this timing varies significantly from motor to motor. It is also affected by the drive mode. So it was easier to do it this way. If the pulse width is too narrow the motor will stall, if the pulse width is too wide the motor will turn very slowly. For the smaller motors 4 to 6 Milliseconds seems to work ok. I have noticed that 555's are very noisy chips so bypass well around the 555. Basically, the PIC activates a motor coil or coils every high to low transition of the 555-timer input. The motor drive circuit uses TIP120 transistors. This is over kill for the small floppy motors. They seem to draw about 300 Ma. So a UNL2003 could be used for the smaller motors (less than 0.5A). However the dot matrix stepper motors seem to draw about 1.2 amps or more which is easily handled by the TIP120's but not the UNL2003. I did not heat sink the TIP120's however, anything larger than a few amps will require heatsinks. It also helps to put a cap from the Motor V+ to ground to help with the motors current demand. Be sure to use the proper voltage cap. Also a series resistor (8 to 20 Ohm 20 Watts) in series with the motor V+ is useful for testing unknown motors. PIC: This controller uses a 16F84 running at 10 MHz. 10 MHz was chosen because I had a 10 MHz crystal. There is nothing timing critical here so it should run at almost speed as long as the processor clock is a couple of times greater than the 555 period. Contols: Refer to schematic Mode: The mode input is coded as a binary number. For information on the various drives see the URL listed above. 0 0 Two Phase Drive 0 1 Half Step Drive 1 0 Wave Drive 0 0 Does Nothing No Step Direction: 0 Counter Clockwise or Anti-clockwise 1 Clockwise Off/Step: Motor steps if this is a 1 (high). This was intended to provide continuous motor rotation. There is a little debounce (see code) , so it might work with a switch. I have not tried this. See Pulses. Off has no effect on the Pulses input. Hold/Release: The stepper motor will provide holding torque if one or more coils are energized even though the motor is not turning. If this input is 1 (high) holding torque is maintained, if it is 0 ( low ) there is no holding drive. Watch the motor current here, some of these steppers can many draw amps. Pulses: For each high/low transition the motor will step. This is the TMR0 input so it will accept up to 255 pulses within the period of the 555. There is no provision to detect overflow. Pulse width etc. should conform to the 16F84 input requirements. Motor connections: The diagram shows the wire colors for a Japan Servo KPM4-002. This motor is discussed in the URL listed above. It is easy to figure out other motor connections. Trial and error worked for me. Other: The PIC code was done with the CC5X C compiler running under MPLAB. I like to use C and a restricted version of the CC5X compiler is available free. I use a homemade Tait style programmer with PICserial programmer software. Cheap! The C code follows. This is a very simple program but seems to work well. I have also included the ASM file produced by the compiler. Good luck 73, Art N4PT Aprewitt@prodigy.net ================================ C Program for Stepper controller ================================ /* StepA C Version 0 */ /* Stepper controller */ /* A. Prewitt */ /* N4PT */ /* Aprewitt@prodigy.net */ /* Jan 9 2000 */ /* CC5X C Compiler */ /* NO WDT */ #include <16f84.h> /* Part of CC5X */ #pragma bit Pulses = PORTA.4 /* Drives TOCKI a step for each pulse */ #pragma bit HoldIn = PORTB.3 /* 0= release 1=hold */ #pragma bit StepIn = PORTB.4 /* 0=NoStep 1=step */ #pragma bit DirIn = PORTB.5 /* 0=CCW 1=CW */ #pragma bit ModeLoIn = PORTB.6 /* 00 = 2ph 01 =1/2step 10=wave 11=nil */ #pragma bit ModeHiIn = PORTB.7 #define TwoPhase 0 #define HalfStep 1 #define WaveDrive 2 #define NoPhase 3 #define True 1 #define False 0 #define CW 1 #define CCW 0 char Index ; char Mode ; char MotorVal ; bit Dir ; bit StepDly ; bit Step ; #pragma bit ModeLo = Mode.0 #pragma bit ModeHi = Mode.1 char TwoPhase_Tbl(char i ) /* Two Phase Drive look up table */ { skip(i); #pragma return[] = 0xc #pragma return[] = 0x6 #pragma return[] = 0x3 #pragma return[] = 0x9 } char WaveDrive_Tbl ( char i) /* Wave Drive Look up Table */ { skip(i) ; #pragma return[] = 0x8 #pragma return[] = 0x4 #pragma return[] = 0x2 #pragma return[] = 0x1 } char HalfStep_Tbl(char i) /* Half Step or Micro Step Look up Table */ { skip(i) ; #pragma return[] = 0x8 #pragma return[] = 0xc #pragma return[] = 0x4 #pragma return[] = 0x6 #pragma return[] = 0x2 #pragma return[] = 0x3 #pragma return[] = 0x1 #pragma return[] = 0x9 } void main(void) { INTCON = 0x10 ; /* Enable the INTE Interrupt bit Interrupts not Used */ /* The INTF Flag is set on each high to low */ /* transition of the 555 */ /* The motor drive is on for 1 period of the 555 */ OPTION = 0x28 ; /* TOCKI enabled and Prescaler to WDT */ PORTA = 0b.00000 ; TRISA = 0b.10000 ; /* RA4 as TMR0 input -- RA0 - RA3 as motor Drives */ PORTB = 0b.00000000 ; TRISB = 0b.11111111 ; /* all B as inputs */ PORTA = 0 ; INTF = 0 ; /* Clear ALL */ Index = 0 ; TMR0 = 0 ; Mode = 0 ; MotorVal = 0 ; Step = 0 ; StepDly = 0 ; Dir = CCW ; do { if (Dir != DirIn ) /* Adjusts Table index if Direction change */ { if (DirIn == CW ) { Index = Index + 2 ; /* Ahead 2 table entries */ Dir = CW ; } else { Index= Index - 2 ; /* Back 2 table entries */ Dir = CCW ; } } if ( INTF ) /* True if RB0 High to Low */ { Step = StepIn & StepDly ; /* Cheap debounce of the Step input */ if ((Step) || ( TMR0 > 0 )) /* Either Step or TMR0 Can move motor */ { ModeLo = ModeLoIn; /* Get Mode selection */ ModeHi = ModeHiIn ; Mode = Mode & 0x03 ; /* Make sure mode is 0 - 3 */ switch ( Mode) /* Which mode */ { case TwoPhase: Index = Index & 0x03 ; MotorVal = TwoPhase_Tbl(Index); break ; case HalfStep: Index = Index & 0x07 ; MotorVal = HalfStep_Tbl(Index); break ; case WaveDrive: Index = Index & 0x03 ; MotorVal = WaveDrive_Tbl (Index); break ; case NoPhase: break ; } PORTA = MotorVal ; /* Send Table value to Motor */ if (Dir == CW ) /* Adjust Table index for Direction */ Index++ ; else Index-- ; if ( TMR0 > 0 ) /* Was it TMR0 ? */ TMR0-- ; /* Yep Decrement TMR0 */ else Step = 0 ; /* No Clear Step */ } else { if (!HoldIn ) /* No Motor action just 555 timer */ PORTA = 0 ; /* Clear the port is No Hold required */ else PORTA = MotorVal ; /* HOLD Send last value to keep locked */ } INTF = False ; /* Clear the INTF flag */ StepDly = StepIn ; /* cheap debounce of Step */ } } while(True); /* Do Forever */ } ASM Output of Compiler ; CC5X Version 3.0E, Copyright (c) B. Knudsen Data ; C compiler for the PICmicro family ; ************ 9. Feb 2000 23:27 ************* processor 16F84 radix DEC TMR0 EQU 0x01 OPTION_REG EQU 0x81 PCL EQU 0x02 PORTA EQU 0x05 TRISA EQU 0x85 PORTB EQU 0x06 TRISB EQU 0x86 PCLATH EQU 0x0A INTCON EQU 0x0B Zero_ EQU 2 RP0 EQU 5 INTF EQU 1 HoldIn EQU 3 StepIn EQU 4 DirIn EQU 5 ModeLoIn EQU 6 ModeHiIn EQU 7 Index EQU 0x0D Mode EQU 0x0E MotorVal EQU 0x0F Dir EQU 0 StepDly EQU 1 Step EQU 2 ModeLo EQU 0 ModeHi EQU 1 i EQU 0x0C i_2 EQU 0x0C i_3 EQU 0x0C GOTO main ; FILE D:\HAM\PIC\MPLAB\MPLAB\STEPPER\STEPA.C ;/* StepA C */ ;/* Stepper controller */ ;/* A. Prewitt */ ;/* N4PT */ ;/* Jan 9 2000 */ ;/* CC5X C Compiler */ ; ; ; ;#include <16f84.h> ; ;#pragma bit Pulses = PORTA.4 /* Drives TOCKI */ ;#pragma bit HoldIn = PORTB.3 /* 0= release 1=hold */ ;#pragma bit StepIn = PORTB.4 /* 0=NoStep 1=step */ ;#pragma bit DirIn = PORTB.5 /* 0=CCW 1=CW */ ;#pragma bit ModeLoIn = PORTB.6 /* 00 = 2ph 01 =1/2step 10=wave 11=nil */ ;#pragma bit ModeHiIn = PORTB.7 ; ;#define TwoPhase 0 ;#define HalfStep 1 ;#define WaveDrive 2 ;#define NoPhase 3 ;#define True 1 ;#define False 0 ;#define CW 1 ;#define CCW 0 ; ;char Index ; ;char Mode ; ;char MotorVal ; ; ;bit Dir ; ;bit StepDly ; ;bit Step ; ; ; ;#pragma bit ModeLo = Mode.0 ;#pragma bit ModeHi = Mode.1 ; ; ;char TwoPhase_Tbl(char i ) /* Two Phase Drive look up table */ ;{ TwoPhase_Tbl MOVWF i ; skip(i); CLRF PCLATH MOVF i,W ADDWF PCL,1 ; #pragma return[] = 0xc RETLW .12 ; #pragma return[] = 0x6 RETLW .6 ; #pragma return[] = 0x3 RETLW .3 ; #pragma return[] = 0x9 RETLW .9 ;} ; ; ;char WaveDrive_Tbl ( char i) /* Wave Drive Look up Table */ ;{ WaveDrive_Tbl MOVWF i_2 ; skip(i) ; CLRF PCLATH MOVF i_2,W ADDWF PCL,1 ; #pragma return[] = 0x8 RETLW .8 ; #pragma return[] = 0x4 RETLW .4 ; #pragma return[] = 0x2 RETLW .2 ; #pragma return[] = 0x1 RETLW .1 ;} ; ;char HalfStep_Tbl(char i) /* Half Step or Micro Step Look up Table */ ;{ HalfStep_Tbl MOVWF i_3 ; skip(i) ; CLRF PCLATH MOVF i_3,W ADDWF PCL,1 ; #pragma return[] = 0x8 RETLW .8 ; #pragma return[] = 0xc RETLW .12 ; #pragma return[] = 0x4 RETLW .4 ; #pragma return[] = 0x6 RETLW .6 ; #pragma return[] = 0x2 RETLW .2 ; #pragma return[] = 0x3 RETLW .3 ; #pragma return[] = 0x1 RETLW .1 ; #pragma return[] = 0x9 RETLW .9 ;} ; ; ; ;void main(void) ;{ main ; ; ; ;INTCON = 0x10 ; /* Enable the INTE Interrupt bit Interrupts not Used */ MOVLW .16 MOVWF INTCON ; /* The INTF Flag is set on high to low transitions of the 555 */ ; /* The motor drive is on for 1 period of the 555 */ ; ;OPTION = 0x28 ; /* TOCKI and Prescaler to WDT */ MOVLW .40 BSF 0x03,RP0 MOVWF OPTION_REG ;PORTA = 0b.00000 ; BCF 0x03,RP0 CLRF PORTA ;TRISA = 0b.10000 ; /* RA4 as TMR0 input -- RA0 - RA3 as motor Drives */ MOVLW .16 BSF 0x03,RP0 MOVWF TRISA ;PORTB = 0b.00000000 ; BCF 0x03,RP0 CLRF PORTB ;TRISB = 0b.11111111 ; /* all B as inputs */ MOVLW .255 BSF 0x03,RP0 MOVWF TRISB ;PORTA = 0 ; BCF 0x03,RP0 CLRF PORTA ;INTF = 0 ; /* Clear ALL */ BCF 0x0B,INTF ;Index = 0 ; CLRF Index ;TMR0 = 0 ; CLRF TMR0 ;Mode = 0 ; CLRF Mode ;MotorVal = 0 ; CLRF MotorVal ;Step = 0 ; BCF 0x10,Step ;StepDly = 0 ; BCF 0x10,StepDly ;Dir = CCW ; BCF 0x10,Dir ; ; ;do ;{ ; ; ; if (Dir != DirIn ) /* Adjusts Table index if Direction change */ m001 BTFSC 0x10,Dir GOTO m002 BCF 0x03,RP0 BTFSC 0x06,DirIn GOTO m003 GOTO m005 m002 BCF 0x03,RP0 BTFSC 0x06,DirIn GOTO m005 ; { ; if (DirIn == CW ) m003 BCF 0x03,RP0 BTFSS 0x06,DirIn GOTO m004 ; { ; Index = Index + 2 ; /* Ahead 2 table entries */ MOVLW .2 ADDWF Index,1 ; Dir = CW ; BSF 0x10,Dir ; } ; else GOTO m005 ; { ; Index= Index - 2 ; /* Back 2 table entries */ m004 MOVLW .2 SUBWF Index,1 ; Dir = CCW ; BCF 0x10,Dir ; } ; } ; ; if ( INTF ) /* True if RB0 High to Low */ m005 BTFSS 0x0B,INTF GOTO m001 ; { ; ; Step = StepIn & StepDly ; /* Cheap debounce of the Step input */ BSF 0x10,Step BCF 0x03,RP0 BTFSS 0x06,StepIn BCF 0x10,Step BTFSS 0x10,StepDly BCF 0x10,Step ; ; ; if ((Step) || ( TMR0 > 0 )) /* Either Step or TMR0 Can move motor */ BTFSC 0x10,Step GOTO m006 MOVF TMR0,W BTFSC 0x03,Zero_ GOTO m014 ; { ; ; ModeLo = ModeLoIn; /* Get Mode selection */ m006 BCF 0x0E,ModeLo BCF 0x03,RP0 BTFSC 0x06,ModeLoIn BSF 0x0E,ModeLo ; ModeHi = ModeHiIn ; BCF 0x0E,ModeHi BTFSC 0x06,ModeHiIn BSF 0x0E,ModeHi ; Mode = Mode & 0x03 ; /* Make sure mode is 0 - 3 */ MOVLW .3 ANDWF Mode,1 ; ; switch ( Mode) /* Which mode */ MOVF Mode,W BTFSC 0x03,Zero_ GOTO m007 XORLW .1 BTFSC 0x03,Zero_ GOTO m008 XORLW .3 BTFSC 0x03,Zero_ GOTO m009 XORLW .1 BTFSC 0x03,Zero_ GOTO m010 GOTO m010 ; { ; case TwoPhase: ; Index = Index & 0x03 ; m007 MOVLW .3 ANDWF Index,1 ; MotorVal = TwoPhase_Tbl(Index); MOVF Index,W CALL TwoPhase_Tbl MOVWF MotorVal ; ; break ; GOTO m010 ; ; case HalfStep: ; Index = Index & 0x07 ; m008 MOVLW .7 ANDWF Index,1 ; MotorVal = HalfStep_Tbl(Index); MOVF Index,W CALL HalfStep_Tbl MOVWF MotorVal ; ; break ; GOTO m010 ; ; case WaveDrive: ; Index = Index & 0x03 ; m009 MOVLW .3 ANDWF Index,1 ; MotorVal = WaveDrive_Tbl (Index); MOVF Index,W CALL WaveDrive_Tbl MOVWF MotorVal ; ; break ; ; ; case NoPhase: ; ; break ; ; } ; PORTA = MotorVal ; /* Send Table value to Motor */ m010 MOVF MotorVal,W BCF 0x03,RP0 MOVWF PORTA ; ; if (Dir == CW ) /* Adjust Table indexer for Direction */ BTFSS 0x10,Dir GOTO m011 ; Index++ ; INCF Index,1 ; else GOTO m012 ; Index-- ; m011 DECF Index,1 ; ; if ( TMR0 > 0 ) /* Was it TMR0 ? */ m012 BCF 0x03,RP0 MOVF TMR0,W BTFSC 0x03,Zero_ GOTO m013 ; TMR0-- ; /* Yep Decrement TMR0 */ DECF TMR0,1 ; else GOTO m016 ; Step = 0 ; /* No Clear Step */ m013 BCF 0x10,Step ; ; } ; else GOTO m016 ; { ; if (!HoldIn ) /* No Motor action just 555 timer */ m014 BCF 0x03,RP0 BTFSC 0x06,HoldIn GOTO m015 ; PORTA = 0 ; /* Clear the port is No Hold required */ CLRF PORTA ; else GOTO m016 ; PORTA = MotorVal ; /* Send last value to keep motor locked */ m015 MOVF MotorVal,W BCF 0x03,RP0 MOVWF PORTA ; } ; INTF = False ; /* Clear the INTF flag */ m016 BCF 0x0B,INTF ; StepDly = StepIn ; /* Poor mans debounce of Step */ BCF 0x10,StepDly BCF 0x03,RP0 BTFSC 0x06,StepIn BSF 0x10,StepDly ; } ; ;} ;while(True); /* Do Forever */ GOTO m001 ;} END