Translate this page
Controlling The Real World With Computers
::. Control And Embedded Systems .::

Experiment 7 - Bi-directional Control Of Motors And The H-Bridge
Home                                                        Order Let me know what you think

Previous: Experiment 6 - More Precise Control Of Motors
Next: Experiment 8 - Digital To Analog Conversion

It would be useful to be able to run a motor in either direction. That's simple with a motor of the type that's been used in the experiments thus far. Reverse the DC power connections on such a motor and its shaft will rotate in the opposite direction. One way to control direction is to use switches arranged in the following manner:

This configuration is called an H-Bridge due to its shape. Let's say that the motor runs forward when its + terminal is connected to Motor V+ and its - terminal is connected to ground. It will run in reverse when the opposite is true. Turn on switch A and switch D and the motor will run forward. Turn on switch B and switch C and it will run in reverse. The following table shows all of the possibilities. A 1 means a switch is on, and a 0 means it's off:

A

B

C

D

State

A

B

C

D

State

0

0

0

0

Off

1

0

0

0

Off

0

0

0

1

Off

1

0

0

1

Forward

0

0

1

0

Off

1

0

1

0

SHORT!!

0

0

1

1

Brake

1

0

1

1

SHORT!!

0

1

0

0

Off

1

1

0

0

Brake

0

1

0

1

SHORT!!

1

1

0

1

SHORT!!

0

1

1

0

Reverse

1

1

1

0

SHORT!!

0

1

1

1

SHORT!!

1

1

1

1

SHORT!!

Only a few of the possibilities are needed. In fact, seven of the combinations must be avoided because they short out the power supply. We can use the forward and reverse, one of the offs and, optionally, one of the brakes.

The braking action might need a little explaining. A motor's armature will pass through the fields of the magnets when it is turning, as will the wire that's wound around the armature. That will induce an electrical current in the wire. Spin the shaft of a motor and you can measure a voltage at its terminals; it has become a generator. Short the motor's terminals and you will feel it resist your attempts to spin the shaft. That's because the magnet that's formed by the current induced in the armature winding is opposite to that of the motor's field magnet. The opposite poles attract, resulting in a braking action. Both of the brake combinations short the terminals of the motor together.

There are a variety of devices that can be used for switches, including common mechanical switches, transistors and relays. The TIP120 will work for low currents, but a heat sink is needed for high current. The reason is that most transistors have a voltage drop between their collector and emitter of about .7 volts. Recall that power is P = I * V. With say, 5 amps and a .7V drop, the TIP120 will dissipate 5 * .7 = 3.5 watts. That's wasted power that could have been used by the load with a more efficient device.

The switch to be used here is a Metal Oxide Semiconductor Field Effect Transistor, or MOSFET. MOSFETs come in two polarities called N and P channel. The one to be used here is an N channel device. A schematic representation and a picture showing the pin designations is below (the sunflower is almost never included). Notice that the Drain is also connected to the metal tab, making it necessary to insulate the MOSFET when using a heatsink:

An enhancement mode N channel MOSFET is turned on by making the gate more positive than the source. What is enhanced is the conductivity between source and drain. The unit of conductivity is the mho, which is simply ohm spelled backwards. The IRF3708 used here will turn on if the gate is about 2 volts more positive than the source (see IRF3708.PDF ). The gate is not physically connected to the rest of the device. It influences the device by the fact that it is on one side of a very thin insulator, and works by means of the imposed field effect. In fact, the insulator is so thin that it can be ruptured by static voltages as weak as that from a piece of plastic. Great care should be taken when working with a MOSFET. The leads are sometimes shorted with a piece of wire or conductive foam when shipped. The shorting material should not be removed until the last moment when installing the device. Also, make sure you are grounded or have at least touched ground before touching a MOSFET.

Now let's calculate power again. The resistance from Source to Drain of the IRF3708 is very high when it is off, but a maximum of only .029 ohms when it is on. Recall from How To Read A Schematic that V = I * R. That means we can replace the V in the power equation above with I * R since the two are equal. Thus,
P = I * (I * R) = I2 * R.
If we use the same 5 amp current and .029 ohms we get:
P = 52 * .029 = 25 * .029 = .725 watts,
which means wasted power is reduced by almost 5 times.

The basic h-bridge looks like this using MOSFETs:

There is a bit of a problem when a MOSFET is used for Switch A or Switch B. The Drain is connected to the V+ for the motor and the Source to the motor. If the gate is then made to be 2 volts greater than the Source, the MOSFET will turn on. When that happens however, the voltage at the Source will increase until the gate is no longer 2 volts greater than the Source and the MOSFET will turn off. Another voltage source is needed that is always greater than Motor V+ by at least 2 volts.

Enter the 555 timer/oscillator (so called because it oscillates between a high and low level) (see LMC555.PDF):

The 555 timer has been around for about 30 years. A very good tutorial on how the "triple nickel" works has been written by Williamson Labs. Only the basic operation will be covered here. Positive 5V power goes to pin 8 and ground to pin 1. Pin 4 is a reset line that is active when it's low. It also gets +5V to disable it. Pin 5 is for an adjustment that is not used here. The capacitor connected to it helps keep down potential noise. The output is on pin 3.

In the circuit above, the output will be high as long as the voltage on pin 2 is less than 1/3 of V+ and will go low when pin 6 reaches 2/3 of V+. The following is the sequence of events for the circuit:

  1. Pin 2 is less than 1/3 V+ when power is turned on, so the output on pin 3 starts out high.
  2. The series value of R1 and R2 (R1 + R2) will start charging the capacitor connected to pins 2 and 6.
  3. When the voltage on the capacitor reaches 2/3 of V+, the output on pin 3 will switch low. Pin 7, an open collector output, will also go low and begin discharging the capacitor through R2. Only R2 is used for discharge.
  4. The capacitor will discharge until it reaches 1/3 of V+. The output will again go high, pin 7 will stop discharging, and the cycle will start over.

The output will be a series of pulses. The frequency of the pulse train will be:
F = 1/(.693 * C1 * (R1 + 2 R2))
Thus, F = 1/(.693 * 10-8 * (680 + (6.8K * 2))) = 10105.05212Hz Hz, or about 10K Hz.
(rusty on exponents? .01 = 10-2 so .01µF; = 10-2 * 10-6 = 10-8 Farads -- just add the exponents to multiply)

Keeping R1 significantly smaller than R2 produces what will be close to a 50% duty-cycle square wave.

The characteristics of the timer depend on the RC Time Constants formed by R1, R2 and C1. The basic RC circuit looks like this:

The RC time constant for the above is simply R * C, or just RC. It takes RC seconds for Vout to reach about 63% of Vin.

The RC circuit is not linear, which is to say the voltage on C is not in direct proportion to time. For example, there will not be twice as much voltage on C in 2 seconds as there was in 1 second. That's because the rate at which the capacitor charges is in direct proportion to the current through R.

Let's say we start with Vin = 1 volt and that there is 0 volts on C. The current through R is I = V/R = 1/R initially since there is no voltage on C (see How To Read A Schematic ). As the voltage on C increases however, it gets closer to Vin so the voltage drop across R decreases, and with it the current. As the current drops, the rate at which C charges also decreases.

A lot of things in nature work this way. A lot of water bursts forth initially when the dam breaks, but the rate decreases rapidly as the pressure falls. More water escapes, the pressure decreases, the rate falls and so on.

The voltage increases (or decreases when discharged through the resistor) on the capacitor logarithmically. The logarithm of a number is the power to which a base must be raised to get the number (see Data lines, bits, nibbles, bytes, words, binary and HEX).

For example, log10 1000 = 3 means that the base 10 must be raised to the power of 3 to get 1000.

The base e is commonly used for natural phenomena. It is equal to approximately 2.718281828. The special symbol ln is used for natural logarithms. ln 1000 = 6.907755279 says that e6.907755279 = 1000.

The voltage on a charging capacitor in an RC circuit in t seconds is shown by the charging equation:
Vt = Vin * (1 - e-(t/RC))

Let's say t equals the RC time constant: t = RC. That makes the part in the parenthesis 1 - e-1 = .632120559, which is where the 63% comes from. You should be able to find an ex function on even the least expensive scientific calculator to confirm the above.

The 555 circuit alternately charges and discharges C1 between a maximum and minimum value. The discharge equation can be used to show the relationship of the voltages, RC and time:
Vmin = Vmax * e -(t/RC)

Let's derive an equation that will tell us how much time it will take for charge and discharge. This can be done by isolating t to the left side of the equation:

  1. Start with Vmin = Vmax * e -(t/RC)
  2. Divide both sides by Vmin:
    1 = (Vmax * e -(t/RC))/Vmin
  3. Multiply both sides by e (t/RC):
    e (t/RC) = Vmax/Vmin
    (two rules here -- to multiply, add exponents, and anything raised to the power of 0 = 1)
  4. Take the ln of both sides:
    t/RC = ln(Vmax/Vmin)
    (think, " to what power must e be raised to get e (t/RC)?")
  5. Multiply both sides by RC:
    t = RC ln(Vmax/Vmin)

In the case of the 555, Vmax = 2/3 V and Vmin = 1/3 V, so Vmax/Vmin = 2.
ln 2 = .693147181 (look familiar?).
Both resistors are used for charging so
tcharge = 7480 * 10-8 * ln 2 = .000051847 seconds
Discharge uses only R2:
tdischarge = 6800 * 10-8 * ln 2 = .000047134 seconds
The time for a total cycle is the on time plus the off time:
= .000051847 + .000047134 = .000098981 seconds.
Note the almost 50% duty-cycle (on and off almost the same). The frequency is the inverse of the time for a cycle:
1/.000098981 = 10102.94821 Hz, which is more precise than when we rounded to .693. The target was 10K Hz. This is close enough.

This application will need more than the maximum 100ma current capability of the 555. A 4429 high current driver will provide the additional current (see TC4429.PDF ):

Please note that both pins 1 and 8 are used for power, and pins 4 and 5 for ground. Never use the device without using both pairs. It's also a good idea to use both 6 and 7 for the output. Also, be sure to place .1µF; capacitors from power to ground near the device to absorb high current spikes.

A few more components can now be added to the 555 circuit to form a voltage doubler:

The 1N5819s are Schottky barrier rectifiers. They are used due to their lower-than-normal forward drop of .6V maximum, which falls to .4V or less under normal current loads. The output of the 4429 will swing from .025V to 4.975V with a 5 volt supply. I got the above waveforms from actual measurements on an oscilloscope. Notice that the voltage through D3 offsets the square wave (raises the whole thing) on the right side of C5 so that the top of the waveform (its peak) is near 10 volts. D4 directs everything to a second capacitor that filters the output. I measured 9.71 volts for the Bottom Drive Voltage with no load. A doubler is actually an almost-doubler. Note that C3, C4, C5 and C6 are all electrolytics and are polarized.

One of the secrets of the doubler is the fact that a capacitor such as C5 permits the pulse train from the oscillator to get through, but does not allow the DC voltage from D3 to get to the 4429. That's what allows the pulse train and the DC to add to each other, to then be usured out by D4 to the filter capacitors. C5 is functioning as a blocking capacitor. It functions to block pure DC, but lets AC or pulses through. Consider the following capacitor:

A negative charge has been produced on the left plate of the capacitor (see How to Read a Schematic). Since like charges repel, negatively charged electrons are pushed off of the other plate, rusulting in a positive charge. The capacitor can be said to have reached a point of equilibrium by the time the charges are balanced as in the above illustration. When the charge is first applied to the left plate and the electrons are driven off of the right plate however, whatever negative charge that was on the right plate will flow off of it, providing there is a path from the right plate to ground. A resistor or other load can supply the path. If the resistor is small enough to cause the capacitor to reach equilibrium before the next change in direction of the square wave, there will be only a pulse in response to the input. The width of the pulse is determined by the time it takes to reach equilibrium:

The above illustrates the step response of the circuit. C5 is large enough in the doubler circuit and the load light enough that the pulse train appears to get through unharmed. A careful reading will reveal however, that direct current won't appear to get through at all since it charges or discharges the capacitor only when power is first turned on. Equilibrium is reached early in the process, and the result is an offset of the pulse train.

Since C5 is large compared to the load and the width of the input pulses, the right side charges and discharges look virtually the same as the input on the left side. There will always be some distortion, but with large enough capacitance it will be minimal. Note that there is no negative input. The output of the 4429 is from 0 to 5 volts. When the pulse rises, a positive charge is pushed off of the right side of the capacitor. When the pulse again falls to 0, the left side will then be more negative than the right. This will attract the positive charge on the right, pulling it to 0 to complete the cycle.

The bottom drive section is actually just sort of thrown in since the IRF3708 turns on at just 2 volts. Using almost 10 volts however, serves to lower the Drain to Source resistance even more. The data sheet says it falls to as little as .008 ohms with a Gate to Source voltage of 10 volts.

The circuit for the top MOSFETSs works in a similar manner, except that the square wave is offset by the motor voltage. Whatever the motor voltage is (up to 12 volts), the top Gate to Source voltage will always be about 5 volts greater. The 1N4746 zener keeps the voltage from being greater than 18 volts, which is the limit of the 4429s that will also be used to drive the MOSFETs.

4429s will be used to drive the MOSFETs because the MOSFETs have a high gate capacitance, and it is best to drive them full on or full off as quickly as possible. The idea is to spend as little time as possible in a moderate resistance region in order to minimize the power they consume. Fully off, the leakage current from Drain to Source is about 20 microamps. Since power is P = I * V, it's almost nothing in the off state. Fully on, the resistance is a maximum of .029 ohms. Since P = I2 * R, the power is very little in the on state as well. The gate capacitance is 2417pf (pf = picofarads = 10-12 Farad) however, which calls for a high current to drive it. We can figure out how fast the MOSFET will be turned on or off using t = R * C from above. From Ohm's Law, R = V/I, so t = (V/I) * C, or t = (V * C)/I. From the 4429 data sheet it will be found that it can deliver a short-duration peak current of 6 Amps. The maximum voltage to a gate will be 10 volts. Using the gate capacitance above, t = (10 * 2417 * 10 -12)/6 = about .04028 microseconds -- not bad at all.

Back to the H-Bridge. Things can be simplified by saying we want to turn on A and D for forward, B and C for reverse and C and D for brake. Shorts can be avoided if forward and reverse disable each other. This can be done with a NAND gate (see the Boolean Logic section). The output of a two-input NAND gate will go low if both of its inputs are high. This permits us to use a NAND gate both as a gate and as an inverter. An inverter is formed by connecting one side of the gate to +5V. If the other side is then made high the output will be low. If it's low the output will be high. That's what in inverter does for a living.

The NAND gate used here is a 74HC00. It tolerates the high noise content of this type of circuit better than the standard 74LS00. One reason for that is that it requires its active high input voltage to be greater than the more common variety (compare 74HC00.PDF to 74LS00.PDF ). The 6.8K pull-up resistors make certain the high voltage will be closer to 5 volts. The full circuit follows. Please be certain to connect the top and bottom drive voltages from the doubler to the h-bridge part of the circuit. The connections are left out of the schematic to make it easier to read:

The parts can be ordered on the order page.

The combination of the 1N4001 rectifiers and 1N4739 zeners limits the gate to source voltage on the top switches to 10 volts (maximum is 12). The 1N5819 Schottky barrier rectifiers help keep negative pulses from backing up into the 4429 drivers, and the IN4001s block positive pulses. The 1N4733s help protect devices driving the inputs. Note that the MOSFETs already have zener protection built into them as well.

Be sure not to leave out any of the filter capacitors. Take special note of the fact that both a 1µF tantalum and a .1µF are connected to the power terminals of each 4429. The tantalums are used due to their low impedance at high frequencies. Impedance is expressed in ohms, and for a perfect capacitor is its reactance, which equals 1/(2 * pi * f) ohms, where f is frequency. Problem is, there is no such thing as a perfect capacitor. Tantalums are better here than electrolytics. The .1µF; capacitors can be just about anything. Ceramic will work just fine. There is also a 1000µF; electrolytic capacitor filter on +5V and on Motor +. There are all sorts of noises lurking around in motor control circuits. Leaving out filters can cause erratic operation or even destroy your 8255.

Notice what happens if Forward is made high. The forward signal takes one side of the Switch A NAND gate high. If the other side of the gate is also high, the output of the gate will be low, making the output of the 4429 high since it is an inverter, which will turn on MOSFET Switch A. If the other side of the NAND gate is low however, the output of the gate will be high, the output of the 4429 low and the switch will be off.

The Forward signal is also connected to an inverter formed with another NAND gate in the 74HC00. The output of the inverter goes to one side of the Reverse NAND gate, which disables Reverse. The inverted signal also goes to the 4429 for Switch D which turns it on. The Reverse MOSFET Switch B and Switch C are controlled in a like manner by the Reverse signal.

Now consider what happens if both Forward and Reverse are turned on. They will disable each other's top switches so that neither Switch A nor Switch B will be turned on. They will however, still turn on switches C and D which will short the motor out and provide a brake. The motor will be turned off but no braking action will take place when both Forward and Reverse are low.

I have operated this circuit continuously for extended periods with a motor voltage of 11 volts and with 2 motors and 2 resistors in parallel (see How To Read A Schematic ) to provide a load totaling 5 amps. The MOSFETs didn't even get warm with simple clip-on heatsinks while running this 55 watt load at 50% duty-cycle. It should be possible to operate significantly higher power loads with little need for additional heatsinking, and similar loads with no heatsinks at all.

The IRF3708 can take a maximum of 61 watts at high temperature according to the data sheet. Consider how much current about half that much power, 30 watts, translates to.

Recall that P = I * V and V = I * R
so P = I * (I * R) = I2 * R
divide both sides by R and switch sides,
I2 = P/R
P = 30 and worst case R = .029 so
I2 = 30/.029
I2 = 1034.482759
Now take the square root of both sides and get:
I = 32.16337605 amps

It would probably be a good idea to consider 30 amps the absolute maximum. It would also be a very good idea to use a good size heatsink with such high currents. After all, we are talking about driving a 360 watt load at 12 volts.

Testing the circuit before connecting it to the computer is strongly advised. I used the following circuit for testing:

Ground one of the inputs to the h-bridge and connect the above 2K Hz oscillator output to the other for a 50% duty-cycle test. Remove the oscillator, ground the other side, remove the ground from the previously grounded side and connect the oscillator to it to check the bridge for the opposite rotation of the motor. Run each direction for at least 15 minutes. The MOSFETs should get only barely warm with a 5 amp load, a 12 volt motor supply voltage and clip-on heatsinks. It would be a good idea to power the motor from other than the computer's power supply.

Many applications have no need to supply such high current devices. There are smaller h-bridges that require little additional circuitry. Consider the 754410 (see SN754410.PDF ). The following is derived from the data sheet:

There are 4 sections inside, each with an input designated A, and an output designated Y:

Each output is good for about 1 amp continuous output. The EN pins enable the outputs when high. The outputs are disconnected from what they are driving when the EN lines are low. An inverter can be added to provide bi-directional operation:

When the Forward/Reverse line is high the motor will rotate forward if the PWM line is also high. When the Forward/Reverse line is low (indicated by the bar over Reverse) the motor will rotate in reverse if the PWM line is high. Thus, the PWM line switches the motor on and off, providing pulses in the direction desired. The inverter could be one made out of a gate as above, or something such as the 74HC04, a HEX (means 6 here) inverter (see 74HC04.PDF ).

Rather than use up room for a whole IC though, a single transistor can be used as an inverter. The PPI can provide a minimum of 3V and 2.5ma to the inverter. The inverter shown below only needs about .6ma input at 3 volts, and the 1.5K output resistor is small enough to restore the 2.5ma drive of the PPI. Stand the resistors on end for minimum space. The circuit with some additional protection rectifiers added looks like this:

I tested this circuit for extended periods using the .4 amp motor provided with Experimenter's Kit #1 which can be ordered, and the IC didn't even get warm without a heatsink. To duplicate the test, simple connect Motor V+ to +5V. Connect the 2K Hz test oscillator above to the PWM input. Disconnecting the oscillator should turn off the motor. Alternately connect and remove +5V from the Foward/Reverse line to check for proper direction control. The circuit will easily fit on the breadboard that can be ordered with Experimenter's Kit #1.

All sections can be used to increase output capability:

This one got a little too warm to touch with a 1.5 amp, 12 volt motor. That's because I forgot an important parameter of the chip. Its maximum power is only 2 watts. It didn't much like 18 watts! Actually, with the 50% duty cycle test I ran it was actually more like 9 watts since it was on only half the time. It's a good idea to use 100% in calculations though, since that could happen. Fortunately, the IC has a thermal shutdown circuit that helps protect it from dumb mistakes.

Here's how to calculate what the chip can take:
Since P = V * I, the maximum current at 12 volts is I = P/V = 2/12 = .167 amp.

When Motor V+ was connected to +5V and one of the clip-on T0-220 heatsinks was stuck on top of the IC, it still got pretty warm, but the motor ran OK and the chip held up. The motor drew .77 amps, which means the IC would provide 3.85 watts at 100%. That's the value of a heatsink; it draws excess power off of the IC so that the IC itself never sees more than its maximum power, and never overheats. Another test was with a 20ma motor at 12 volts. The heatsink got just barely warm after more than an hour of 50% duty-cycle operation. In another test using 12 volts, a 100 ohm, 5 watt resistor was connected. Still just barely warm. Finally, a 5 volt motor that draws about 100ma was connected with an 82 ohm, 2 watt resistor in series with the h-bridge outputs using a Motor V+ of 12 volts. The IC was still just warm. All of the tests used the heatsink. The voltage divider section of How To Read A Schematic provides clues on how to figure out how to test with a motor you might have. Consider the following:



Current is the same through all elements of a series circuit. Thus, in the above example, the voltage dropped across the 82 ohm resistor with a current of 100ma is
V = I * R = .1 * 82 = 8.2 volts.
That means that 12 - 8.2 = 3.8 volts appears across the motor (providing the bridge can actually supply 12 volts, which is unlikely).

It would probably be a good idea to hold 100% power to about 2 watts and to glue on a heatsink such as the Wakefield 651-B:

People have even stacked 754410s for more power. Simply press one down on top of the other, solder their pins together, put a heatsink on top and go for it.

If you need more than the 754410's 2 watts but less than the 360 watts of my circuit, you might try the L298 dual h-bridge (see L298.PDF). It's good for a maximum of 25 watts.

The following is a simplified diagram, a picture and a pinout derived from the data sheet. The pins are actually in a straight line on the package and .05" apart, then bent out to accommodate standard .1" circuit board spacing. You will need to bend them left and right a little to get them to fit on a breadboard, but it can be done. The odd pins are on the front of the package and the even pins on the back:

The EnA and EnB inputs enable a side's outputs by making one side of all four of its AND gates high, which permits an IN line to turn on its output. Notice the bubble on one input of each bottom AND gate. That means that there is an inverter on the input, making it active low. A bottom gate's output will be high if one of its inputs is high and the other one is low. A top gate's output is determined in the same manner discussed in the Boolean Logic AND gate section; its output will be high if both of its inputs are high.

The following table shows the results of various combinations of inputs for side A. X means don't care. In other words, all switches will be off with Enable off, regardless of the status of IN 1 or IN 2:

EnA IN 1 IN 2 OUT 1 OUT 2
0 X X All Switches Off - Stop
1 0 0 Bottom Switches On - Brake
1 0 1 Low High
1 1 0 High Low
1 1 1 Top Switches On - Brake

Only one of the brakes is needed, so let's use the bottom transistors to short the motor out. OUT 1 high and OUT 2 low will be forward, and the reverse will be, well, reverse. The table now becomes:

EnA IN 1 IN 2 Status
1 1 0 Forward
1 0 1 Reverse
1 0 0 Brake
0 X X Stop

The following shows the connections for side A:

The protection rectifiers are fast recovery types such as the 1N5416. Fast recovery means they turn off very fast when the reason for them conducting (the back EMF of the motor) is removed. Recovery needs to be less than 200ns (ns = nanoseconds = 10-9 second).

This circuit was tested using 13.8 volts driving a .88 amp motor. One of the clip-on TO-220 heatsinks was fitted to the L298. It got pretty warm with this 12 watt load, but not too hot to touch. It would probably be a good idea to limit the circuit to half of the L298's 25 watt limit and stay under the 2 amp DC limit to be safe.

It's also possible to parallel both sides for more current carrying capability. This will permit going to 4 amps, although it would still be a good idea to hold down the total power:

Notice that channel 1 should be paralleled with channel 4, and channel 2 with channel 3.

The L298 can be bolted to a heatsink through its tab. No insulation is needed since the tab is ground.

The next step is to come up with some software that will make the board drive the various types of h-bridges. The first thing that must be done is to make the output control structure handle both directions of travel. To do that, some sections are added:

// outcnt7a.h

struct OC
{
  int type; // type of bridge being controlled
  int direction; // 0 = off, 1 = forward, 2 = reverse

// Forward Elements

  int ForwardPortAddress; // address of the port with Forward output line

  char ForwardOnMask;     // mask that will turn Forward line on when
                          // ORed with the data value and stored back

  char ForwardOffMask;    // mask that will turn Forward line off when
                          // ANDed with the data value and stored back

  int *ForwardPortData;   // pointer to the data value for the Forward port

  long ForwardSetOn;      // on-time setting for Forward line

  long ForwardSetOff;     // off-time setting for Forward line

  long ForwardOnCount;    // counts left for on time

  long ForwardOffCount;   // counts left for off time

// Reverse Elements

  int ReversePortAddress; // address of the port with Reverse output line

  char ReverseOnMask;     // mask that will turn Reverse line on when
                          // ORed with the data value and stored back

  char ReverseOffMask;    // mask that will turn Reverse line off when
                          // ANDed with the data value and stored back

  int *ReversePortData;   // pointer to the data value for the Reverse port

  long ReverseSetOn;      // on-time setting for Reverse line

  long ReverseSetOff;     // off-time setting for Reverse line

  long ReverseOnCount;    // counts left for on time

  long ReverseOffCount;   // counts left for off time

// Brake Elements

  int BrakePortAddress; // address of the port with Brake output line

  char BrakeOnMask;     // mask that will turn Brake line on when
                         // ORed with the data value and stored back

  char BrakeOffMask;    // mask that will turn Brake line off when
                        // ANDed with the data value and stored back

  int *BrakePortData;   // pointer to the data value for the Brake port

// Direction Elements

  int DirectionPortAddress; // address of the port with Direction output line

  char DirectionOnMask;     // mask that will turn Direction line on when
                            // ORed with the data value and stored back

  char DirectionOffMask;    // mask that will turn Direction line off when
                            // ANDed with the data value and stored back

  int *DirectionPortData;   // pointer to the data value for the Direction port
};

// end outcnt7a.h

Click here to download outcnt7a.h

The new OC structure still has the basic elements from Experiment 6, but adds some. A type element and direction element have been added to the top of the structure. The type element will hold a number indicating what type of h-bridge is being handled by this particular structure in the array. The direction element is 0 for off, 1 for forward and 2 for reverse.

Rather than having a single port address, a pair of masks and a data pointer, the structure now contains both forward and reverse sections, along with the direction and brake information needed by some bridges. Each section has its own data pointer and on and off masks.

The forward and reverse sections add on and off set value elements which serve to hold the counts to be stored in the counters which have also been added. The new configuration routine in the digital module (digi7a.c here) takes changes to the structure into account:


// configure the array number location with the port numbers indicated
// and to the bit numbers dictated by the port numbers
// type:
// 0 = uni-directional, no brake
// 1 = uni-directional with brake
// 2 = pwm line, directional line, no brake
// 3 = pwm line, directional line, with brake
// 4 = dual pwm lines -- both high = brake
// 5 = pwm line and two direction lines as for L298
// 255 = end of points = tells isr not to look anymore & saves time
int ConfigureOutput(int arraynumber,
                    int type,
                    int ForwardPortNumber,
                    int ReversePortNumber,
                    int DirectionPortNumber,
                    int BrakePortNumber)
{
  int x;

  if(arraynumber < 0 || arraynumber > 23)
    return 0; // illegal number

  if(ForwardPortNumber < 0 || ForwardPortNumber > 23)
    return 0; // illegal number

  if(OutputControl[arraynumber] == NULL)
  {
    if((OutputControl[arraynumber] = malloc(sizeof(struct OC))) == NULL)
    {
      printf("Not enough memory for output control.\n");
      return 0;
    }
  }

  // zero out members
  memset(OutputControl[arraynumber], 0, sizeof(struct OC));

  if(type == 255)
  {
    OutputControl[arraynumber]->type = 255;
    return 1;
  }

  // set up the forward masks
  if(!SetPort(arraynumber, ForwardPortNumber,
              &OutputControl[arraynumber]->ForwardPortAddress,
              &OutputControl[arraynumber]->ForwardPortData,
              &OutputControl[arraynumber]->ForwardOnMask,
              &OutputControl[arraynumber]->ForwardOffMask))
    return 0;

  if(!type) // uni-directional no brake has forward pwm only
    return 1;

  if(type == 1 || type == 3 || type == 5) // 1,3,5 use brake line
                                          // (5 uses it for logic lines)
  {
    if(!SetPort(arraynumber, BrakePortNumber,
                &OutputControl[arraynumber]->BrakePortAddress,
                &OutputControl[arraynumber]->BrakePortData,
                &OutputControl[arraynumber]->BrakeOnMask,
                &OutputControl[arraynumber]->BrakeOffMask))
      return 0;
  }

  if(type == 1 || type == 3 || type == 5) // 1,3,5 use direction line
                                          // (5 uses it for logic lines)
  {
    if(!SetPort(arraynumber, DirectionPortNumber,
                &OutputControl[arraynumber]->DirectionPortAddress,
                &OutputControl[arraynumber]->DirectionPortData,
                &OutputControl[arraynumber]->DirectionOnMask,
                &OutputControl[arraynumber]->DirectionOffMask))
      return 0;

    OutputControl[arraynumber]->ReversePortAddress = // reverse is same as forward
      OutputControl[arraynumber]->ForwardPortAddress; // with a direction line

    OutputControl[arraynumber]->ReversePortData =
      OutputControl[arraynumber]->ForwardPortData;

    OutputControl[arraynumber]->ReverseOnMask =
      OutputControl[arraynumber]->ForwardOnMask;

    OutputControl[arraynumber]->ReverseOffMask =
      OutputControl[arraynumber]->ForwardOffMask;
  }

  if(type == 4) // 4 is a dual pwm so has a separate reverse line
  {
    if(!SetPort(arraynumber, ReversePortNumber,
                &OutputControl[arraynumber]->ReversePortAddress,
                &OutputControl[arraynumber]->ReversePortData,
                &OutputControl[arraynumber]->ReverseOnMask,
                &OutputControl[arraynumber]->ReverseOffMask))
      return 0;
  }

  return 1;
}

// portflag settings:
// A CU B CL
// x  x x  x
// low = output, hi = input
// example:
// 0101
// A = out, CU = in, B = out, CL = in

int portflag = 0x0f;  // set by set_up_ppi() -- init to all inputs

// set up flags, etc. for an output port
int SetPort(int arraynumber,
            int PortNumber,
            int *PortAddress,
            int **PortData,
            int *OnMask,
            int *OffMask)
{
  int x;

  if(arraynumber < 0 || arraynumber > 23)
    return 0;

  if(PortNumber < 0 || PortNumber > 23)
    return ClearPort(arraynumber); // error - free port & return 0

  *OnMask = 1 << (PortNumber & 7); // shift by bits[2:0]
  *OffMask = ~*OnMask;

  x = PortNumber >> 3; // the port number is in bits 3 and 4

  switch(x) // 0 = Port A, 1 = Port B, 2 = Port C
  {
    case 0:
    if(portflag & 8) // bit 3 hi = A in, not out
      return ClearPort(arraynumber);
    *PortAddress = ppi_porta; // address for Port A
    *PortData = &porta_val;   // point to Port A data value
    break;

    case 1:
    if(portflag & 2) // bit 1 hi = B in, not out
      return ClearPort(arraynumber);
    *PortAddress = ppi_portb; // address for Port B
    *PortData = &portb_val;   // point to Port B data value
    break;

    case 2:
    if(portflag & 1 && *OnMask &0x0f) // bit 0 hi = CL in, not out
      return ClearPort(arraynumber);
    if(portflag & 4 && *OnMask &0xf0) // bit 2 hi = CU in, not out
      return ClearPort(arraynumber);
    *PortAddress = ppi_portc; // address for Port C
    *PortData = &portc_val;   // point to Port C data value
    break;
  }

  return 1;
}

Click here to download digi7a.c
Click here to download digi7a.h

After checking the range of the forward port number and array number, the routine tries to reserve memory for the position using malloc, then it uses memset to clear all members to 0. If type is 255, it means this is the last position used in the array, so it sets the structure's type member to 255 also, then leaves with a return of 1 (0 means there has been an error).

Once the routine reaches this point, the call to it is considered to be a legitimate pwm node setup. It first calls SetPort(..) to set up the forward parameters. Notice the parameters inside the call to SetPort(..) that are prefixed with an ampersand (&). Recall from Experiment 2 that variables passed by value to a routine can't be modified by the routine because a copy, rather than the actual variable, is used in the routine.

Here however, pointers are being passed of the variables that need to be set by the routine. SetPort(..) can dereference the pointers and gain access to the original variables. Notice the pointer to port data. Since it is already a pointer, SetPort(..) accepts it as a pointer to a pointer, indicated by the two asterisks for the **PortData argument. Dereferencing it allows setting the address to which it points.

Even though arguments sent to SetPort(..) are derived from the structure, they don't look like structure elements to SetPort(..). They look like normal arguments passed by value or as pointers. The result is a simplification which makes the routine a little easier to read. It also avoids using the structure as a global.

For example, the on mask is set in the following manner in Experiment 2:
OutputControl[arraynumber]->onmask = 1 << (portselect & 7);

Here, it's just:
*OnMask = 1 << (PortNumber & 7);
then the off mask is set:
*OffMask = ~*OnMask;

The portflag variable gets set to the mode variable sent to set_up_ppi(..). That allows SetPort(..) to check ports to make sure they are set up as outputs. Bits 3 and 4 of the PortNumber argument are used for the numerical reference for the switch statement. That permits checking to make sure the port is an output, then setting the port address and port data pointer.

Back to ConfigureOutput(..). The process is finished after setting up the forward elements if type is 0, which means the structure node is used for a uni-directional driver, such as one of the single T0-220 circuits. From there, decisions about what to set up are made based on the type number. For example, the brake and direction elements are set up for types 1, 3 and 5.

The new interrupt service routine (ISR) is shown below:

// the timer interrupt handler
// type:
// 0 = unidirctional, no brake
// 1 = unidirectional with brake
// 2 = pwm line, directional line, no brake
// 3 = pwm line, directional line, with brake
// 4 = dual pwm lines -- both high = brake
// 5 = pwm line and two direction lines as for L298
// 255 = last slot -- leave
interrupt new_timer()
{
  int x;

  disable();

  timer_counter++;

  for(x=0; x<24; x++)
  {
    if(OutputControl[x] == NULL // not set up or off
      || !OutputControl[x]->direction)
          continue;

    if(OutputControl[x]->type == 255) // last slot
          break;

    if(OutputControl[x]->direction == 1
      || OutputControl[x]->type < 2)
    {
      if(OutputControl[x]->ForwardOnCount > 0L)
      {
        OutputControl[x]->ForwardOnCount--;

        if(!OutputControl[x]->ForwardOnCount)
        {
          // keep existing bits but remove this one
          *OutputControl[x]->ForwardPortData
            &= OutputControl[x]->ForwardOffMask;

          // put the result in this node's port register
          outp(OutputControl[x]->ForwardPortAddress,
            *OutputControl[x]->ForwardPortData);

          OutputControl[x]->ForwardOffCount =
            OutputControl[x]->ForwardSetOff;
        }
      }

      // note that this will not decrement as soon as set
      // above, but will wait until the next interrupt
      else if(OutputControl[x]->ForwardOffCount > 0L)
      {
        OutputControl[x]->ForwardOffCount--;

        if(!OutputControl[x]->ForwardOffCount)
        {
          // keep existing bits and OR this one in
          *OutputControl[x]->ForwardPortData |=
            OutputControl[x]->ForwardOnMask;

          // put the result in this node's port register
          outp(OutputControl[x]->ForwardPortAddress,
            *OutputControl[x]->ForwardPortData);

          OutputControl[x]->ForwardOnCount =
            OutputControl[x]->ForwardSetOn;

        }

      }

    } // end if(OutputControl[x]->direction == 1
      // || OutputControl[x]->type < 2)

    else if(OutputControl[x]->direction == 2
        && OutputControl[x]->type > 1) // types 2,3 and 4 can have reverse
    {
      if(OutputControl[x]->ReverseOnCount > 0L)
      {
        OutputControl[x]->ReverseOnCount--;

        if(!OutputControl[x]->ReverseOnCount)
        {
          // keep existing bits but remove this one
          *OutputControl[x]->ReversePortData
            &= OutputControl[x]->ReverseOffMask;

          // put the result in this node's port register
          outp(OutputControl[x]->ReversePortAddress,
            *OutputControl[x]->ReversePortData);

          OutputControl[x]->ReverseOffCount =
            OutputControl[x]->ReverseSetOff;
        }

      } // end if(OutputControl[x]->ReverseOnCount > 0L)

      // note that this will not decrement as soon as set
      // above, but will wait until the next interrupt
      else if(OutputControl[x]->ReverseOffCount > 0L)
      {
        OutputControl[x]->ReverseOffCount--;

        if(!OutputControl[x]->ReverseOffCount)
        {
          // keep existing bits and OR this one in
          *OutputControl[x]->ReversePortData |=
            OutputControl[x]->ReverseOnMask;

          // put the result in this node's port register
          outp(OutputControl[x]->ReversePortAddress,
            *OutputControl[x]->ReversePortData);

          OutputControl[x]->ReverseOnCount =
            OutputControl[x]->ReverseSetOn;
        }

      } // end else if(OutputControl[x]->ReverseOffCount > 0L)

    } // end else if(OutputControl[x]->direction == 2
      // && OutputControl[x]->type > 1)

  } // end for(x=0; x<24; x++)

  enable();

} // end  new_timer()

It runs through all of the array locations and skips the ones that are not being used as indicated by their being NULL. It breaks out of the loop if a direction element is 255. It counts down the up or down counters as dictated by the direction element and turns on or off the appropriate port lines.

The pwm(..) routine in the timer module is used to set up a pwm channel:


// Set up Pulse Width Modulation for an output
//
// arraynumber is the position in the output control array
//
// type:
// 0 = uni-directional, no brake
// 1 = uni-directional with brake
// 2 = pwm line, directional line, no brake
// 3 = pwm line, directional line, with brake
// 4 = dual pwm lines -- both high = brake
// 5 = pwm line and two direction lines as for L298
// 255 = last slot -- leave
//
// Forward and Reverse port numbers are pwm lines
//
// The Direction port number is provided for bridges that have a reverse line
// Set to anything if not used
//
// The Brake port number is provided for circuits that have a brake line
// Set to anything if not used
//
int pwm(int arraynumber, int type,
    int ForwardPortNumber, int ReversePortNumber,
    int DirectionPortNumber, int BrakePortNumber,
    double ForwardOnTime, double ForwardOffTime,
    double ReverseOnTime, double ReverseOffTime,
    int StartDirection)
{
  int x;

  if(StartDirection < 0 || StartDirection > 2)
      return 0;

  if(ForwardOnTime <= MinTime || ForwardOnTime >= MaxTime
    || ForwardOffTime <= MinTime || ForwardOffTime >= MaxTime)
      return 0;

  disable(); // no interrupts while setting up

  if(!ConfigureOutput(arraynumber, type,
                      ForwardPortNumber,
                      ReversePortNumber,
                      DirectionPortNumber,
                      BrakePortNumber))
  {
    enable();
    return 0;
  }

  OutputControl[arraynumber]->ForwardSetOn =
    (long)((frequency * ForwardOnTime) + 0.5); // round up at .5

  OutputControl[arraynumber]->ForwardSetOff
    = (long)((frequency * ForwardOffTime) + 0.5);

  OutputControl[arraynumber]->ForwardOnCount
    = OutputControl[arraynumber]->ForwardSetOn;

  OutputControl[arraynumber]->ForwardOffCount
    = OutputControl[arraynumber]->ForwardSetOff;

  OutputControl[arraynumber]->direction = StartDirection;

  OutputControl[arraynumber]->type = type;

  if(!type) // uni directional
  {
    enable();
    return 1;
  }

  if(type == 1 || type == 3) // 1 and 3 have a brake
  {
    *OutputControl[arraynumber]->BrakePortData
      &= OutputControl[arraynumber]->BrakeOffMask; // turn off the brake

    outp(OutputControl[arraynumber]->BrakePortAddress,
      *OutputControl[x]->BrakePortData);
  }


  if(type > 1) // 2,3,4,5 use reverse pwm line, 2,3,5 use direction line
  {
    if(ReverseOffTime <= MinTime || ReverseOffTime >= MaxTime
      || ReverseOffTime <= MinTime || ReverseOffTime >= MaxTime)
    {
      free(OutputControl[arraynumber]);
      OutputControl[arraynumber] = NULL;
      enable();
      return 0;
    }

    OutputControl[arraynumber]->ReverseSetOn =
      (long)((frequency * ReverseOnTime) + 0.5); // round up at .5

    OutputControl[arraynumber]->ReverseOnCount
      = OutputControl[arraynumber]->ReverseSetOn;

    OutputControl[arraynumber]->ReverseSetOff =
      (long)((frequency * ReverseOffTime) + 0.5);

    OutputControl[arraynumber]->ReverseOffCount
      = OutputControl[arraynumber]->ReverseSetOff;

    if(type == 2 || type == 3 || type == 5) // 2,3,5 use a direction line
    {
      if(StartDirection == 1)
      *OutputControl[arraynumber]->DirectionPortData
        != OutputControl[arraynumber]->DirectionOnMask; // set for forward

      else if(StartDirection == 2)
      *OutputControl[arraynumber]->DirectionPortData
        &= OutputControl[arraynumber]->DirectionOffMask; // clear for reverse

      outp(OutputControl[arraynumber]->DirectionPortAddress,
        *OutputControl[x]->DirectionPortData);

      if(type == 5)
      {
        if(StartDirection == 1)
          *OutputControl[arraynumber]->BrakePortData
            &= OutputControl[arraynumber]->BrakeOffMask; // turn off the brake

        else if(StartDirection == 2)
          *OutputControl[arraynumber]->BrakePortData
            |= OutputControl[arraynumber]->BrakeOnMask; // turn on the brake

        outp(OutputControl[arraynumber]->BrakePortAddress,
          *OutputControl[x]->BrakePortData);
      }
    }

  } // end if(type > 1)

  enable();

  return 1;

} // end int pwm(..)

Click here to download timer7a.c
Click here to download timer7a.h

These contain all of the timer setup, pwm and other timer routines on this page.

The StartDirection variable is first checked to make sure it ranges from 0 to 2 (0 = off, 1 = forward, 2 = reverse). All on and off times are checked for error against MinTime and MaxTime, which are determined when the timer is set up. MinTime is set to be equal to the period of the timer, which is 1.0/frequency. The timer can't move faster than it moves.

MaxTime is determined thus:
MaxTime = (pow(2.0, 32.0)/2.0)/frequency;
The standard compiler function pow(..) has the prototype
double pow(double base, double exponent);

It returns the base raised to the exponent. In this case, it's 2 raised to the 32nd power. Longs are 32 bits (at least with the DOS compiler I use), and binary has a base of 2. The maximum number that a long can hold is 232. The maximum allowable size is set to half that.

Interrupts are then disabled so nothing will be done that might conflict, then ConfigureOutput(..) output is called (described above). If that is successful, the forward elements are given values.

The forward and reverse sections have set on and set off elements that are longs. They hold the values to which the on and off counters will be set as appropriate. The on and off time arguments are doubles to permit as fine of resolution as possible. The set on and set off elements are calculated from the input arguments, but then cast (see Experiment 5) to longs.

For example, the forward set on value is calculated thus:

OutputControl[arraynumber]->ForwardSetOn =
(long)((frequency * ForwardOnTime) + 0.5);

The actual count will be the frequency times the on time. If the frequency is 1000Hz, for example, it takes 1000 cycles to take up one second, 1500 cycles to take up 1.5 seconds or 35 cycles to take up .035 seconds (35ms). Rounding is done at .5 counts. The long cast returns only the portion of the number to the left of the decimal.

An example: The frequency is 2386.36Hz and the on time is .025 seconds.
1. .025 * 2386.36 = 59.659
2. Add .5 and get: 59.659 + .5 = 60.159
3. Use only the portion to left of the decimal for the long set value = 60

The actual time will be 60/2386.36 = .025142895 seconds

Forward on and off counts are given their respective set values, and the structure's type and start direction values are set to the corresponding argument values. If type = 0, interrupts are enabled and a 1 is returned since the process is over.

A brake line is used if the type is 1 or 3. The routine turns the line off if that's the case.

If the type is 2, 3, 4 or 5, the reverse pwm line is used, so reverse on and off times are checked then set up if they are OK in the same manner as the forward on and off times. Notice that the memory used by the node is freed and the node set to NULL if there is an error in order to keep the ISR described above from using the node.

Types 2, 3 and 5 use a direction line. Only forward and reverse modes need be considered since the node was memset to 0. The line is turned on if StartDirection is set to 1 and turned off if it is set to 2.

Type 5 is for an L298 h-bridge. It uses the brake line in addition to the direction line for direction logic. The brake line is turned off for forward and on for reverse -- the reverse of the direction line.

Let's say we want to set up to run a simple, uni-directional pwm motor. To do that, enter the items needed and use 0s for the ones not needed:

pwm(MainMotor, 0, // array number, type
    PA0, 0,       // forward port, reverse port not used
    0,0,          // direction and brake not used
    0.01, 0.001,  // forward on and forward off times
    0,0,1);       // reverse on and off not used, start forward

MainMotor is part of an enumeration as explained in Experiment 6. PA0 is used as the pwm line, and the motor is supposed to start in the forward mode, which is all type 0 can do other than be turned off.

A shortcut can be written that takes care of all of the arguments that will be set to 0 anyway:

// set up a uni-directioanl pwm channel
int UniPwm(int arraynumber, int ForwardPortNumber,
    double ForwardOnTime, double ForwardOffTime)
{
  return pwm(arraynumber, 0, // arraynum, type 0
             ForwardPortNumber, 0, // forward port, no rev port
             0, 0, ForwardOnTime,  // no direction, no brake, forward on time
             ForwardOffTime, 0, 0, 1); // fwd off time, no rev on/off, fwd start
}

All UniPwm(..) needs is an array number, the forward port number, and the on time and off time. Notice that it returns what it gets when it calls pwm(..). An even shorter short cut only needs the duty cycle, rather than an on and off time:

// set up a uni-directioanl pwm channel using duty cycle
int UniPwmDuty(int arraynumber, int ForwardPortNumber,
    double ForwardDutycycle)
{
  int x;
  double ForwardOnTime,ForwardOffTime;

  if(ForwardDutycycle > 1.0)
    ForwardDutycycle/=100.0;

  if(ForwardDutycycle > 1.0)
    ForwardDutycycle = 1.0;

  if(ForwardDutycycle <= 0.5)
  {
    ForwardOnTime = minpulse;
    ForwardOffTime = ((1.0 - ForwardDutycycle)/ForwardDutycycle) * ForwardOnTime;
  }

  else
  {
    ForwardDutycycle = 1.0 - ForwardDutycycle;
    ForwardOffTime = minpulse;
    ForwardOnTime = ((1.0 - ForwardDutycycle)/ForwardDutycycle) * ForwardOffTime;
  }

  return UniPwm(arraynumber, ForwardPortNumber,
    ForwardOnTime, ForwardOffTime);
}

UniPwmDuty(..) allows the duty cycle to be expressed as a fraction or as a percent. The ForwardDutyCycle argument is treated as a decimal fraction if it is less than 1, or as a percent if greater. It is eventually worked with as a decimal fraction or 1 In both cases. It is divided by 100 if it is greater than 1.

If the duty cycle is then less than or equal to .5, the forward on time is set equal to the minpulse value that was set in the set_up_new_timer(..) routine. If it is greater than .5, then the forward off time is made equal to minpulse and the forward duty cycle is set to 1 - the forward duty cycle.

The purpose of all this number manipulation is to determine the caller's intent, determine whether on or off should be the smaller value and set it to the minimum pulse width size. That's about 2.5ms for the first two circuits above, or about 10ms if an L289 is included in the mix of circuits. The long pulse value is calculated using the short pulse.

For example, let's say the caller sends 30 as the duty cycle. It's first divided by 100 since it's greater than 1, which makes it .3. Since .3 is less than .5, the on time is assigned minpulse. The on time is thus equal to minpulse and is also .3 of the total time. The off time must then be .7 of the total time since the sum of the two must equal 1. Now let's say set_up_new_timer(..) sets minpulse to 10ms. Thus, if T is the total time,
.3T = .01
Divide both sides by .3:
T = .01/.3 = .033333333 seconds total time
Off time is .7 of total time, so it's
OffTime = .7 * .033333333 = .023333333 seconds
Also, OffTime = ((1 - .3)/.3) * .01
This determines how many times larger off is than on, then uses that to multiply by on to get off. There is no need to first determine the total time. This is the process used in the code above.

The fractions simply switch places if the duty cycle is greater than .5. UniPwmDuty(..) returns the return of UniPwm(..), which is given the array number, the forward port number and the calculated on and off times. Recall that UniPwm(..) returns the return of pwm(..). Cascading returns in this manner is very common.

Another handy shortcut is used to blink indicators such as LEDs:

// start a blinker
Blink(int arraynumber, int BlinkPort, double BlinkOnTime, double BlinkOffTime)
{
  return pwm(arraynumber, 0, // arraynum, type 0
          BlinkPort, 0, // forward port, no rev port
          0, 0, BlinkOnTime,  // no direction, no brake, forward on time
          BlinkOffTime, 0, 0, 1); // fwd off time, no rev on/off, fwd start
}

Just give it the array location, the port you want to use and the on and off times.

Use the following to test the above:

// experi7a.c

#include <dos.h>
#include <stdio.h>
#include <bios.h>

// defines OC structure
#include "outcnt7a.h"

// include header with constants
#include "const7a.h"

// include header with external prototypes
#include "extern7a.h"

enum
{
  MainMotor,
  LED1,
  LED2,
  LASTSLOT
};

void main(void)
{
  int x;
  double dc,oldfreq,newfreq;

  oldfreq = 1193180.0/65536.0;

  get_port(); // get the port number and establish register locations

  // make everthing an output
  set_up_ppi(Aout_CUout_Bout_CLout);

  set_up_new_timer(2000.0);

  newfreq = get_frequency();

  printf("old frequency = %f new frequency = %f Hz\n"
  ,oldfreq,newfreq);

  printf("10%%:\n");
  UniPwmDuty(MainMotor, PA0, 10.0);
  Blink(LED1, PA1, .3, .2);
  Blink(LED2, PA2, .15, .1);
  SetLast(LASTSLOT);

  showall();
  printf("\npress any key to continue\n");
  getch();

  printf("\n.01 on, .01 off:\n");
  pwm(MainMotor, 0, // array number, type
    PA0, 0,         // forward port, reverse port not used
    0,0,            // direction and brake not used
    0.01, 0.01,    // forward on and forward off times
    0,0,1);         // reverse on and off not used, start forward

  show(MainMotor);
  printf("\npress any key to continue\n");
  getch();

  while(!kbhit())
  {
    for(dc=20.0; dc<=90.0; dc+=10.0)
    {
      printf("dutycycle = %f\n",dc);

      UniPwmDuty(MainMotor, PA0, dc);

      wait(.1);

      if(kbhit())
        break;
    }

    if(dc < 90.0)
    	break;

    for(dc=80.0; dc>20.0; dc-=10.0)
    {
      printf("dutycycle = %f\n",dc);

      UniPwmDuty(MainMotor, PA0, dc);

      wait(.1);

      if(kbhit())
     	 break;
    }

    if(dc > 20.0)
    	break;
  }

  // don't forget to free memory!
  FreeOutputControl();

  portaoff();

  // be sure to restore the timer!
  restore_old_timer();
}

// end experi7a.c

Click here to download timer7a.c
Click here to download timer7a.h
Click here to download digi7a.c
Click here to download digi7a.h
Click here to download outcnt7a.h
Click here to download const7a.h
Click here to download extern7a.h
Click here to download experi7a.c

Once you have downloaded everything, compile each of the C files then link them to produce experi7a.exe. After that's done, simply type in experi7a and press the enter key to run the program. The motor should increase then decrease speed continuously until a key is pressed.

It would be a good idea to connect LEDs to PA1 and PA2 so there will be a visual indication that the system is working. Use 390 ohm resistors for current limiting (see Experiment 3).

The following is another shortcut routine, this time for the IRF3708 circuit above that uses dual pwm lines:

// set up a dual pwm channel using duty cycle
int DualPwmDuty(int arraynumber, int StartDirection,
    int ForwardPortNumber, double ForwardDutycycle,
    int ReversePortNumber, double ReverseDutycycle)
{
  double ForwardOnTime,ForwardOffTime;
  double ReverseOnTime,ReverseOffTime;

  if(ForwardDutycycle > 1.0)
    ForwardDutycycle/=100.0;

  if(ForwardDutycycle > 1.0)
    ForwardDutycycle = 1.0;

  if(ForwardDutycycle <= 0.5)
  {
    ForwardOnTime = minpulse;
    ForwardOffTime = ((1.0 - ForwardDutycycle)/ForwardDutycycle) * ForwardOnTime;
  }

  else
  {
    ForwardDutycycle = 1.0 - ForwardDutycycle;
    ForwardOffTime = minpulse;
    ForwardOnTime = ((1.0 - ForwardDutycycle)/ForwardDutycycle) * ForwardOffTime;
  }

  if(ReverseDutycycle > 1.0)
    ReverseDutycycle/=100.0;

  if(ReverseDutycycle > 1.0)
    ReverseDutycycle = 1.0;

  if(ReverseDutycycle <= 0.5)
  {
    ReverseOnTime = minpulse;
    ReverseOffTime = ((1.0 - ReverseDutycycle)/ReverseDutycycle) * ReverseOnTime;
  }

  else
  {
    ReverseDutycycle = 1.0 - ReverseDutycycle;
    ReverseOffTime = minpulse;
    ReverseOnTime = ((1.0 - ReverseDutycycle)/ReverseDutycycle) * ReverseOffTime;
  }

  return pwm(arraynumber, 4, // type 4 is dual pwm
    ForwardPortNumber, ReversePortNumber,
    0,0, // no direction or brake port numbers
    ForwardOnTime, ForwardOffTime,
    ReverseOnTime, ReverseOffTime,
    StartDirection);
}

Most of the calculations are the same. The only real difference is the fact that DualPwmDuty(..) needs one more array location and duty-cycle variable, and that the call to pwm(..) adds the appropriate variables. Note that the motor will not immediately start if StartDirection is set to 0. This is useful in situations where it is desirable to set up the motor on initiation, but not turn it on until later.

The Forward(..) and Reverse(..) routines set the structure's direction variable as appropriate and turn on or off pwm lines as needed. Note that the L298 part of the routines (type = = 5) uses the brake line as part of its direction logic, in addition to the direction line. The Stop(..) routine sets the direction variable to 0 and turns off the pwm lines:

// go forward
int Forward(arraynumber)
{
  if(OutputControl[arraynumber] == NULL)
    return 0;

  if(OutputControl[arraynumber]->type < 2)
    return 0; // < 2 is unidirectional

  if(OutputControl[arraynumber]->type == 255)
    return 0; // 255 is last node

  disable();

  OutputControl[arraynumber]->direction = 1;

  // 2,3 and 5 use the direction line
  if(OutputControl[arraynumber]->type == 2
  || OutputControl[arraynumber]->type == 3
  || OutputControl[arraynumber]->type == 5)
  {
    *OutputControl[arraynumber]->DirectionPortData
      |= OutputControl[arraynumber]->DirectionOnMask; // set for forward

    outp(OutputControl[arraynumber]->DirectionPortAddress,
      *OutputControl[arraynumber]->DirectionPortData);

    if(OutputControl[arraynumber]->type == 5)
    {
      *OutputControl[arraynumber]->BrakePortData
        &= OutputControl[arraynumber]->BrakeOffMask; // turn off the brake line

      outp(OutputControl[arraynumber]->BrakePortAddress,
        *OutputControl[arraynumber]->BrakePortData);
    }
  }

  // keep existing bits but remove this one
  *OutputControl[arraynumber]->ReversePortData
  &= OutputControl[arraynumber]->ReverseOffMask;

  // put the result in this node's port register
  outp(OutputControl[arraynumber]->ReversePortAddress,
  *OutputControl[arraynumber]->ReversePortData);

  enable();

  return 1;
}

// go in reverse
int Reverse(arraynumber)
{
  if(OutputControl[arraynumber] == NULL)
    return 0;

  if(OutputControl[arraynumber]->type < 2)
    return 0; // < 2 is unidirectional

  if(OutputControl[arraynumber]->type == 255)
    return 0; // 255 is last node

  disable();

  OutputControl[arraynumber]->direction = 2;

  // 2,3 and 5 use the direction line
  if(OutputControl[arraynumber]->type == 2
  || OutputControl[arraynumber]->type == 3
  || OutputControl[arraynumber]->type == 5)
  {
    *OutputControl[arraynumber]->DirectionPortData
      &= OutputControl[arraynumber]->DirectionOffMask; // clear for reverse

    outp(OutputControl[arraynumber]->DirectionPortAddress,
      *OutputControl[arraynumber]->DirectionPortData);

    if(OutputControl[arraynumber]->type == 5)
    {
      *OutputControl[arraynumber]->BrakePortData
        &= OutputControl[arraynumber]->BrakeOnMask; // turn on the brake line

      outp(OutputControl[arraynumber]->BrakePortAddress,
        *OutputControl[arraynumber]->BrakePortData);
    }
  }

  // keep existing bits but remove this one
  *OutputControl[arraynumber]->ForwardPortData
  &= OutputControl[arraynumber]->ForwardOffMask;

  // put the result in this node's port register
  outp(OutputControl[arraynumber]->ForwardPortAddress,
  *OutputControl[arraynumber]->ForwardPortData);

  enable();

  return 1;
}

// stop a pwm channel
int Stop(int arraynumber)
{
  if(OutputControl[arraynumber] == NULL)
    return 0;

  disable();

  OutputControl[arraynumber]->direction = 0;

  // keep existing bits but remove this one for forward
  // for L289, this takes its enable line low
  *OutputControl[arraynumber]->ForwardPortData &=
    OutputControl[arraynumber]->ForwardOffMask;

  // put the result in this node's port register
  outp(OutputControl[arraynumber]->ForwardPortAddress,
       *OutputControl[arraynumber]->ForwardPortData);

  // keep existing bits but remove this one for reverse
  *OutputControl[arraynumber]->ReversePortData &=
    OutputControl[arraynumber]->ReverseOffMask;

  // put the result in this node's port register reverse
  outp(OutputControl[arraynumber]->ReversePortAddress,
       *OutputControl[arraynumber]->ReversePortData);

  enable();

  return 1;
}

Use the following to test the routines:

// experi7b.c

#include <dos.h>
#include <stdio.h>
#include <bios.h>

// defines OC structure
#include "outcont.h"

// include header with constants
#include "constant.h"

// include header with external prototypes
#include "extern.h"

enum
{
  MainMotor,
  LED1,
  LED2,
  LASTSLOT
};

void main(void)
{
  int x;
  double dc,oldfreq,newfreq;

  oldfreq = 1193180.0/65536.0;

  get_port(); // get the port number and establish register locations

  // make everthing an output
  set_up_ppi(Aout_CUout_Bout_CLout);

  set_up_new_timer(2000.0);

  newfreq = get_frequency();

  printf("old frequency = %f new frequency = %f Hz\n"
  ,oldfreq,newfreq);

  // set up a dual pwm channel using duty cycle
  DualPwmDuty(MainMotor, 0, // set up, but don't start main motor
    PA0, 75.0, // forward uses line PA0 at 75%
    PA1, 15.0); // reverse uses line PA1 at 15%
  Blink(LED1, PA2, .3, .2);
  Blink(LED2, PA3, .15, .1);
  SetLast(LASTSLOT);

  printf("MainMotor settings with 75%% forward and 15%% reverse:\n");
  show(MainMotor);
  printf("LEDs should be blinking and pwm off -- press any key to start test\n");
  getch();

  while(!kbhit())
  {
    printf("forward 75%% 2 seconds\n");
    Forward(MainMotor);
    wait(2.0); // wait 2 seconds to be kind to power supply

    printf("stop 2 seconds\n");
    Stop(MainMotor);
    wait(2.0); // wait 2 seconds to be kind to power supply

    printf("reverse 15%% 2 seconds\n");
    Reverse(MainMotor);
    wait(2.0); // wait 2 seconds to be kind to power supply

    printf("stop 2 seconds\n");
    Stop(MainMotor);
    wait(2.0); // wait 2 seconds to be kind to power supply
  }

  portaoff();
  portboff();
  portboff();

  // don't forget to free memory!
  FreeOutputControl();

  // be sure to restore the timer!
  restore_old_timer();
}

// end experi7b.c

Click here to download constant.h
Click here to download constant.h
Click here to download extern.h
Click here to download experi7b.c

Link it with the timer7a and digi7a modules to form experi7b.exe.

This one turns on the brake for a dual pwm h-bridge by taking both pwm lines high for .1 second:

// brake a dual pwm channel
// taking both lines high brakes
int BrakeDualPwm(int arraynumber)
{
  if(OutputControl[arraynumber] == NULL)
    return 0;

  disable();

  OutputControl[arraynumber]->direction = 0;

  // keep existing bits but remove this one for forward
  *OutputControl[arraynumber]->ForwardPortData |=
    OutputControl[arraynumber]->ForwardOnMask;

  // put the result in this node's port register
  outp(OutputControl[arraynumber]->ForwardPortAddress,
       *OutputControl[arraynumber]->ForwardPortData);

  // keep existing bits but remove this one
  *OutputControl[arraynumber]->ReversePortData |=
    OutputControl[arraynumber]->ReverseOnMask;

  // put the result in this node's port register reverse
  outp(OutputControl[arraynumber]->ReversePortAddress,
       *OutputControl[arraynumber]->ReversePortData);

  enable();

  wait(0.1); // brake .1 sec

  Stop(arraynumber); // release lines

  return 1;
}

Use the following to test the brake routine:

// experi7c.c

#include <dos.h>
#include <stdio.h>
#include <bios.h>

// defines OC structure
#include "outcont.h"

// include header with constants
#include "constant.h"

// include header with external prototypes
#include "extern.h"

enum
{
  MainMotor,
  LED1,
  LED2,
  LASTSLOT
};

void main(void)
{
  int x;
  double dc,oldfreq,newfreq;

  oldfreq = 1193180.0/65536.0;

  get_port(); // get the port number and establish register locations

  // make everthing an output
  set_up_ppi(Aout_CUout_Bout_CLout);

  set_up_new_timer(2000.0);

  newfreq = get_frequency();

  printf("old frequency = %f new frequency = %f Hz\n"
  ,oldfreq,newfreq);

  // set up a dual pwm channel using duty cycle
  DualPwmDuty(MainMotor, 0, // set up, but don't start main motor
    PA0, 20.0, // forward uses line PA0 at 20%
    PA1, 20.0); // reverse uses line PA1 at 20%
  Blink(LED1, PA2, .3, .2);
  printf("LED1 on/off settings:\n");
  show(LED1);
  Blink(LED2, PA3, .15, .1);
  printf("LED2 on/off settings:\n");
  show(LED2);
  SetLast(LASTSLOT);

  printf("MainMotor on/off settings with 75%% forward and 15%% reverse:\n");
  show(MainMotor);
  printf("LEDs blinking and pwm off -- esc to quit, any other start test\n");

  if(getch() != 27) // escape key
  {
    while(!kbhit())
    {
      printf("forward 20%% 1 second\n");
      Forward(MainMotor);
      wait(1.0); // wait 1 second

      printf("brake, wait .5 seconds\n");
      BrakeDualPwm(MainMotor);
      wait(0.5); // wait 1 second

      printf("reverse 20%% 1 second\n");
      Reverse(MainMotor);
      wait(1.0); // wait 1 second

      printf("brake, wait .5 seconds\n");
      BrakeDualPwm(MainMotor);
      wait(0.5); // wait 1 second
    }
  }

  portaoff();
  portboff();
  portboff();

  // don't forget to free memory!
  FreeOutputControl();

  // be sure to restore the timer!
  restore_old_timer();
}

// end experi7c.c

Click here to download constant.h
Click here to download constant.h
Click here to download extern.h
Click here to download experi7c.c

Link it with the timer7a and digi7a modules to form experi7c.exe.

This one sets up an L298:

// set up an L289
int L289PwmDuty(int arraynumber, int StartDirection,
    int PwmPortNumber, double ForwardDutycycle,
    double ReverseDutycycle, int Direction1Port, int Direction2Port)
{
  double ForwardOnTime,ForwardOffTime;
  double ReverseOnTime,ReverseOffTime;

  if(ForwardDutycycle > 1.0)
    ForwardDutycycle/=100.0;

  if(ForwardDutycycle > 1.0)
    ForwardDutycycle = 1.0;

  if(ForwardDutycycle <= 0.5)
  {
    ForwardOnTime = minpulse;
    ForwardOffTime = ((1.0 - ForwardDutycycle)/ForwardDutycycle) * ForwardOnTime;
  }

  else
  {
    ForwardDutycycle = 1.0 - ForwardDutycycle;
    ForwardOffTime = minpulse;
    ForwardOnTime = ((1.0 - ForwardDutycycle)/ForwardDutycycle) * ForwardOffTime;
  }

  if(ReverseDutycycle > 1.0)
    ReverseDutycycle/=100.0;

  if(ReverseDutycycle > 1.0)
    ReverseDutycycle = 1.0;

  if(ReverseDutycycle <= 0.5)
  {
    ReverseOnTime = minpulse;
    ReverseOffTime = ((1.0 - ReverseDutycycle)/ReverseDutycycle) * ReverseOnTime;
  }

  else
  {
    ReverseDutycycle = 1.0 - ReverseDutycycle;
    ReverseOffTime = minpulse;
    ReverseOnTime = ((1.0 - ReverseDutycycle)/ReverseDutycycle) * ReverseOffTime;
  }

  return pwm(arraynumber, 5,
    PwmPortNumber, PwmPortNumber, // frwrd & rev same = pwm/enable line
    Direction1Port, Direction2Port, // direction gets dir1, brake dir2
    ForwardOnTime, ForwardOffTime,
    ReverseOnTime, ReverseOffTime,
    StartDirection);
}

This one provides a brake for the L298 by taking both of the logic lines low and the pwm (enable) line high:

// apply brake for an L298
int BrakeL298(int arraynumber)
{
  if(OutputControl[arraynumber] == NULL)
    return 0;

  disable();

  OutputControl[arraynumber]->direction = 0;

  // make enable line high
  *OutputControl[arraynumber]->ForwardPortData
    |= OutputControl[arraynumber]->ForwardOnMask;

  // put the result in this node's port register
  outp(OutputControl[arraynumber]->ForwardPortAddress,
    *OutputControl[arraynumber]->ForwardPortData);

  // make direction and brake lines low for brake on bottom switches
  *OutputControl[arraynumber]->DirectionPortData
    &= OutputControl[arraynumber]->DirectionOffMask;

  outp(OutputControl[arraynumber]->DirectionPortAddress,
    *OutputControl[arraynumber]->DirectionPortData);

  // make brake line low
  *OutputControl[arraynumber]->BrakePortData
    &= OutputControl[arraynumber]->BrakeOnMask;

  outp(OutputControl[arraynumber]->BrakePortAddress,
   *OutputControl[arraynumber]->BrakePortData);

  enable();

  wait(0.1); // brake .1 sec

  // make enable line low
  *OutputControl[arraynumber]->ForwardPortData
    &= OutputControl[arraynumber]->ForwardOffMask;

  return 1;
}

Use this to test the L2289 routines:

// experi7e.c

#include <dos.h>
#include <stdio.h>
#include <bios.h>

// defines OC structure
#include "outcont.h"

// include header with constants
#include "constant.h"

// include header with external prototypes
#include "extern.h"

enum
{
  MainMotor,
  LED1,
  LED2,
  LASTSLOT
};

void main(void)
{
  int x;
  double dc,oldfreq,newfreq;

  oldfreq = 1193180.0/65536.0;

  get_port(); // get the port number and establish register locations

  // make everthing an output
  set_up_ppi(Aout_CUout_Bout_CLout);

  set_up_new_timer(2000.0);

  SetLast(LASTSLOT);

  newfreq = get_frequency();

  printf("old frequency = %f new frequency = %f Hz\n"
  ,oldfreq,newfreq);

  Blink(LED1, PA3, .3, .2);
  Blink(LED2, PA4, .15, .1);
  SetLast(LASTSLOT);

  // set up an L289
  L289PwmDuty(MainMotor, 0, // arraynumer, start direction
              PA0, 30.0,    // pwm/enable port, fwrd duty
              50.0,         // reverse dutycycle
              PA2,          // direction1 port
              PA1);         // direction2 port
  printf("Settings from L289 setup:\n");
  show(MainMotor);

  while(!kbhit())
  {
    Forward(MainMotor);
    printf("Forward\n");
    if(kbhit())
      break;
    wait(1.25);

    Stop(MainMotor);
    printf("Stop\n");
    if(kbhit())
      break;
    wait(1);

    Reverse(MainMotor);
    printf("Reverse\n");
    if(kbhit())
      break;
    wait(.75);

    BrakeL298(MainMotor);
    printf("Brake\n");
    if(kbhit())
      break;
    wait(1);
  }

  Stop(MainMotor);

  // don't forget to free memory!
  FreeOutputControl();

  // be sure to restore the timer!
  restore_old_timer();
}

// end experi7e.c

Click here to download constant.h
Click here to download constant.h
Click here to download extern.h
Click here to download experi7e.c

Link it with the timer7a and digi7a modules to form experi7e.exe.

None were tested, but there are chips that use a pwm line and a direction line. This should work:

// set up a channel with a pwm line and a direction line -- no brake
// using duty cycle
int PwmAndDirectionDuty(int arraynumber, int StartDirection,
                        int PwmPortNumber,
                        int DirectionPortNumber,
                        double ForwardDutycycle,
                        double ReverseDutycycle)
{
  double ForwardOnTime,ForwardOffTime;
  double ReverseOnTime,ReverseOffTime;

  if(ForwardDutycycle > 1.0)
    ForwardDutycycle/=100.0;

  if(ForwardDutycycle > 1.0)
    ForwardDutycycle = 1.0;

  if(ForwardDutycycle <= 0.5)
  {
    ForwardOnTime = minpulse;
    ForwardOffTime = ((1.0 - ForwardDutycycle)/ForwardDutycycle) * ForwardOnTime;
  }

  else
  {
    ForwardDutycycle = 1.0 - ForwardDutycycle;
    ForwardOffTime = minpulse;
    ForwardOnTime = ((1.0 - ForwardDutycycle)/ForwardDutycycle) * ForwardOffTime;
  }

  if(ReverseDutycycle > 1.0)
    ReverseDutycycle/=100.0;

  if(ReverseDutycycle > 1.0)
    ReverseDutycycle = 1.0;

  if(ReverseDutycycle <= 0.5)
  {
    ReverseOnTime = minpulse;
    ReverseOffTime = ((1.0 - ReverseDutycycle)/ReverseDutycycle) * ReverseOnTime;
  }

  else
  {
    ReverseDutycycle = 1.0 - ReverseDutycycle;
    ReverseOffTime = minpulse;
    ReverseOnTime = ((1.0 - ReverseDutycycle)/ReverseDutycycle) * ReverseOffTime;
  }

  return pwm(arraynumber, 2, // type 2 = pwm, direction, no brake
    PwmPortNumber, PwmPortNumber, // forward and reverse are same
    DirectionPortNumber, 0, // no brake
    ForwardOnTime, ForwardOffTime,
    ReverseOnTime, ReverseOffTime,
    StartDirection);
}

Test it with this:

// experi7d.c

#include <dos.h>
#include <stdio.h>
#include <bios.h>

// defines OC structure
#include "outcont.h"

// include header with constants
#include "constant.h"

// include header with external prototypes
#include "extern.h"

enum
{
  MainMotor,
  LED1,
  LED2,
  LASTSLOT
};

void main(void)
{
  int x;
  double dc,oldfreq,newfreq;

  oldfreq = 1193180.0/65536.0;

  get_port(); // get the port number and establish register locations

  // make everthing an output
  set_up_ppi(Aout_CUout_Bout_CLout);

  set_up_new_timer(2000.0);

  SetLast(LASTSLOT);

  newfreq = get_frequency();

  printf("old frequency = %f new frequency = %f Hz\n"
  ,oldfreq,newfreq);

  // set up a channel with a pwm line and a direction line -- no brake
  // using duty cycle
  PwmAndDirectionDuty(MainMotor, 0, // set up, but don't start main motor
                      PA0, PA2, // pwm and direction port
                      75.0, 50.0); // forward 75%, reverse 50%
  printf("MainMotor on/off settings pwm = PA0, dir = PA2:\n");
  show(MainMotor);

  Blink(LED1, PA1, .3, .2);
  Blink(LED2, PA4, .15, .1);
  SetLast(LASTSLOT);

  while(!kbhit())
  {
    Forward(MainMotor);
    if(kbhit())
      break;
    wait(1);

    Stop(MainMotor);
    if(kbhit())
      break;
    wait(1);

    Reverse(MainMotor);
    if(kbhit())
      break;
    wait(1);

    Stop(MainMotor);
    if(kbhit())
      break;
    wait(1);
  }

  Stop(MainMotor);

  // don't forget to free memory!
  FreeOutputControl();

  // be sure to restore the timer!
  restore_old_timer();
}

// end experi7d.c

Click here to download constant.h
Click here to download constant.h
Click here to download extern.h
Click here to download experi7d.c

Previous: Experiment 6 - More Precise Control Of Motors
Next: Experiment 8 - Digital To Analog Conversion
Copyright © 2001, Joe D. Reeder. All Rights Reserved.