Lesson 32 Notes

Readings

Assignment

Lesson Outline

Admin

Lab 6 Introduction

Next lesson, you're going to start work on the robot. Eventually, you'll use the MSP430 to power it to navigate a maze. But first, we have to learn how to use the onboard motors to move our robot forward, back, left and right - that's the crux of Lab 6.

How do you move a DC motor? You apply an analog voltage!

[Demo motor with power supply]

But how can we do this with the MSP430? GPIO only gives us logical 1 or 0. We don't have an onboard Digital-to-Analog converter. Say we want to generate an analog output using our MSP430 - how would we do it? The technique we'll learn today and use in Lab 6 is called Pulse Width Modulation.

Pulse Width Modulation

Imagine a light bulb that is on 50% of the time and off 50% of the time. [Draw waveform with 50% duty cycle on the board].

[Turn classroom lights on and off]

If the frequency of the change is very slow, it would be very noticeable that we're just switching between two digital states.

But what if the frequency was very fast? It would just appear as a light with 50% brightness! It would appear like an analog voltage of 50% of the high voltage, providing 50% power.

To change the brightness of the bulb, we can change the percentage of the period the signal is high! Assuming our logic high is 5V. If our signal was high 10% of the time, it would look like a 0.5V analog signal. 50% of the time would look like a 2.5V analog signal. 100% of the time would look like the full 5V analog signal.

We call the percentage of a period the signal is high the signal's duty cycle.

Here's a demo of me switching between different duty cycles to power an LED:

[LED with PWM demo]

See how the brightness of the LED varies?

Let's see how it looks on the multimeter!

[Hook up to DMM]

See how the voltage changes, but reaches a steady analog state each time?

Let's see how it looks on a scope.

[Hook up to O-Scope]

See how it's actually just a recurring signal with a specified percentage high? See how the percentage of time it's high controls the voltage? Sweet!

Terminology

Capture / Compare

Ok, back to the technical stuff.

In addition to what we talked about last time, Timer_A comes equipped with three Capture/Compare blocks.

Back to our trusty block diagram. Today we'll be looking at the bottom:

Timer A Block Diagram

Capture: Monitor a pin for a specified signal (rising edge, falling edge, either edge) and record when it occurs.

Compare: Generate a specified signal with precise timing.

Here's how those registers are configured:

TACCTL Register Description

Capture

Capture mode is selected when the CAP bit in TACCTL is set to 1. It is used to record time events. It can be used for:

Each TACCRx has two possible capture pins - CCIxA and CCIxB. The one being monitored is selectable by software. In the device-specific guide, you can find more information on which timer signals these inputs correspond with in the pin functions, terminal functions, and the timer signal connections tables.

If a capture occurs:

- The TAR value is copied into the TACCRx register
- The interrupt flag CCIFG is set

You could measure the time between a rising edge on two different signals, for example, or you could measure how long a particular signal is high. If you are measuring the time between two events, it is recommended that you use Timer_A in the continuous mode. (What capture mode is this?) Your TAR could overflow once or even multiple times between the two events; you will know an overflow happened if the COV bit was set in the TACCTLx register. If you are in up mode, dealing with the differences in TARs could be a little more difficult. Look at the state diagram below to figure out how to deal with the COV bit.

Capture Cycle

You can use your Timer_A interrupt to help you in your quest for an input capture, as long as you enable the capture/compare interrupt. Note another bit in the TACCTLx register: CCI. TI says this bit is the "capture/compare input. The selected input signal can be read by this bit." In other words, you can see the value of the signal you are watching. Why does that matter? If you are measuring the time a signal is high using CM_3, you will need to differentiate between the TAR at the start of the rising edge and the TAR at the falling edge. Checking the CCI bit will tell you which edge you just left, which might be important inside an interrupt. However, the CCI bit can also get into a race condition; for this reason it is important that you synchronoize your capture with the timer clock.

Compare

Compare mode is selected when the CAP bit in TACCTL is set to 0 (it's 0 by default).

Remember the different Timer_A counting modes from last lesson?

Timer Modes

Remember Up Mode?

Up Mode

It counts from 0 up to the value in TACCR0! We can set the value in TACCR0 just by writing to it.

TimerA0 also comes equipped with two more Capture / Compare registers - TA0CCR1 and TA0CCR2. We can set their values by writing to them as well. These give us interesting capabilities. While TimerA0 counts upward, these registers can perform actions when the values in them are passed. Here's what they can do:

Output Modes

That might be confusing. Check out that example:

Output Modes Example - Up Mode

Example Code

This is the code I used to light the LED at four different levels of brightness:

#include <msp430.h>

#define DELAY_CYCLES 500000

void main(void)
{
        WDTCTL = WDTPW|WDTHOLD;                 // stop the watchdog timer

        // I/O setup
        P1DIR |= BIT0;                //use LED to indicate duty cycle has toggled
        P1REN |= BIT3;                // Button S2 used to change duty cycle of PWM
        P1OUT |= BIT3;

        // TA1CCR1 on P2.1
        P2DIR |= BIT1;
        P2SEL |= BIT1;
        P2OUT &= ~BIT1;

        TA1CTL |= TASSEL_2|MC_1|ID_0; // configure Timer 1 (SMCLK, UP mode)

        // PWM setup
        TA1CCR0 = 1000;               // set signal period to 1000 clock cycles (~1 millisecond)
        TA1CCTL0 |= CCIE;            // enable CC interrupts

        TA1CCR1 = 250;               // set duty cycle to 250/1000 (25%)
        TA1CCTL1 |= OUTMOD_7|CCIE;  // set TACCTL1 to Reset / Set mode//enable CC interrupts

        //clear capture compare interrupt flags
        TA1CCTL0 &= ~CCIFG;
        TA1CCTL1 &= ~CCIFG;

        _enable_interrupt();

        while (1) {

            while (P1IN & BIT3);     //every time the button is pushed, toggle the duty cycle
            __delay_cycles(DELAY_CYCLES);
            TA1CTL &= ~MC_1;
            TA1CCR1 = 1000;            // set duty cycle to 1000/1000 (100%)
            TA1CTL |= MC_1;

            while (P1IN & BIT3);
            __delay_cycles(DELAY_CYCLES);
            TA1CTL &= ~MC_1;
            TA1CCR1 = 750;            // set duty cycle to 750/1000 (75%)
            TA1CTL |= MC_1;

            while (P1IN & BIT3);
            __delay_cycles(DELAY_CYCLES);
            TA1CTL &= ~MC_1;
            TA1CCR1 = 500;            // set duty cycle to 500/1000 (50%)
            TA1CTL |= MC_1;

            while (P1IN & BIT3);
            __delay_cycles(DELAY_CYCLES);
            TA1CTL &= ~MC_1;
            TA1CCR1 = 250;            // set duty cycle to 250/1000 (25%)
            TA1CTL |= MC_1;

            while (P1IN & BIT3);
            __delay_cycles(DELAY_CYCLES);
            TA1CTL &= ~MC_1;
            TA1CCR1 = 100;            // set duty cycle to 100/1000 (10%)
            TA1CTL |= MC_1;

            while (P1IN & BIT3);
            __delay_cycles(DELAY_CYCLES);
            TA1CTL &= ~MC_1;
            TA1CCR1 = 20;            // set duty cycle to 20/1000 (2%)
            TA1CTL |= MC_1;

        }
}


#pragma vector = TIMER1_A0_VECTOR            // TA1CCR0 CCIFG vector
__interrupt void TA1captureCompare0_ISR (void) {
    P1OUT |= BIT0;                        //Turn on LED
    TA1CCTL1 &= ~CCIFG;                  //clear capture compare interrupt flag
}

#pragma vector = TIMER1_A1_VECTOR            // TA1 CCR2 and CCR1 CCIFG, TAIFG vector
__interrupt void TA1captureCompare1_ISR (void) {
    P1OUT &= ~BIT0;                        //Turn off LED
    TA1CCTL1 &= ~CCIFG;                   //clear capture compare interrupt flag

    /* You might have other interrupt sources to deal with as well
       TAIV tells you which specific interrupt flags are set

       if(TA1IV == TA1IV_TAIFG)
       {
         do stuff for TA overflow
         TA1CTL &= ~TAIFG;
       }
    */
}

Below is the code I used to show the PWM signals on the oscilloscope (notice no ISRs needed). Modify the code so that you can output TWO PWM signals that are opposites. Remember, one timer can generate up to THREE PWM signals!

#include <msp430.h>

#define DELAY_CYCLES 500000

void main(void)
{
    WDTCTL = WDTPW|WDTHOLD;                 // stop the watchdog timer

    BCSCTL1 = CALBC1_8MHZ;
    DCOCTL = CALDCO_8MHZ;

    P2DIR |= BIT1;                // TA1CCR1 on P2.1
    P2SEL |= BIT1;
    P2OUT &= ~BIT1;

    TA1CTL |= TASSEL_2|MC_1|ID_0;           // configure for SMCLK

    //use LED (P1.0) to indicate duty cycle has toggled and button (P1.3) to toggle
    P1DIR = BIT0;
    P1REN = BIT3;
    P1OUT = BIT3;

    TA1CCR0 = 1000;                // set signal period to 1000 clock cycles (~1 millisecond)
    TA1CCR1 = 250;                // set duty cycle to 250/1000 (75% for set/reset)
    TA1CCTL1 |= OUTMOD_3;        // set TACCTL1 to Set / Reset mode


    while (1) {

        while (P1IN & BIT3);    //every time the button is pushed, toggle the duty cycle
        __delay_cycles(DELAY_CYCLES);
        TA1CTL &= ~MC_1;
        TA1CCR1 = 500;            // set duty cycle to 500/1000 (50%)
        TA1CTL |= MC_1;
        P1OUT ^= BIT0;

        while (P1IN & BIT3);
        __delay_cycles(DELAY_CYCLES);
        TA1CTL &= ~MC_1;
        TA1CCR1 = 750;            // set duty cycle to 750/1000 (25%)
        TA1CTL |= MC_1;
        P1OUT ^= BIT0;

        while (P1IN & BIT3);
        __delay_cycles(DELAY_CYCLES);
        TA1CTL &= ~MC_1;
        TA1CCR1 = 1000;            // set duty cycle to 1000/1000 (0%)
        TA1CTL |= MC_1;
        P1OUT ^= BIT0;

        while (P1IN & BIT3);
        __delay_cycles(DELAY_CYCLES);
        TA1CTL &= ~MC_1;
        TA1CCR1 = 0;            // set duty cycle to 0/1000 (100%)
        TA1CTL |= MC_1;
        P1OUT ^= BIT0;

        while (P1IN & BIT3);
        __delay_cycles(DELAY_CYCLES);
        TA1CTL &= ~MC_1;
        TA1CCR1 = 100;            // set duty cycle to 100/1000 (90%)
        TA1CTL |= MC_1;
        P1OUT ^= BIT0;

        while (P1IN & BIT3);
        __delay_cycles(DELAY_CYCLES);
        TA1CTL &= ~MC_1;
        TA1CCR1 = 250;            // set duty cycle to 250/1000 (75% for set/reset)
        TA1CTL |= MC_1;
        P1OUT ^= BIT0;
    }
}

Lab 6 Tips

Motor Driver Chip

You cannot hook your MSP430 directly up to the motors - it can't supply enough current! We need to use a motor driver chip instead. It can only supply 1A per circuit! Do not exceed that! Check out the datasheet for wiring details.

Motor Stall Current

This is the max current draw your motor might have - usually happens when it runs up against the wall or something. This better not exceed the 1A your motor driver chip can supply or you'll burn it!

[Show technique to measure stall current]

On my robot, the stall current does not go below one amp until my motor is being driven at 8V or less - roughly 60% duty cycle. Exceed this at your own risk!

MSP430 In-Circuit

Supplying Power

We have 3.3V regulators! Use them! If you try to give 5V to your MSP430, you will fry it! Check out the datasheet for wiring details.

Programming

See the tutorial on the website! You can just jump the VCC / TEST / RESET signal over to the chip on the breadboard.

Chip Reset Due to Current Fluctuation

If the motors draw a large amount of current (due to stall), there is a good chance it will interfere with the current provided to your MSP430. To combat this, you can put a large capacitor across the 5V rail (between power and ground). This will supplement the lost current and prevent your chip from being reset.