Motor Control

     Overview:

      In this exercise, you will learn how to generate Pulse Width Modulation (PWM) signals using the PIC microcontroller and to use PWM to digitally control the power to a peripheral device, like an LED or a motor.   You will be using a Solarobotics motor, driven by a TI SN754410 Quad Half Bridge chip.
      Exercises:
      1. Wire up the TI motor driver chip to a motor and control it with 2 pushbuttons.
      2. Write a C program for the PIC to send a variable PWM signal to an LED.
      3. Generate a variable PWM signal using the PIC's built-in CCP/PWM hardware.
      4. Add direction-control and reverse-detection to the PWM program and use it to control the solarobotics motor.

     Materials:

      1 proto board with PIC 16F877 microcontroller and 20Mhz crystal
      1 TI SN754410 Quad Half Bridge
      1 potentiometer
      2 momentary pushbutton switches
      2 LEDs
      4 1K resistors
      4 alligator bridge clips
      1 multimeter
      1 power supply
      1 computer
      1 EPIC programmer
      1 power strip
      Wire and strippers

     1. Wire it up:

      In an H-bridge circuit, the motor is placed at the center of an arrangement of transistors laid out in the shape of an "H". This allows bidirectional control of the motor by turning on transistors at opposite corners of the H. One pair switches forward current, the other reverse.
A generic H-bridge.
      The SN754410 chip is a little different. Instead of a whole H-bridge, it contains 4 separate outputs, each having the schematic at right. Each output is built from 2 drive transistors and constitues half of an H-bridge. Since the chip contains 4 of them, it can be wired up for to control 2 DC motors or 1 bipolar stepper motor. The second pair of outputs can also be wired in parallel with the first for higher current capacity. We'll only be using 2 outputs, half of the chip, in this exercise.
One output on the SN754410.

Logic diagram of the SN754410. Each pair of outputs has an Enable input.
      Note the 2 diodes in the output circuit above. These are a vital component in an H-bridge or any motor-control application. These diodes are wired in parallel with the drive transistors to ensure that any switching transients generated by the motor coils or voltage generated by the spinning motor don't damage the transistors.
      The 4 outputs are grouped into pairs, each with an Enable (En) control pin. When +5V is applied to pin 1,2En, outputs 1 and 2 are activated. When 1,2En receives 0V, outputs 1 and 2 are "tristated," switched off just as though they were disconnected by a physical switch.


      Wire up the TI driver chip as illustrated. The pushbuttons deliver +5V to the chip's inputs when depressed. When they're released, 1K-Ohm "pulldown" resistors ensure that the inputs get 0V and aren't left floating. As long as 1,2En receives +5V, outputs 1 and 2 are always on. In this configuration, pressing one button or the other will drive the motor either forward or backward. But if the buttons have the same state, no current will flow because both sides of the motor will receive the same voltage.
Wiring diagram for manual control of the motor.

     2. Roll your own PWM:

      PWM, Pulse Width Modulation, involves taking a square wave and, without changing the frequency, changing how long in each cycle the wave spends high and low. The ratio of t-high/t-low is the signal's duty cycle. The higher the duty cycle, the higher the average voltage on the signal line and the higher the power the signal delivers.
Low power (top) and high power (bottom) PWM waveforms.

      The easiest way to program a PWM signal is to pick some period, T1, for our square wave, take an ouput pin high, delay for a time T2 (where T2<T1), then take the output pin low and delay for a time T1-T2. By varying T2, we can change the duty cycle without changing the wave's frequency.
      Since the read_ADC() function returns an 8-bit integer, if we use T1=255us (microseconds), we can use the analog value returned by read_ADC for our T2. Since the value returned by read_ADC() will vary between 0 and 255, T1 will stay constant: T1=read_ADC() + (255-read_ADC()). This will let us control the duty cycle of our PWM signal with an analog signal. The following program changes an LED's brightness based on the position of a potentiometer.
      The LED's anode (positive termnial, longer lead) should be attached to Pin A1. The LED's cathode should be grounded through a 1k-Ohm current-limiting resistor. The potentiometer's center lead should attach to Pin A0 and the other 2 leads should go to +5V and ground.

#include <16F877.h>
#fuses HS,NOPROTECT,NOWDT //Set chip configuration.
#use delay(clock=20000000) //Tell the compiler we're using a 20Mhz crystal.
#bit LEDPin = 5.1 //Place the 1-bit variable LED at byte 5, bit 1 of memory.
#use rs232(baud=115200, xmit=PIN_A2, rcv=pin_A4,invert) //setup serial.
void main(){
    int T2=0; //Local variables must be declared up front. Remember this is C, NOT C++.
    set_tris_a(0b00010001); //make only pins A0 and A4 inputs. 1=input, 0=output.

    //ADCs MUST be set up first. Otherwise, nothing will WORK.
    //Which pins should be analog? If ADCs unused, call with NO_ANALOGS.
    setup_ADC_ports(RA0_ANALOG); //Make PIN_A0 the only analog input.
    //Set the clock source for the ADC. Call with ADC_OFF if ADCs are unused.
    setup_ADC(ADC_CLOCK_INTERNAL); //Use the PIC's internal RC oscillator.
    set_ADC_channel(0); //Which pin to read from? In this case, AN0, aka PIN_A0.

    while(true){ //Start infinite loop.
       T2=read_ADC(); //store analog value in T2. This will set the duty cycle.
       LEDPin = 1; //Take PIN_A1 high, turning the LED on.
       delay_us(T2); //LED on-time based on ADC value.
       LEDPin = 0; //Take PIN_A1 low after T2 microcesonds, turning off LED.
       delay_us(255-T2); //LED off-time based on 255-ADC value, keeping T1=255.
       // NOTE: delay_us accepts variable int8 arguments but only const longs.
       printf("The input value is: %u\r\n", T2); //Send serial data to the PC.
    }
}


     2. Light weight PWM:

      We can also generate a PWM waveform using a piece of peripheral hardware on the PIC chip called a Capture/Compare/PWM, or CCP unit. The PIC's CCP units (the 877 has 2, located on pins C1 and C2) make use of the chip's internal timers to provide several useful functions. In the case of PWM, the CCPs make use of the 8-bit timer, Timer 2. The configuration of Timer 2 determines the frequency of the PWM signal.
      Once the CCPs are set up with setup_CCPx(CCP_PWM) and Timer 2 is configured, you can call set_pwmX_duty(int duty_cycle) ("X" can be either a 1 or a 2) at any time to set the duty cycle generated by the CCP unit. The CCP unit constantly compares duty_cycle against the current value of Timer 2. If duty_cycle >= Timer 2, the CCP sets its pin high. Otherwise, it takes its output pin low.
      Using the built-in CCP hardware, the PIC can generate 2 PWM signals at once without interrupting program flow except to change the duty cycle. Note that, just to be confusing, CCP1 is located on pin C2 and CCP2 is on pin C1. This makes it easy to swap your PWM lines, so be careful.
      In the program below, CCP2 was used to generate the PWM signal. Attach a second current-limited LED to Pin C1. Leave the first LED on Pin A1. This pin will receive a 20Hz "heartbeat" signal from the program's main loop. Inserting heartbeat signals into your loops is a good debugging tool. If you use a different pin for each loop and your program is hanging up, you can tell which loop it's stuck in by simply probing the heartbeat pins for activity. The heartbeat on A1 in this program has been slowed down to 20hz to make it visible.

     2. PWM and Motor Control:

      If we want the motor to be able to turn both directions (bidirectional control), we'll need to add 3 new features to the program from Exercise 2. First, we need to change the way that read_ADC() maps into T2. The motor should stop when the potentiometer is centered and go full speed when it is at either extreme. So T2 will have to vary from 255 at read_ADC()=0 to T2=0 at read_ADC()=127, and back up to T2=255 at read_ADC()=255.
      Second, we need to create 2 direction control outputs to tell the SN754410 which way to drive the motor. The program can decide whether to turn the motor forward or backward using: if(127<read_ADC()).
      The third feature we need to add is reversal-detection. When the motor changes direction, say from forward to reverse, the H-bridge turns off the 2 forward transistors and turns on the 2 reverse transistors. The transistors don't turn off instantly though, so if the reversal is done too quickly it's possible to turn on the pair of reverse transistors while the forward pair is still partially conducting. This will let current bypass the motor and run straight through the H-bridge, possibly burning it out. In order to avoid this, it's important to insert a "dead time" of at least 1 microsecond, where the motor outputs are simply turned off, before reversing directions.