This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

MSP430G2553: enabling Timers in Button Interrupt when LPM4 enabled

Part Number: MSP430G2553

Tool/software:

Hi everybody,

I got a problem with activating a timer in a interrupt, which occurs while the microcontroller is in LPM4.

In my main function, I enable the low power mode to save energy until the user is pressing a button. After that I get into the ISR of the button and want to activate a timer for a standby function. Unfortunately I cannot activate the timer because the microcontroller is still in low-power mode.

I tried the function __bic_SR_register in the ISR to instantly deactivate the low power mode, but this did not work as the program is jumping again in LPM4 after finishing with the ISR.

I also tried the __bic_SR_register_on_exit function. This does properly deactivate the LPM but just when the ISR is finished and I want to activate the timer in the ISR, so the timer does not get activated this way.

I did put my code below and the section where I want to activate and deactivate the LPM4 are commented with "deepsleep"

Does anybody of you can help me out with my problem?

Thanks a lot.

Yours sincerely,

Timo Kurz

 

#include <msp430.h>
#include <stdint.h>
#include "asciizeichen.h"
#include <stdio.h>
#include <string.h>

// Definitions for Nokia 5110 display
#define LCD_RST BIT2
#define LCD_DC BIT4
#define LCD_DIN BIT5
#define LCD_CLK BIT6
#define LCD_CE BIT1
#define LCD_LED BIT7

#define SW BIT3                             // ToDo: change to correct activation bit - VORSICHT!!!! AKTUELL NOCH PORT1 STATT PORT2
#define OUT BIT0                            // ToDo: change to correct output bit
#define SENSOR_PIN (BIT1 | BIT2)

#define STANDBY_TIMEOUT 32768                   // Timer for standby modus
#define DEBOUNCE_DELAY 5000                 // debounce Timer

#define CHARGING 1000000                    // Value for recognizing if device is plugged in
#define MAX_TEMP 120                         // Value for safety overheat shutdown
#define MIN_VOLTAGE 0                        // Value for safety shutdown if battery is empty


volatile uint16_t adc_result[3] = {0};      // Variable to store ADC results
volatile uint16_t current = 0;              // Variable for actual charging current
volatile uint16_t voltage = 0;              // Variable for actual battery voltage
volatile uint16_t temperature = 0;          // Variable for actual temperature
static int started = 0;


void delay(unsigned int t);
void LCD_write_byte(unsigned char data, unsigned char mode);
void LCD_init();
void LCD_clear();
void LCD_string(char *characters);
void LCD_char(char character);
void ausgabe_string(char *str1, double kommazahl);


void main(void) {

    double zahl = 0.0;
    int zaehler = 0, i = 0;

    WDTCTL = WDTPW | WDTHOLD;               // Stop watchdog timer

    P1DIR |= OUT;                           // Set Output pin -> Output
    P1DIR &= ~SW;                           // Set SW pin -> Input
    P1REN |= SW;                            // Enable Resistor for SW pin
    P1OUT |= SW;                            // Select Pull Up for SW pin
    P1OUT &= ~OUT;                          // Disable Output at start

    P1IES |= SW;                            // Select Interrupt on Falling Edge
    P1IE |= SW;                             // Enable Interrupt on SW pin

    // configure ADC
    ADC10CTL1 = INCH_2 + CONSEQ_3;          // Channel select A2 + sequence of channels
    ADC10CTL0 = SREF_0 + ADC10SHT_2 + ADC10ON + ADC10IE;    // REF = VCC & VSS + ADC SampleAndHoldTime 16 x ADC10CLKs  + + //
    ADC10AE0 |= SENSOR_PIN;                 // These bits enable the PIN 1.1 & P1.2 for analog input
    P1SEL |=  SENSOR_PIN;                   // ADC input select mode P1.1 & P1.2

    // Timer0 to interrupt after 10 seconds
    TA0CCR0 = STANDBY_TIMEOUT / 8;              // 32.768 kHz crystal oscillator, divided by 8
    TA0CTL = TASSEL_1 + MC_0 + ID_3;        // ACLK (32.768 kHz), Stop mode, Divider 8
    TA0CCTL0 |= CCIE;

    // Timer1 for debouncing
    TA1CCTL0 |= CCIE;                       // Enable Timer1 interrupt
    TA1CCR0 = DEBOUNCE_DELAY;               // Set debounce delay
    TA1CTL = TASSEL_2 + MC_0 + TACLR;       // SMCLK, Stop mode, clear timer

    __bis_SR_register(GIE);                 // Enable global interrupts

    while(1)
    {
        if(started)
        {
            LCD_init();
            P1OUT |= OUT;                   // Toggle Output Pin 1.0
            P1OUT |= LCD_LED;               // Backlight on
            LCD_clear();

            while (started) {
                char str1[20] = "Zahl: ";
                ausgabe_string(str1, zahl);
                LCD_string("     ");
                LCD_string("Funktioniert  ");

                for (i = 0; i <= zaehler; i++) {
                    LCD_char(128);
                }

                zaehler += 1;
                if (zaehler >= 5) {
                    zaehler = 0;
                }

                zahl += 0.1;
                if (zahl >= 10.0) {
                    zahl = 0.0;
                }

                start_ADC_conversion();     // Start the ADC conversion
                __delay_cycles(1000000);    // one sec delay
            }
        }
        // ToDo: value if device is currently charging
        else if(current > CHARGING)         // State if device Timeout and device is currently charging
        {
            P1OUT &= ~LCD_LED;              // Backlight off
            LCD_clear();                    
        }
        else
        {
            P1OUT &= ~LCD_LED;              // Backlight off
            LCD_clear();
            P1OUT &= ~OUT;                  // Turn off Output
            // __bis_SR_register(LPM4_bits + GIE); // ToDo: deepsleep
        }
    }
}

void start_ADC_conversion() {
    ADC10CTL1 = INCH_10 + ADC10DIV_3;       // Select channel A10 for temperature, divide by 4
    ADC10CTL0 = SREF_1 + ADC10SHT_2 + ADC10ON + ADC10IE + REFON + REF2_5V; // Internal reference, 2.5V, ADC on, enable interrupt
    __delay_cycles(1000);                   // Wait for reference to settle
    ADC10CTL0 |= ADC10SC + ENC;             // Start conversion
    while (ADC10CTL1 & ADC10BUSY);          // Wait for conversion to finish
    adc_result[0] = ADC10MEM;               
    temperature = ((adc_result[0] - 673) * 423) / 1024; // Convert ADC value to temperature in Celsius
    if(temperature > MAX_TEMP)               // overheat protection
    {
        P1OUT &= ~OUT;                      // Turn off Output
        // ToDo: Response, for overheating
    }
    ADC10CTL0 &= ~ENC;                      // Disable conversion
    ADC10CTL0 &= ~REFON;                    // Turn off reference to save power

    ADC10CTL1 = INCH_2;                     // select channel A2 as input
    ADC10CTL0 |= ADC10SC + ENC;
    while (ADC10CTL1 & ADC10BUSY);
    adc_result[1] = ADC10MEM;               
    voltage = adc_result[1];                // ToDo: conversion to actual value
    // ToDo: safety shutdown if battery is empty
    // if(voltage < MIN_VOLTAGE)
    // {
    //     P1OUT &= ~OUT;                  // Turn off Output
    //     // ToDo: Response, for empty battery
    // }
    ADC10CTL0 &= ~ENC;

    ADC10CTL1 = INCH_1;                     // select channel A1 for ADC + Conversion sequence mode select ==  Sequence-of-channels    + ACLK - clock (no input div)
    ADC10CTL0 |= ADC10SC + ENC;             // ADC10SC == 0 >> no sample start conversation + ENC == EnableConversation
    while (ADC10CTL1 & ADC10BUSY);
    adc_result[2] = ADC10MEM;               
    current = adc_result[2];                // ToDo: conversion to actual current
    ADC10CTL0 &= ~ENC;                      // disable conversation
}

#pragma vector=ADC10_VECTOR
__interrupt void ADC10_ISR(void) {
    ADC10CTL0 &= ~ADC10IFG;                 // Clear interrupt flag
}

#pragma vector=PORT1_VECTOR
__interrupt void Port_1(void) {
    P1IE &= ~SW;                            // Disable button interrupt
    P1IFG &= ~SW;                           // Clear SW interrupt flag
    started = 1;
    // __bic_SR_register(LPM4_bits);        // ToDo: Wakeup from deepsleep
    TA0CTL |= MC_1;                         // Start Timer0 in up mode
    TA1CTL |= TASSEL_2 + MC_1;              // Start Timer1 for debouncing
}

#pragma vector=TIMER0_A0_VECTOR
__interrupt void Timer_A0_ISR(void) {
    TA0CTL &= ~MC_1;                        // Stop Timer0
    started = 0;
    TA0CCTL0 &= ~CCIFG;
}

#pragma vector=TIMER1_A0_VECTOR
__interrupt void Timer_A1_ISR(void) {
    TA1CTL &= ~MC_1;                        // Stop Timer1
    P1IE |= SW;                             // Re-enable button interrupt
    TA1CTL |= TACLR;                        // Clear Timer1
    TA1CCTL0 &= ~CCIFG;                     // Clear Timer1 interrupt flag
}

  • You don't seem to understand how low power modes work.

    You set a low power mode by fiddling with bits in the status register. When an interrupt occurs, the system exits the low power mode (changing SR) while saving the current values in SR to the stack.

    When the return from interrupt instruction is executed, that restores the status register from the value on the stack. Returning to whatever the mode was before the interrupt. Unless you have used an on_exit macro to change the data on the stack.

    Execution then begins where it left off just after where you set the low power mode. So you can start a timer in an ISR and it will keep running when the ISR exits if its clock is running.

  • Hi David,

    I think you are absolutely right. I do not understand, how to code this correctly.

    As I explained above, I started the timer and used the on_exit macro to escape the LPM after finishing the ISR. With this setting the timer does not work and the code never gets into the timer interrupt. Without enabling a LPM, the timer works with the same code.

  • 1) SMCLK (TASSEL=2) doesn't run below LPM0. No timers run in LPM4.

    2) A wakeup (_bic_SR_Register_on_exit(LPM_mumble)) is ineffective if main() isn't in LPM (it is not remembered). The button may be pushed at any moment, including while main() is Active.

    I think it will be difficult to untangle this. I suggest you sleep at LPM3 and run your debounce timer using ACLK (which does run in LPM3).

    Unsolicited: When the switch finished bouncing, it's almost certain that the P1IFG:SW bit is set. I recommend you clear it before re-enabling P1IE:SW.

**Attention** This is a public forum