Msp430 - Basics

| Comments

It’s interesting that msp430 has low power mode (LPM) which enables the MCU to get into sleep mode and stay operating. There are 6 profiles for this: 5 low-power modes, and one active mode.

These modes related to which components get disabled, thus lowering power consumption. The components are: CPU, MCLK (Master Clock), SMCLK (Sub-System Master Clock), ACLK (Auxiliary Clock), DCO (Digitally Controlled Oscillator), and internal crystal oscillator. By default, after power-up, DCO is used as the source oscillator for MCLK and SMCLK, while ACLK is sourced from a low-frequency (~32kHz) additional watch-crystal (LFXT1), All the clocks is software selectable, and can use either DCO, additional crystal (LFXT1), or internal low-power oscillator (VLO), but ACLK is the only clock that can not be configured to use DCO as its source oscillator.

Now back to the operating modes, here is the list of profile for msp430

  • Active mode is the normal operation profile, all components is enabled and running.
  • LPM 0 has CPU and MCLK disabled
  • LPM 1 has CPU, MCLK, and DCO disabled. DCO only disabled if it’s not used in active mode.
  • LPM 2 has CPU, MCLK, and SMCLK disabled.
  • LPM 3 has CPU, MCLK, SMCLK, and DCO disabled.
  • LPM 4 has CPU, MCLK, SMCLK, ACLK, DCO, and internal crytal oscillator disabled.

So yes, LPM 4 is the deep sleep mode.

The low-power mode can be set to be interruptible. That is, by setting the interrupt-enable flag together with the LPM profile. We’ll see how to do that in a moment.

The usual first program for microcontroller is the grand LED-blinking code. And to do that naively is to XOR-ing the bit to the pin where the led is mounted, and add some delay/sleep for some cycles so it’s alternating between high and low currents.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <msp430.h> 

int main(void) {
    WDTCTL = WDTPW | WDTHOLD;   // Stop watchdog timer
    /* Set P1.0 direction to output */
    P1DIR |= BIT0;

    /* Set P1.0 output high */
    P1OUT |= BIT0;

    while (1) {
        /* Wait for 200000 cycles */
        __delay_cycles(200000);
        /* Toggle P1.0 output */
        P1OUT ^= BIT0;
    }

    return 0;
}

/* Adapted from http://simplyembedded.wordpress.com/tutorials/lesson3/
 */

Now you see the code above would require the MCU to run in active mode all the time, and for MSP430 to living up to its title, we can optimize the code so it can run with less power consumption (shame on me because I haven’t measure the actual power consumption yet). First let’s take a look at the first line inside our main block above. The default watchdog timer was set to do a h/w reset in every 32ms (or the default timer set, since it’s probably different), so we have to hold it, or more like killing it, since we wont have anything to do with the timer.

But hey, it’s a timer. Could we possibly use that as the timer for our blinking led, sure we could. The watchdog can be configured to interrupt the normal execution and move the PC (Program Counter) to the ISR (Interrupt Service Routine), and execute the code over there. This combined with the low-power mode can be used to eliminate the infinite loop above, just like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <msp430.h> 

int i;

int main(void) {
    /* Set Watchdog timer to interrupt in interval 32ms,
     * clocked from SMCLK. */
    WDTCTL = WDT_MDLY_32;
    IE1 |= WDTIE;

    /* Set P1.0 direction to output */
    P1DIR |= BIT0;

    /* Set P1.0 output high */
    P1OUT |= BIT0;

    i = 0;

    __bis_SR_register(LPM1_bits | GIE);
}


/* Watchdog Timer interrupt service routine */
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector=WDT_VECTOR
__interrupt void watchdog_timer(void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(WDT_VECTOR))) watchdog_timer (void)
#else
#error Compiler not supported!
#endif
{
    i++;
    if (i >= 25) {
        P1OUT ^= BIT0;                            /* Toggle P1.0 using exclusive-OR */
        i = 0;
    }
}

For now I wont mind with the extra flag checking over there, but in case you curious, it’s checking for the compiler version and will use the pragma and __interrupt void watchdog_timer(void) for TI or IAR C compiler, and will use a bit longer line void __attribute__ ((interrupt(WDT_VECTOR))) watchdog_timer (void) for gcc. Other than those compiler, throw error.

Now back to our topic. I said I will show you how to set an interruptible low-power mode, and that it is. With some addition, actually, we should add the Watchdog interrupt-enable flag to the register, then proceed to the LPM1 mode while standby for any interrupts registered.

We defined the ISR as watchdog_timer and since 32ms is a bit too fast, we multiply the counter by 25 so it will blinking with interval at roughly 800ms.

Below is the code for blinking fizzbuzz I’ve just made. And instead of timer we make use the SW2 button mounted on pin 1.3. Since we don’t use any clock we can do this from the deepest slumber mode of the MCU: LPM4

The code run and then goes to LPM4, we trigger interrupt by pushing the switch button, then it will count from 1 to ~100 and checking for fizzbuz at each number. The red led will on if it’s a modulo 3, green led will on if it’s a modulo 5, and both will on if it’s modulo 15.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <msp430.h>

long i;

int main(void) {
    // Disable Watchdog
    WDTCTL = WDTPW | WDTHOLD;

    // Set output direction on Pin 1.0 and 1.6
    // start from off
    P1DIR |= BIT0 | BIT6;
    P1OUT &= ~(BIT0 | BIT6);

    // Enable interrupt on Pin 1.3
    // on Hi-Lo transition
    P1IE |=  BIT3;
    P1IES |= BIT3;

    // Enable pull-up resistor on Pin 1.3
    P1OUT |= BIT3;
    P1REN |= BIT3;

    // Clear interrupt flag on Pin 1.3
    // so we can accept more interrupt
    P1IFG &= ~BIT3;

    i = 0;

    // Go into deep sleep, but listen for interrupt
    __bis_SR_register(LPM4_bits | GIE);
    return 0;
}

// Port 1 interrupt service routine
#if defined(__TI_COMPILER_VERSION__) || defined(__IAR_SYSTEMS_ICC__)
#pragma vector=PORT1_VECTOR
__interrupt void Port_1(void)
#elif defined(__GNUC__)
void __attribute__ ((interrupt(PORT1_VECTOR))) Port_1 (void)
#else
#error Compiler not supported!
#endif
{
    i++;
    if (i % 15 == 0)            // red + green
        P1OUT |= (BIT0 | BIT6);
    else if (i % 3 == 0) {      // red
        P1OUT &= ~BIT6;
        P1OUT |= BIT0;
    }
    else if (i % 5 == 0) {      // green
        P1OUT &= ~BIT0;
        P1OUT |= BIT6;
    }
    else {
        P1OUT &= ~(BIT0 | BIT6);
    }
    if (i >= 100) i = 0;
    P1IFG &= ~BIT3;
}

Anyway here’s some secret. The code above won’t work as intended, despite it’s logically correct, we will see the led being on and the switch clicking count number is not in sync. Since we’re using switch over here, we’re doomed to experience this thing called contact bounce. But I will leave the code as it is right now since if we want to fix this, we should use the timer to do some soft-debouncing, which require us to step down from LPM4 to LPM1. Or use external crystal as oscillator and feed it to ACLK, can’t be bothered to do that right now. ._.

Comments