Control of Speed and Direction of a DC Motor Using Power FETs

copyright, H. Paul Roach, Peter H. Anderson, Department of Electrical Engineering
Morgan State University, Baltimore, MD 21239
August, '96

Volume I discusses the use of an output bit from the parallel port to vary the duty cycle of a pulsetrain so as to vary the effective voltage across a motor winding and thus vary the motor's speed.

This section discusses how power FETs may be used to similarly control the speed and also control the direction. It also shows how the cursor keys on a keyboard may be read so as to perform various control functions.

Please refer to Figure #1.

Note the H configuration which is marked in bold in the figure. Assume FET Q_on/off is on.

Assume the left p-channel FET on the battery side of the motor coil and the right n-channel FET on the ground side of the motor are on. Current then flows toward the right through the motor's coil causing it to turn in one direction. For convenience in assigning designations to the FETs, I have assumed clockwise.

However, if the right p-channel FET on the battery side and the left n-channel FET on the ground side are on, and the others are off, the direction of the current is reversed, causing the motor to turn in the opposite direction.

The state of FET Q_on/off determines whether power is supplied to the motor and the "on" duty cycle may be varied so as to vary the speed. Parallel port output Data_0 is used to control this FET. When at logic zero, gate voltage V_g is near ground and the FET is off. When at logic one, V_g is above 4.0 V and the FET is on.

The direction is determined by the state of parallel port output Data_1.

When at logic zero, transistors Q1 and Q3 are off and Q2 is on. Thus the collector of Q3 is near ground. This causes Q_cw_hi (p-channel) to be on as the gate voltage V_g is less than 8.0 Volts and Q_ccw_lo (n-channel) to be off as the gate voltage is less than 2.0 Volts. However, the collector of Q2 is in the opposite state, near +12 V. This causes Q_ccw_hi to be off and Q_cw_lo to be on. Thus, the path is such that current flows through the motor causing it to turn clockwise.

When Data_1 is at logic one, transistor Q3 is on (collector at near 0.3 Volts) and Q2 is off, (collector at +12 V). Thus Q_ccw_hi is on, Q_ccw_lo is on and the other two FETs are off. Thus, the motor turns in the opposite direction.

Notes on the Design.

Transistors Q1, Q2 and Q3 are small NPN transistors (2N2222 or 2N3904) which are used as level shifters. In this case, they are converting TTL logic levels to nominally +12 and 0.0 Volt levels to control the FETs.

The advantage in using FETs is that they have a very small "on" resistance, R_on_ds, of less than 0.18 Ohms and thus they have far less power dissipation than bipolar transistors controlling the same current. The result is that for a given current, an FET may be run with a smaller heat sink or possibly with no heat sink.

Although the data sheet for the IRF530 (n-channel) and its complement IRF9530 (p-channel) indicates a maximum current of 14 A, be careful in this interpretation.

The thermal resistivity R_theta_ja is 80 degrees C / W. Thus, to avoid exceeding the maximum junction temperature T_j_max of 150 C, a power dissipation of 1.5 W should be observed when T_ambient is 25 degrees C. This sets the maximum current with no heat sink to 3.0 A.

When a heat sink having a thermal resistivity of 11 degrees C / W is used, R_theta_jc = 1.67, R_theta_cs = 1.0 and R_theta_sa = 11 degrees C per watt. Thus the effective R_theta_ca is 13.67 degrees C / W. If the ambient is 25 C, the maximum power is 9.1 Watts which translates to a current of nominally 7.0 A.

The subject of thermal resistivity and heat sinks is discussed in Peter H. Anderson, "Applications of Thermodynamics to Electrical Engineering", March, '96, ISBN 0-965337-2-0. This is available from the author for $7.00.

FETs are also attractive in that biasing and current limiting resistors are eliminated. However, on the down side, they are somewhat more expensive than such bi-polar transistors as the TIP-30 series.

Alternatives to the IRF530 are 510, 511, 512, 513, 520, 521, 522, 523, 531, 532, 533, 540, 541, 542 and 543. The differences appear to be limited to R_on_ds which translates into current handling capability and V_d max, which is typically 60 or 100 V.

Such vendors as BG Micro and Jameco stock n-channel devices, but not p-channel. However, DigiKey carries both n-channel and p-channel devices.

IRF530N-ND, n-channel, TO-220AB package $1.51
IRF9530N-ND, p-channel, TO-220AB package $2.40

Transient Suppression.

When turning off the motor, the current is quickly reduced to zero causing an L-di/dt transient which may well be on the order of several thousand volts which with no protection will destroy the FETs. However, the designers of these particular FETs incorporated a zener diode between the source and drain to suppress this transient. Lacking this, a series configuration of back to back zener diodes might be placed across the motor's coil as shown in Figure #2. The breakdown voltage of the zener diodes must be greater than the driving voltage. For example, if +12 VDC is used to control the motor, zeners having breakdowns of 15 or 18 Volts would be suitable.

Programming.

In program DC_MOT1.C, the motor is simply turned one way for a minute, stopped for a minute and then turned the other way. Note that FET Q_on/off is turned on by a logic 0 on the Data 0 output. The direction is controlled by the Data 1 output.

In line 13, the direction is 1 and the motor is energized for 50 ms. In line 15, the motor is turned off for 50 ms. Thus, the for loop has a run time of 0.1 secs with a 50 percent duty cycle.

In line 21, the motor is turned off. In line 25, the direction is 0 and the motor is pulsed 600 times at a 50 percent duty cycle.

/*
** Program DC_MOT1.C
**
** Turns motor in one direction for a minute.  Stops motor for a minute.
** Turns motor in opposite direction for one minute.
**
** H. Paul Roach, MSU, 2 August, '96
*/

#include <stdio.h>                                            /* 1 */
#include <dos.h>                                              /* 2 */
                                                              /* 3 */
#define DATA 0x03bc                                           /* 4 */
#define STATUS DATA+1                                         /* 5 */
#define CONTROL DATA+2                                        /* 6 */
                                                              /* 7 */
void main(void)                                               /* 8 */
{                                                             /* 9 */
   int n;                                                     /* 10 */
   for(n= 0; n<600; n++) /* turn one way for one minute */    /* 11 */
   {                                                          /* 12 */
      outportb(DATA, 0x02);                                   /* 13 */
      delay(50);                                              /* 14 */
      outportb(DATA, 0x03);                                   /* 15 */
      delay(50);                                              /* 16 */
   }                                                          /* 17 */
                                                              /* 18 */
   for(n= 0; n<600; n++)  /* stop for a minute */             /* 19 */
   {                                                          /* 20 */
      outportb(DATA, 0x01);                                   /* 21 */
      delay(100);                                             /* 22 */
   }                                                          /* 23 */
                                                              /* 24 */
   for(n= 0; n<600; n++) /* turn other way for one minute *   /* 25 */
                                                              /* 26 */
   {                                                          /* 27 */
      outportb(DATA, 0x00);                                   /* 28 */
      delay(50);                                              /* 29 */
      outportb(DATA, 0x01);                                   /* 30 */
      delay(50);                                              /* 31 */
   }                                                          /* 32 */
}                                                             /* 33 */

Use of the ftime() Function.

Prior to discussing program DC_MOT2.C, consider program FTIME.C which illustrates how to continually fetch the time of the system clock so as perform a task for a specified period of time. In this simple example, a dot is printed for 5000 ms. Struct timeb consists of, among other things, a long integer, "time" which is the number of seconds since Jan 1, 70 and a short integer, "millitm", which is the fractional number of milliseconds.

At the beginning of the task, line 9, the start time is fetched. The task is then performed, in this case, the simple printing of a dot and the current time is fetched off the system clock in line 14. This is repeated until the current time is 5000 ms larger than the start time.

The advantage of this approach over using the TurboC delay function is that while executing the delay function, nothing else is being done. Thus, although the delay function has its place, it is unusable in many applications, where something dynamic must be done for a specified period of time.

/*
** Program FTIME.C
**
** Illustrates the use of ftime function to perform a task for a period
** of time.  Prints dots for 5000 msecs.
**
** H. Paul Roach, MSU, 5 August, '96
*/

#include <stdio.h>                                            /* 1 */
#include <dos.h>                                              /* 2 */
#include <sys\timeb.h>                                        /* 3 */
                                                              /* 4 */
void main(void)                                               /* 5 */
{                                                             /* 6 */
   struct timeb t_start, t_current;                           /* 7 */
   int t_diff;                                                /* 8 */
   ftime(&t_start);                                           /* 9 */
   do                                                         /* 10 */
   {                                                          /* 11 */
      printf(".");                                            /* 12 */
      ftime(&t_current);                                      /* 13 */
      t_diff = (int) (1000.0 * (t_current.time - t_start.time)/* 14 */
        + (t_current.millitm - t_start.millitm));             /* 15 */
   }                                                          /* 16 */
   while(t_diff < 5000);                                      /* 17 */
}                                                             /* 18 */

Program DC_MOT2.C

Program DC_MOT2.C uses the ftime function to turn the motor in a specified direction, at a specified speed for a specified duration of time. Note that in lines 45 through 73, the motor is either stopped by holding Data_0 high, or turned CW by holding Data_1 low and pulsing Data_0 or turned CCW by holding Data_1 high and pulsing Data_0. Quite like the FTIME.C program above, the current time is continually read and the "do" loop is exited when the current time less the start time is equal to or greater than the specified duration.

The main() consists simply of straight line calls to function turn_motor() so as to perform a task sequence. I leave it to the reader to use their imaginations in extending this to reading commands from an ASCII text file, quite like the discussion relating to "Interfacing with a Junk Disk Drive". The reader might also consider such scenarios as conditioning the function calls on the state of inputs on the Status Port. An application which comes to mind is to turn a motor until an optical sensor senses the moving part is at some point, at which time, some other action is taken.

/*
** Program DC_MOT2.C
**
** Illustrates the use of ftime function to perform a task for a period
** of time.
**
** Function turn_motor(int direction, int speed, int duration_ms) turns
** motor in specified direction, at specified speed for the specified
** duration in millsecs.  Speeds SLOW, MEDIUM and FAST correspond to
** 25, 50 and 75 percent duty cycle.
**
** H. Paul Roach, MSU, 2 August, '96
*/

#include <stdio.h>                                            /* 1 */
#include <dos.h>                                              /* 2 */
#include <sys\timeb.h>                                        /* 3 */
                                                              /* 4 */
#define DATA 0x03bc                                           /* 5 */
#define STATUS DATA+1                                         /* 6 */
#define CONTROL DATA+2                                        /* 7 */
                                                              /* 8 */
#define SLOW 1    /* speeds */                                /* 9 */
#define MEDIUM 2                                              /* 10 */
#define FAST 3                                                /* 11 */
                                                              /* 12 */
#define STOP 0   /* directions */                             /* 13 */
#define CW 1                                                  /* 14 */
#define CCW 2                                                 /* 15 */
                                                              /* 16 */
void turn_motor(int direction, int speed, int duration_ms);   /* 17 */
                                                              /* 18 */
                                                              /* 19 */
void main(void)                                               /* 20 */
{                                                             /* 21 */
   turn_motor(CW, FAST, 10000);                               /* 22 */
   turn_motor(CW, SLOW, 30000);                               /* 23 */
   turn_motor(STOP, FAST, 10000);                             /* 24 */
   turn_motor(CCW, SLOW, 30000);                              /* 25 */
}                                                             /* 26 */
                                                              /* 27 */
void turn_motor(int direction, int speed, int duration_ms)    /* 28 */
{                                                             /* 29 */
   struct timeb t_start, t_current;                           /* 30 */
   int t_diff, t_delay;                                       /* 31 */
                                                              /* 32 */
   switch(speed)                                              /* 33 */
   {                                                          /* 34 */
      case SLOW:  t_delay = 25;                               /* 35 */
                  break;                                      /* 36 */
                                                              /* 37 */
      case MEDIUM:  t_delay = 50;                             /* 38 */
                    break;                                    /* 39 */
                                                              /* 40 */
      case FAST:  t_delay = 75;                               /* 41 */
                  break;                                      /* 42 */
   }                                                          /* 43 */
                                                              /* 44 */
   ftime(&t_start);                                           /* 45 */
   do                                                         /* 46 */
   {                                                          /* 47 */
      if (direction == STOP)                                  /* 48 */
      {                                                       /* 49 */
         outportb(DATA, 0x01);                                /* 50 */
      }                                                       /* 51 */
      else if (direction == CW)                               /* 52 */
      {                                                       /* 53 */
         outportb(DATA, 0x02);                                /* 54 */
         delay(t_delay);                                      /* 55 */
         outportb(DATA, 0x03);                                /* 56 */
         delay(100 - t_delay);                                /* 57 */
      }                                                       /* 58 */
                                                              /* 59 */
      else /* its CCW */                                      /* 60 */
      {                                                       /* 61 */
         outportb(DATA, 0x00);                                /* 62 */
         delay(t_delay);                                      /* 63 */
         outportb(DATA, 0x01);                                /* 64 */
         delay(100 - t_delay);                                /* 65 */
      }                                                       /* 66 */
                                                              /* 67 */
      ftime(&t_current);                                      /* 68 */
      t_diff = (int) (1000.0 * (t_current.time - t_start.time)/* 69 */
          +(t_current.millitm - t_start.millitm));            /* 70 */
  }                                                           /* 71 */
  while(t_diff < duration_ms);                                /* 72 */
}                                                             /* 73 */

Reading a Cursor Key.

Prior to discussing program DC_MOT3.C, consider program GETKEY.C which illustrates how to fetch a character from the keyboard, notably, how to fetch the cursor keys.

Of course, the reader might be confused as to why a special function would be required when such functions as getch() are in the TurboC library. The answer is that getch() works for ordinary keystrokes; that is, the characters you see on your keyboard. It doesn't work for the function and cursor keys.

Some interrupts may be executed from software using the int86 function. Interrupt level 21 was developed by the original developers of DOS as a standard interface in fetching specific information. The identity of the specific information is passed in the high byte of the A register (in.h.ah). For example, if in.h.ah is 0x2a, the year, month, day and day of the week are returned in various registers.

The value of in.h.ah for reading the keyboard is 0x08 and the value of the character is returned in the low byte of A (out.h.al). This works for ordinary keystrokes.

However, IBM needed more than the 256 characters that can be accommodated in a byte. Thus, they defined an "extended ASCII" by reserving the return code of 0x00 to mean, "there is more, do it again.". Thus, if a user depresses the character c', out.h.al will be 0x63 which is the ASCII value of the character c'. However, if the user depresses the right arrow key, out.h.al will be 0x00 and a second call using int86 will return 0x4d. That is, one might think of the ASCII code for the right arrow as being 0x00 0x4d.

Note in the following program, if out.h.al is equal to zero, the function getkey() is again called (line 27). I happened to add 0x100 to the result to indicate the result is extended ASCII. Thus, in running the program, you should find that depressing a c' causes the value 0x63 to be displayed. Depressing the right arrow should cause 0x14d to be displayed.

You may use program GETKEY.C to determine the extended codes for such keys as the cursors, DEL, INS, PgUp, PgDn and the function keys.

/*
** Program GETKEY.C
**
** Illustrates how to fetch scan code from keyboard.
**
** H. Paul Roach, Morgan State University, July 30, '96
*/

#include <stdio.h>                                            /* 1 */
#include <conio.h>  /* for kbhit */                           /* 2 */
#include <dos.h>                                              /* 3 */
                                                              /* 4 */
int getkey(void);                                             /* 5 */
                                                              /* 6 */
void main(void)                                               /* 7 */
{                                                             /* 8 */
   int key;                                                   /* 9 */
   while(1)                                                   /* 10 */
   {                                                          /* 11 */
      if (kbhit)                                              /* 12 */
      {                                                       /* 13 */
         key=getkey();                                        /* 14 */
         printf("%d %x\n", key, key);                         /* 15 */
      }                                                       /* 16 */
   }                                                          /* 17 */
}                                                             /* 18 */
                                                              /* 19 */
int getkey(void)                                              /* 20 */
{                                                             /* 21 */
   union REGS in, out;                                        /* 22 */
   in.h.ah = 0x08;                                            /* 23 */
   int86(0x21, &in, &out);                                    /* 24 */
   if (out.h.al == 0)                                         /* 25 */
   {                                                          /* 26 */
      return(getkey()+0x100);                                 /* 27 */
   }                                                          /* 28 */
   else                                                       /* 29 */
   {                                                          /* 30 */
      return(out.h.al);                                       /* 31 */
   }                                                          /* 32 */
}                                                             /* 33 */

Cursor Control of the Motor.

In program DC_MOT3.C, the motor is controlled by the cursor keys. If the right arrow is depressed, the motor turns one way and if the left arrow is depressed, it turns the other way. The speed is controlled by the up and down cursors. The motor is stopped by depressing the s' key and the program is exited by depression of the q' key.

The program starts with the action set to STOP. If no key is hit, this action is passed to control_motor() and parallel port output Data_0 is brought high, turning off the motor (line 41).

However, if a key is hit, function get_action() is called which in turn calls getkey() (line 91). The value of the key determines the action; STOP, FASTER, etc (lines 92-116). Function control_motor() is again called. Assume the action is RIGHT. In line 53 the direction bit is set and in lines 81-84 the motor is turned on and then off with a delay which is initially set to 50 ms. Note that t_delay is declared as a static and is thus the value is retained in the function.

If control_motor() is called with the action being FASTER, t_delay is increased so as to increase the duty cycle of the time the motor is on (line 66). Of course, the upper limit of t_delay is 100, which corresponds to a 100 percent duty cycle. Similarly for SLOWER, the lower limit is 0.

/*
** Program DC_MOT3.C
**
** Motor is controlled by cursor keys.  Program starts with motor
** stopped.  Motor is then moved in direction specified by right and
** left arrows keys.  Speed is controlled by up and down arrow keys. 
** Motor is stopped in response to "S" or 's" keys.  Routine is exited
** in response to "Q" or "q" keys.
**
** H. Paul Roach, MSU, August 3, '96
*/

#include <stdio.h>                                            /* 1 */
#include <conio.h>  /* for kbhit */                           /* 2 */
#include <dos.h>                                              /* 3 */
                                                              /* 4 */
#define DATA 0x03bc                                           /* 5 */
#define STATUS DATA+1                                         /* 6 */
#define CONTROL DATA+2                                        /* 7 */
                                                              /* 8 */
#define QUIT 1                                                /* 9 */
#define STOP 2                                                /* 10 */
#define NOCHANGE 3                                            /* 11 */
#define FASTER 4                                              /* 12 */
#define SLOWER 5                                              /* 13 */
#define LEFT 6                                                /* 14 */
#define RIGHT 7                                               /* 15 */
                                                              /* 16 */
void control_motor(int action);                               /* 17 */
int get_action(void);                                         /* 18 */
int getkey(void);                                             /* 19 */
                                                              /* 20 */
void main(void)                                               /* 21 */
{                                                             /* 22 */
   int action = STOP;                                         /* 23 */
   control_motor(action);                                     /* 24 */
   while(1)                                                   /* 25 */
   {                                                          /* 26 */
      if (kbhit())                                            /* 27 */
      {                                                       /* 28 */
         action = get_action();                               /* 29 */
      }                                                       /* 30 */
      else                                                    /* 31 */
      {                                                       /* 32 */
         if(action != STOP)                                   /* 33 */
         {                                                    /* 34 */
            action = NOCHANGE;                                /* 35 */
         }                                                    /* 36 */
      }                                                       /* 37 */
      control_motor(action);                                  /* 38 */
      printf("%d ", action);                                  /* 39 */
   }                                                          /* 40 */
}                                                             /* 41 */
                                                              /* 42 */
void control_motor(int action)                                /* 43 */
{                                                             /* 44 */
   static int data_byte=0x00, t_delay = 50;                   /* 45 */
   if (action == STOP)                                        /* 46 */
   {                                                          /* 47 */
      data_byte = data_byte | 0x01;                           /* 48 */
      outportb(DATA, data_byte);                              /* 49 */
   }                                                          /* 50 */
   else                                                       /* 51 */
   {                                                          /* 52 */
      switch(action)                                          /* 53 */
      {                                                       /* 54 */
         case QUIT:  exit(0);                                 /* 55 */
                                                              /* 56 */
         case NOCHANGE:                                       /* 57 */
                     break;                                   /* 58 */
                                                              /* 59 */
         case RIGHT: data_byte = data_byte & (~0x02);         /* 60 */
                     break;                                   /* 61 */
                                                              /* 62 */
         case LEFT:  data_byte = data_byte | 0x02;            /* 63 */
                     break;                                   /* 64 */
                                                              /* 65 */
         case FASTER:                                         /* 66 */
                     if(t_delay == 100)                       /* 67 */
                     {                                        /* 68 */
                        break; /* this is max speed */        /* 69 */
                     }                                        /* 70 */
                     else                                     /* 71 */
                     {                                        /* 72 */
                        t_delay = t_delay + 1;                /* 73 */
                        break;                                /* 74 */
                     }                                        /* 75 */
                                                              /* 76 */
         case SLOWER:                                         /* 77 */
                     if(t_delay == 0)                         /* 78 */
                     {                                        /* 79 */
                        break; /* this is min speed */        /* 80 */
                     }                                        /* 81 */
                     else                                     /* 82 */
                     {                                        /* 83 */
                        t_delay = t_delay - 1;                /* 84 */
                        break;                                /* 85 */
                     }                                        /* 86 */
      }                                                       /* 87 */
      outportb(DATA, data_byte & (~0x01)); /* motor on */     /* 88 */
      delay(t_delay);                                         /* 89 */
      outportb(DATA, data_byte | 0x01);                       /* 90 */
      delay(100-t_delay);                                     /* 91 */
   }                                                          /* 92 */
}                                                             /* 93 */
                                                              /* 94 */
int get_action(void)                                          /* 95 */
{                                                             /* 96 */
  int key, action;                                            /* 97 */
  key = getkey();                                             /* 98 */
  switch(key)                                                 /* 99 */
  {                                                           /* 100 */
     case 'q':                                                /* 101 */
     case 'Q':  action = QUIT;                                /* 102 */
                break;                                        /* 103 */
     case 's':                                                /* 104 */
     case 'S':  action = STOP;                                /* 105 */
                break;                                        /* 106 */
                                                              /* 107 */
     case 0x48+0x100:  /* up arrow */                         /* 108 */
                action = FASTER;                              /* 109 */
                break;                                        /* 110 */
                                                              /* 111 */
     case 0x50+0x100:  /* down arrow */                       /* 112 */
                action = SLOWER;                              /* 113 */
                break;                                        /* 114 */
                                                              /* 115 */
     case 0x4d+0x100:  /* right arrow */                      /* 116 */
                action = RIGHT;                               /* 117 */
                break;                                        /* 118 */
                                                              /* 119 */
     case 0x4b+0x100:                                         /* 120 */
                action = LEFT;                                /* 121 */
                break;                                        /* 122 */
                                                              /* 123 */
     default:   action = NOCHANGE;                            /* 124 */
                break;                                        /* 125 */
   }                                                          /* 126 */
   return(action);                                            /* 127 */
}                                                             /* 128 */
                                                              /* 129 */
int getkey(void)                                              /* 130 */
{                                                             /* 131 */
   union REGS in, out;                                        /* 132 */
   in.h.ah = 0x08;                                            /* 133 */
   int86(0x21, &in, &out);                                    /* 134 */
   if (out.h.al == 0)                                         /* 135 */
   {                                                          /* 136 */
      return(getkey()+0x100);                                 /* 137 */
   }                                                          /* 138 */
   else                                                       /* 139 */
   {                                                          /* 140 */
      return(out.h.al);                                       /* 141 */
   }                                                          /* 142 */
}                                                             /* 143 */