Real world applications often call for controlling small to medium sized DC motors from digital circuits. For smaller motors it is usually economically infeasible to buy a commercial speed controller as the cost of the controller will far outstrip the cost of the motor itself. The PIC's high speed, low cost, and low power requirements lend it to being an inexpensive "smart chip" controller for DC motors.
Many modern speed controllers for DC motors use a technique called Pulse Width Modulation (PWM) to control the speed of the motor. Using a switching device such as a power transistor or enhancement mode MOSFET, power is switched on an off rapidly to the motor. The natural inductance and resistance of the motor acts as a low pass filter and makes the effective voltage seen by the motor to be the average value of the voltage over time. By varying the duty cycle (width) of this switched voltage, the effective average voltage can be lowered (narrow width pulse) or raised (wide pulse). This produces the effect of a linear change in voltage, however since the switching device is either off or in saturation it is never required to dissipate too much power internally. This increases the overall efficiency of the system as well.
The other half of the problem is how to tell the motor controller what speed you would like the motor to be set to. Any of a number of schemes would work, however the Radio Control (R/C) community has a standard for controlling servos that is particularly apt for this application. This standard uses a pulse, of variable width, repeated periodically to specify the "position" of the servo. The position variable in the servo can take on values from "full retract" to "full extension" and any position in between. Where the servo is positioned is determined by the width of this incoming pulse. In this application we will use this same technique, however rather than full retract or full extension our controller sets the speed of the motor it is controlling from full reverse to full forward.
The control pulse is defined by the width that represents "neutral," or in the case of the motor controller the width that represents "stop," and the change in width or delta that will yield full travel. A typical value for neutral on a Futaba R/C servo is 1520 uS, however for me it was easier to use 1500 uS. Depending on the servo, the "maximum" and "minimum" position will be some delta plus or minus this neutral position. On a Futaba S143 servo that I tested, these values were 700 uS for the minimum or full retract position and 2300 uS for maximum or full extension position. Generally they land near the 1000 uS for a minimum pulse width and 2000uS as a maximum pulse width. Given the timing constants of the program, this program responds to pulses that are between 860 uS and 2140 uS. The exact relationship is shown in Figure 1.
Figure 1: Time relationship of the output
The last piece of this specifcation is how often one needs to send the control pulse to the servo. Again using the Futaba servo as a reference it is about every 20 mS or 50 times a second. Thus, the motor controller keeps the motors "on" at the current setting until either a new pulse arrives with the same or different width, or until approximately 20 mS has passed without seeing a new pulse.
How It Works
The hardware for this project is straight forward. Referring to the schematic shown in Figure 2. "The Motor Controller Schematic" you will see that the input from the controller is fed into bit RB0. Port A of the PIC is connected to an opto-isolator chip. The purpose of this chip is to shield the digital side of the circuit from the electronic noise generated by the motors being switched. Further, it allows the motors to be easily run from a separate, and potentially much higher supply voltage. Resistors RP1a through RP1d are four elements of a 470 ohm resistor pack. There use is to imit the current in the opto-isolator LEDs to less than 10mA. The other side of the opto-isolator is used to drive the power transistors. Q1 and Q2 in the schematic are TIP 125 PNP power darlingtons and Q3 and Q4 are TIP 120 NPN power darlingtons. Resistors R1 through R4 are chosen to create a current of greater than 6mA through the bases of the transistors. The current can be greater, but should never be less than 6 mA.
Figure 2: The Motor Controller Schematic
The value for R1 through R4 must be calculated based on the voltage Vmotor. In my prototype I was using a 9.6 V battery for the motors. As the voltage across the resistor is reduced by the forward diode drops of the base-emitter junctions of both the power transistor and the transistor in the opto-isolator, it is about 1.2 to 1.5 volts lower than Vmotor. In my case this made it about 8 volts so I chose to use 1K ohm resistors for R1 - R4 resulting in a base current of 8 mA. This is sufficient current to insure that the transistors will switch completely on.
Rounding out the schematic diodes D1 through D4 provide a path for the inductive "kick" that is generated when the motor is switched off. Remember that when you turn off the inductors the magnetic field they had generated collapses. When the field collapses it generates a voltage that is opposite in polarity to the voltage that originally generated the field. Since the voltage is of the opposite polarity, without the diodes it would reverse bias the transistors and likely burn them out. The diodes provide a conductive path back through the power supply or battery and protect the transistors. This is referred to as a snubber circuit.
The transistor collectors are connected to the motor. You can see that by turning on transistors Q1 (RA3) and Q4 (RA0) the positive battery voltage appears at the left terminal of the motor and the negative battery terminal is connected to the right motor terminal. This will turn the motor in one direction (possibly clockwise). However, when Q2 (RA2) and Q3 (RA1) are turned on, this situation is reversed with the postive battery terminal connected to the right motor terminal and the negative battery terminal connected to the left motor terminal. This causes the motor to turn in the opposite (possibly counter-clockwise) direction. This particular circuit is called an H-bridge or a full bridge. It allows bidirectional control of a motor with a single power supply. One other feature to note is that if you turn on both Q3 (RA1) and Q4 (RA0), between the diode and the turned on transistors you create a connection from the left terminal of the motor to the right terminal of the motor. This has the effect of braking the motor if it is turning because the same EMF field that is being generated by the motor is fed back into the motor and it trys to turn the motor the other way. At no time must Q1 and Q3 or Q2 and Q4 be turned on simultaneously as this would have the effect of connecting the positive battery terminal to the negative battery terminal and creating a direct short circuit through the transistors. I refer to this as "one time smoke mode."
The software is a bit trickier only because the 16C54 has neither interrupts nor an input capture function. The required capability can be described quite clearly in the form of a state machine diagram. This diagram is shown in Figure 3 "The Controller State Machine".
Figure 3: The Controller State Machine
Initially the program is idling in the IDLE state. In this state all outputs are off. When the input on RB0 goes high the program enters the IDLE+MEASURING state. In this state the PIC is measuring the width of the incoming pulse by waiting to see RB0 return low. As in idle, during this state the outputs are quiescent. When RB0 does return low, if the width of the input pulse is within the valid width range, the program enters the PWM state. In the PWM state the outputs are toggled at a frequency of 2000 Hz with a duty cycle determined by the width of the pulse that was measured. Further, depending on whether the pulse was wider than the 'neutral' pulse or narrower the motor will be turned in the forward or the reverse direction. The PWM state can be exited for one of two reasons, either 20 mS has passed (PWM timeout) or RB0 has gone high again. In the first case the program returns to IDLE, however in the second case the PIC has to start measuring the width of the pulse. This is done in the fourth and last state, PWM+MEASURING. In this state the outputs are actively generating a PWM output while the PIC waits for RB0 to go low again. As in the PWM state there are two ways to exit, either the PWM timeout occurs (it has been 20 mS since starting to generate the PWM output) or RB0 goes low again. In the first case, PWM timeout, the outputs are turned off but instead of going into the IDLE state the program goes back into the IDLE+MEASURING state. If RB0 goes low, and the pulse is of valid width, the program resets the PWM parameters to their new values and returns to the PWM state for another 20 mS.
Implementing the state machine is tricky but not impossible. The central routine of the program is DO_MEASURE. The way it is designed, this routine is called once every 5 uS exactly. The PIC is executing at 20 Mhz and the cycle time is thus 200 nS, the DO_MEASURE routine takes 2.8uS to run, and so we always execute exactly 11 clocks or 2.2 uS between calls to DO_MEASURE. This is easily accomplished in the IDLE loop since not much is required outside of DO_MEASURE except to check to see if a valid pulse has been received. It is somewhat more problematic in the PWM loop.
The PWM loop requires both an inner and an outer loop. In the inner loop the program turns the outputs on and off to generate a cycle and in the outer loop the program counts cycles so that it can stop generating an output after 20 mS has elapsed. Further, each time through the inner loop, the program must consume exactly 2.2 uS so that it can also call DO_MEASURE in case the PIC is measuring an input while generating PWM output. When the counters decrement to zero they must be reloaded and that takes cycles so as you look through the PWM loop you will notice that the last iteration of the loop is unrolled to allow DO_MEASURE to be called effectively twice during end loop processing. The inner loop generates a variable width output by turning the outputs on, counting down a counter, and then turning the outputs off when that counter reaches zero. The number of times the inner loop runs is fixed at 100 so if the PWM counter is set to 1, the loop turns on the output for one count and then turns if off for 99 counts. Similarly, if the PWM counter is set to 50 then the output is on for 50 counts and off for 50 counts. As you can see the PWM count represents the duty cycle of the output waveform. In this implementation it can take on any value between 1% and 100%.
The inner PWM loop runs 100 times and this determines the frequency of the PWM wave that is generated. Since the loop must take 5uS so that it can call DO_MEASURE on schedule, 100 iterations takes 500 uS. Thus the period of each cycle is 500 uS and the output frequency is 1/.0005 or 2000 Hz. You can get a faster PWM frequency by lowering the number of iterations, raising the PIC clock frequency, or reducing the number of instructions in the loops and DO_MEASURE routine. I believe the latter to be impossible. As the inner loop takes 500uS to run, running it 40 times takes 20 mS. This determines the value of the outer loop count.
There are several modifications you might wish to make to this program. The difficulty lies in keeping the timing relationships accurate.
If you wish to do more with the PIC than just generate PWM output while measuring a wave you can lower the frequency with which you call DO_MEASURE. This will reduce the accuracy of the measurement but will give you more cycles inside the PWM inner and outer loops. If for example you call DO_MEASURE every 10 uS rather than 5uS you can double the amount of code between calls. The price however is that you only get 100 unique values between 1000 uS and 2000 uS. If you aren't using this circuit with R/C equipment and can tolerate wider control pulses then this can be a useful thing to do.
You can increase the valid range of pulses by lowering the GLITCH value (minimum pulse width) and/or by increasing the value loaded into COUNT_HI and COUNT_LO (maximum pulse width).
Since there are seven unused pins on port B you may wish to add additional LEDs as indicators. Three useful indications might be full forward, full reverse, and brake. These could be set inside the MAX_PWM and DO_BRAKE functions. This will increase the power consumption of the digital side of the circuit however it will still be well within the drive capability of any R/C receiver module.
Finally the most obvious and useful modification is to replace the bipolar transistors with something capable of carrying more current. The TIP 120 and TIP 125s will carry 5 amps with heat sinks, replacing them with an H-bridge built out of MOSFET transistors could increase the current capacity to 50 amps or more.
- pservo.src - A version using Parallax Inc. mnemonics.
- servo.asm - A version using MicroChip mnemonics.
There are also two MicroChip include files you will need, they are p16c5x.inc and p16c5xd.inc.
- Servo.hex - Assembled servo.asm.
- pservo.obj - Assembled pservo.src.