Jump to content
retnuH

Problem with interrupts on P1 and a timer

Recommended Posts

Hi there, I'm trying to write something that tries to use power as efficiently as possible.

 

The main idea is that the circuit will be in LPM4 most of the time, until a button is pressed. Once pressed, the button will start a timer, go into LPM3, and some led's will blink periodically (for a few seconds). Once thats done, it will just go back to LPM4 and wait for the button to be pressed again.

 

For simplicity, I'm just trying to get things working in LPM0 (I think I've got the switching modes worked out ok) on a Launchpad with the -2553 chip. I'm using Energia with the msp430-gcc toolchain.

 

So far, the button press starts the timer, and the lights flash ok, but for some reason, it won't respond to a second button press. I've tweaked and fiddled a fair bit, but I'm a bit stumped. Not really sure how to proceed!

 

Any advice appreciated,

 

H

 

#include 

#define GREEN_LED_MASK  BIT6
#define RED_LED_MASK    BIT0
#define PUSH2_MASK      BIT3

unsigned char count = 0;
unsigned char times = 0;
unsigned int mode = LPM0_bits;

void setup() 
{
 P1DIR = (GREEN_LED_MASK | RED_LED_MASK);
 pinMode(PUSH2, INPUT_PULLUP);     

 digitalWrite(RED_LED, HIGH);
 digitalWrite(GREEN_LED, LOW);
 do {
   P1IFG = 0;
 } while (P1IFG != 0);
 P1IE =  PUSH2_MASK;
 P1IES = 0;
 eint();
}

void loop() 
{
 _BIS_SR(mode);
}

interrupt (TIMER0_A0_VECTOR) TA0_ISR(void)
{
 if (++count > 2*times) {
   P1OUT = GREEN_LED_MASK;    
   TACTL = TACLR;
   count == 0;
 } else {
   P1OUT ^= GREEN_LED_MASK | RED_LED_MASK;
 }
}

interrupt (PORT1_VECTOR) PORT1_ISR(void)
{
 ++times;
 do {
   P1IFG = 0;
 } while (P1IFG != 0);
 P1OUT = RED_LED_MASK;
 TACCR0 = 0x1FFF;
 TACCTL0 = CCIE;
 // Set up Timer A "Up to CCR0" mode, divide by 2^n, clock from SMCLK (2)/ACLK (1), clear timer
 TACTL = MC_1 | ID_0 | TASSEL_1 | TACLR;
}

Share this post


Link to post
Share on other sites

This was working a couple of days back... Load on a LaunchPad, poke button. Scratch that, I didn't notice the Energia part. You could still adapt, though.

 

#include 

#include 
#include 

#define LED       BIT6
#define BUTTON    BIT3

#define DEBUG
#undef DEBUG

#ifdef DEBUG
#define DEBUGLED  BIT0
#endif

void __attribute__((interrupt (PORT1_VECTOR))) port1_isr(void);
void __attribute__((interrupt (WDT_VECTOR))) wdt_isr(void);

#define PWM_PERIOD  128
static const uint16_t brightness_levels[] = { 0, 6, 23, 56, PWM_PERIOD };
#define n_brightness_levels ( sizeof( brightness_levels ) / sizeof( brightness_levels[0] ) )
static volatile uint8_t cur_brightness = 0;
static volatile uint8_t debounce_ctr = 0;

/* /3 -- WDT_ADLY_250 assumes LFXTCLK @ 32KiHz, VLO is ~12KHz */
#define POWEROFF_TIMER  (2400 / 3)    /* 10m @ 250ms */
#if 0
#undef POWEROFF_TIMER
#define POWEROFF_TIMER  (40 / 3)    /* TEST: 10 seconds */
#endif

static volatile uint32_t poweroff_timer = POWEROFF_TIMER;
static volatile bool poweroff = false;

void main(void)
{
 WDTCTL = WDT_ADLY_250;      /* 750ms if running on VLO */

 BCSCTL2 |= SELM_3 | SELS;   /* MCLK to LFXTCLK, SMCLK to LFXTCLK */
 BCSCTL3 |= LFXT1S_2;      /* LFXT1 to VLO */

 IE1 |= WDTIE;
 IFG1 &= ~WDTIFG;

 P1DIR |= LED;

 CCR0 = PWM_PERIOD;
 CCTL1 = OUTMOD_7;
 TACTL = TASSEL_1;

 P1DIR &= ~BUTTON;
 P1IE |= BUTTON;
 P1REN |= BUTTON;
 P1IFG &= ~BUTTON;
 P1OUT |= BUTTON;

#ifdef DEBUG
 P1DIR |= DEBUGLED;
 P1OUT &= ~DEBUGLED;
#endif

 __eint();

 while( 1 )
 {
   if( poweroff || cur_brightness == 0 )
   {
     TACTL |= MC_0;
     CCR1 = 0;
     P1SEL &= ~LED;
     P1OUT &= ~LED;
     __bis_status_register( debounce_ctr == 0 ? LPM4_bits : LPM3_bits );
   }
   else
   {
     TACTL |= MC_0;
     if( cur_brightness == n_brightness_levels - 1 )
     {
       P1SEL &= ~LED;
       P1OUT |= LED;
     }
     else
     {
       P1SEL |= LED;
       CCR1 = brightness_levels[ cur_brightness ];
       TACTL |= MC_1;
     }
     __bis_status_register( LPM3_bits );
   }
 }

 WDTCTL = 0xDEAD;
}

void __attribute__((interrupt (PORT1_VECTOR))) port1_isr(void)
{
 P1IE &= ~BUTTON;
 debounce_ctr = 1;

 if( !poweroff && ( ++cur_brightness >= n_brightness_levels ) )
 {
   cur_brightness = 0;
 }

 poweroff_timer = 0;
 poweroff = false;

 P1IFG &= ~BUTTON;
 __bic_status_register_on_exit( LPM4_bits );
}

void __attribute__((interrupt (WDT_VECTOR))) wdt_isr(void)
{
#ifdef DEBUG
 P1OUT ^= DEBUGLED;
#endif

 /* Decrement debounce_ctr iff > 0, else WDT timer will keep
  * it on for one more loop, causing a delay in poweroff */
 if( debounce_ctr > 0 && --debounce_ctr == 0 )
 {
   P1IFG &= ~BUTTON;
   P1IE |= BUTTON;
 }

 if( ++poweroff_timer == POWEROFF_TIMER )
 {
   poweroff = true;
 }

 IFG1 &= ~WDTIFG;
 __bic_status_register_on_exit( LPM4_bits );
}

Share this post


Link to post
Share on other sites

I got your sample to work, thanks!

 

Still can't figure out why my interrupt on P1 stops working in my code, though.

 

Are there any cases where interrupts are automatically enabled or disabled?

Share this post


Link to post
Share on other sites
Still can't figure out why my interrupt on P1 stops working in my code, though.

unsigned char count = 0;
unsigned char times = 0;

void handleButtonPress(void);

void setup() 
{
 pinMode(RED_LED,OUTPUT);
 pinMode(GREEN_LED,OUTPUT);
 pinMode(PUSH2, INPUT_PULLUP);     
 digitalWrite(RED_LED, HIGH);
 digitalWrite(GREEN_LED, LOW);
 attachInterrupt(PUSH2, handleButtonPress, RISING);
}

void loop() 
{
 LPM3;
}

void handleButtonPress(void)
{
 ++times;
 P1OUT |= digital_pin_to_bit_mask[RED_LED];
 P1OUT &= ~digital_pin_to_bit_mask[GREEN_LED];
 TACCR0=0x1FFF;
 TACCTL0 = CCIE;
 TACTL = MC_1 | ID_0 | TASSEL_1 | TACLR;
}

__attribute__((interrupt(TIMER0_A0_VECTOR)))
static void TA0_ISR(void)
{
 if (++count > 2*times) {
   P1OUT |= digital_pin_to_bit_mask[RED_LED];
   P1OUT &= ~digital_pin_to_bit_mask[GREEN_LED];
   TACTL = TACLR;
   count == 0;
 } else {
   P1OUT ^= digital_pin_to_bit_mask[RED_LED]|digital_pin_to_bit_mask[GREEN_LED];
 }
}

 

In your original code you were overwriting the P1OUT values instead if just changing the pins that needed to be changed. The pullup resistor directon for the push button is controlled by the value of BIT3 in P1OUT. Overwriting wiped out that setting. In the code above I turned on and off the bits for the leds instead of overwriting. I also got rid of signal.h and used the more naked style of defining an ISR using __attribute__. You might also notice for the handling the PORT1 interrupt I used the more Energia friendly method of attachInterrupt() with a function handler. This makes it easier to write handlers for different pins on the same port.

 

Are there any cases where interrupts are automatically enabled or disabled?

 

Yes interrupts are automatically enabled by the Energia framework code. The watchdog timer is automatically enabled to increment the millis() value. If you don't care about that you can disable it with a disableWatchDog() call. If you use Serial on a chip that doesn't have a real UART, it will steal the TIMER1 interrupt and will not be available for use. There are other cases. You might take a look at the core code and see what is going on down inside the framework for a deeper understanding.

 

-rick

Share this post


Link to post
Share on other sites

Thanks a million, the clearing of the bit in POUT explained everything.

 

Thanks also for the Energia specific advice, I appreciate it!

 

The thanks buttons seemed to have gone with the recent forum change, so I "liked" the post until I figure out how to do otherwise.

 

Thanks again,

 

H

Share this post


Link to post
Share on other sites

Thanks a million, the clearing of the bit in POUT explained everything.

 

Thanks also for the Energia specific advice, I appreciate it!

 

The thanks buttons seemed to have gone with the recent forum change, so I "liked" the post until I figure out how to do otherwise.

 

Thanks again,

 

H

 

Yes, "Thanks" is now "Like". I should change that. Welcome to 43oh.

How did you find this url.. It's not public yet.

Share this post


Link to post
Share on other sites

Yes, "Thanks" is now "Like". I should change that. Welcome to 43oh.

How did you find this url.. It's not public yet.

 

I actually joined the forum on Thursday, just before the switchover, and made my first post then, so I was just using the old forum link from the 43oh main site. I received the reply notification email and just followed the link. Didn't even realize there was going to be a change until I followed the link!

 

H

Share this post


Link to post
Share on other sites

Here is a follow up to the original post that is focused on trying to use the framework and stay in LPM4 as much as possible

 

-rick

 

 

 

File: LPM_Button.ino

/**
* LPM_Button - simple button handler with sleep features
*
*   Demonstrates how to sleep in LPM4 and have port interrupt wake up on button push,
*   switch to LPM3 run for a bit and then go back to LPM4 from interrupts.
*/

typedef unsigned int (*uintFuncPtr)(void);

/* forward declarations */
void attachInterrupt2(uint8_t, uintFuncPtr, int);
unsigned int handleButton(void);

/*
* -- G L O B A L   V A R I A B L E S --
*/
unsigned char count = 0;
volatile unsigned char times = 0;

/*
* setup() - Configure pins, disable WDT, assign Pushbutton (p1.3) interrupt handler
*/

void setup() 
{
 disableWatchDog(); // disable timer keeper to use less power

 pinMode(RED_LED,OUTPUT);
 pinMode(GREEN_LED,OUTPUT);
 pinMode(PUSH2, INPUT_PULLUP);     

 digitalWrite(RED_LED, HIGH);
 digitalWrite(GREEN_LED, LOW);

 attachInterrupt2(PUSH2, handleButton, RISING);
}

void loop() 
{
 LPM4;  // start in ultra low power mode, all work is done in the ISR routines
}

/*
* handleButton() - interrupt handler for button up (low -> high) event
*/
unsigned int handleButton(void)
{
 ++times;

 P1OUT |= digital_pin_to_bit_mask[RED_LED];
 P1OUT &= ~digital_pin_to_bit_mask[GREEN_LED];

 // Set up Timer A "Up to CCR0" mode, divide by 2^n, clock from SMCLK (2)/ACLK (1), clear timer
 TACCR0 = 0x1fff;
 TACCTL0 = CCIE;
 TACTL = MC_1 | ID_0 | TASSEL_1 | TACLR;

 return OSCOFF; // switch from LPM4 to LPM3 so the ACLK can drive the Timer clock
}

/*
* TA0_ISR() - Handle CCR0 interrupt
* in LPM3 mode, blink the lights and then go back to deep sleep
*/
__attribute__((interrupt(TIMER0_A0_VECTOR)))
static void TA0_ISR(void)
{
 if (++count > 2 * times) {
   P1OUT |= digital_pin_to_bit_mask[RED_LED];
   P1OUT &= ~digital_pin_to_bit_mask[GREEN_LED];

   TACTL = TACLR;

   __bis_status_register_on_exit(OSCOFF);  // switch back back into LPM4 sleep
 } 
 else {
   P1OUT ^= digital_pin_to_bit_mask[RED_LED]|digital_pin_to_bit_mask[GREEN_LED];
 }
}

File: WInterrupt2.ino

/*
* WInterrupt2.ino - interrupt handlers with LPM modification
*
*   Functions that provide a way for user written PORTN interrupt handlers to clear bits
*   in the SR register of an ISR so the MCU can switch sleep modes. The user written
*   handlers should return 0 or an unsigned LPM modebit value(s) to clear on ISR exit.
*
*   Based on WInterrupts.cpp
*
*   Author: Rick Kimball <rick@kimballsoftware.com>
*/

#define bit_pos(A) ((A) == 1u << 0 ? 0 \
                 : (A) == 1u << 1 ? 1 \
                 : (A) == 1u << 2 ? 2 \
                 : (A) == 1u << 3 ? 3 \
                 : (A) == 1u << 4 ? 4 \
                 : (A) == 1u << 5 ? 5 \
                 : (A) == 1u << 6 ? 6 \
                 : (A) == 1u << 7 ? 7 \
                                  : 0)

typedef unsigned int (*uintFuncPtr)(void);

#define NUM_INTS_PER_PORT 8
static volatile uintFuncPtr intFuncP1[NUM_INTS_PER_PORT];
#if defined(__MSP430_HAS_PORT2_R__)
static volatile uintFuncPtr intFuncP2[NUM_INTS_PER_PORT];
#endif

/**
* attachInterrupt2 - assign a user defined function to a port interrupt pin
*/

void attachInterrupt2(uint8_t interruptNum, unsigned int (*userFunc)(void), int mode)
{
 uint8_t bit = digitalPinToBitMask(interruptNum);
 uint8_t port = digitalPinToPort(interruptNum);

 if ((port == NOT_A_PIN) || !((mode == FALLING) || (mode == RISING))) return;

 __dint();
 switch(port) {
 case P1:
   P1IE |= bit;
   P1IFG &= ~bit;
   P1IES = mode ? P1IES | bit : P1IES & ~bit;
   intFuncP1[bit_pos(bit)] = userFunc;
   break;
#if defined(__MSP430_HAS_PORT2_R__)
 case P2:
   P2IE |= bit;
   P2IFG &= bit;
   P2IES = mode ? P2IES | bit : P2IES & ~bit;
   intFuncP2[bit_pos(bit)] = userFunc;
   break;
#endif
 default:
   break;
 }
 __eint();
}

void detachInterrupt2(uint8_t interruptNum)
{
 uint8_t bit = digitalPinToBitMask(interruptNum);
 uint8_t port = digitalPinToPort(interruptNum);

 if (port == NOT_A_PIN) return;

 switch(port) {
 case P1:
   P1IE &= ~bit;
   intFuncP1[bit_pos(bit)] = 0;
   break;
#if defined(__MSP430_HAS_PORT2_R__)
 case P2:
   P2IE &= ~bit;
   intFuncP2[bit_pos(bit)] = 0;
   break;
#endif
 default:
   break;
 } 
}

__attribute__((interrupt(PORT1_VECTOR)))
void Port_1(void)
{
 uint8_t i;
 for(i = 0; i < 8; i++) {
   if((P1IFG & BV(i)) && intFuncP1[i]) {
     unsigned bits2clear;
     if ( bits2clear=intFuncP1[i]() ) {
       __bic_status_register_on_exit(bits2clear);
     }
     P1IFG &= ~BV(i);
   }
 }
}

#if defined(__MSP430_HAS_PORT2_R__)
__attribute__((interrupt(PORT2_VECTOR)))
void Port_2(void)
{
 uint8_t i;
 for(i = 0; i < 8; i++) {
   if((P2IFG & BV(i)) && intFuncP2[i]) {
     unsigned bits2clear;
     if ( bits2clear=intFuncP2[i]() ) {
       __bic_status_register_on_exit(bits2clear);
     }
     P2IFG &= ~BV(i);
   }
 }
}
#endif

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×