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.

Sensing button input with MSP430

Other Parts Discussed in Thread: MSP430F2619

Hello guys,

I have my msp set up to accept user input to set setpoints. When they press the "up" button the pressure setpoint increases by 10 and when they press "down" button it decreases by 1.

 

My question is how do you set the sensing of the input to only take 1 "command" from the button. Right now to simulate the button press I am just applying a voltage to the appropriate input on the chip but even if i barely tap the input the chip scans the input multiple times and adds multiples of 10 to my variable.

Will this be fixed by using an actual keypad with debounce or is some code required? For example should I use the port interrupt that way on a rising edge i enter ISR and increment/decrement variable?

Does this make sense? If not I can attempt to rewrite my question.

Thanks,

  • Brock,

    I think your question is fairly understandable: you need to debounce a (push) button.

    Ideally all port pin inputs should be handled with interrupts. Actually, all internal & external events should be interrupt-driven to achieve real-time responses & minimize CPU active time which consumes much more current than low power modes.

    Particularly for the port pin interrupt, you can specify which edge (rising/falling) to trigger the interrupt flag and subsequently active the interrupt service routine. Now you can perform your debounce routine. You can definitely use a while loop as you mentioned in your post. However, a HW delay could substitute for this SW delay to obtain much better efficiency if you have a free Timer resource to spare.

    Here's an example of for a single button debounce using port interrupt & a WDT timer for a HW debounce. Upon port pin interrupt, the ISR disables the port interrupt, enables the WDT to trigger after an interval, and goes off to service the button press. Once the WDT interrupt triggers, the code enables the port interrupt again.
    If you have several interruptible pins, you'll need to service them accordingly.

     

    <.....main code....

    // sets up pins for Port2, direction, edge, etc, interrupt enabled.
    __bis_SR_register( LPMx + GIE );       // Go to sleep, wait for port pin interrupt
    //wake up from pin interrupt, do stuff
    ......................>

     

    #pragma vector=PORT2_VECTOR
    __interrupt void Port2_ISR(void)
    {
      buttonsPressed = P2IFG;
     
      P2IFG &= ~ (Which_ever_pin_interrupt_is_on);      // Clear the flag
      P2IE &= ~ (Which_ever_pin_interrupt_is_on);       // Temporarily
    disable the interrupt

      //WDT as 250ms interval counter
      SFRIFG1 &= ~WDTIFG;
      WDTCTL = WDTPW + WDTSSEL_1 + WDTTMSEL + WDTCNTCL + WDTIS_5;
      SFRIE1 |= WDTIE;

      //Exits ISR, returns to active mode to execute code servicing the button press
      __bic_SR_register_on_exit(LPM3_bits);   
     
    }

    #pragma vector=WDT_VECTOR
    __interrupt void WDT_ISR(void)
    {
     
      SFRIFG1 &= ~WDTIFG;
      SFRIE1 &= ~WDTIE;
      WDTCTL = WDTPW + WDTHOLD;
      P2IE |= 
    (Which_ever_pin_interrupt_is_on); 
    }

     

    Regards.

    Dung

  • Hey Dung, thanks for the response.

    I need some checking of my code that you or someone else may be able to answer.

     

    I went with your basic logic for handling my button press/debouncing. I am using Timer A instead of WDT b/c the WDT is used for my LCD initialization.My problem is once i put an input and the port interrupt service routine is carried out and i am put back into the code, my P1IE bit isn't being reset by the TimerA interrupt so my program is stalling b/c it can't sense any more input on that pin.

     

    I am checking this by using the register viewer in debug mode. P1IE is x01 before port interrupt and then goes to x00 after the port interrupt ISR(rightfully so) but I must not be entering my Timer A isr because my P1IE bit isn't being enabled back to x01. Here is a snippet of my main code followed by the ISRs.

     

      LCD_inst(CLEAR_DISPLAY);
      LCD_cursor(0x00);
      LCD_string(&stext23[0]);  //"Input Pressure"
     
      __bis_SR_register(LPM4_bits + GIE); //enter LPM0 and wait for button press

     

    here is my port 1 ISR:

     

    #pragma vector=PORT1_VECTOR
    __interrupt void Port_1(void)
    {
        P1IFG &= ~0x010;    // clear port interrupt flag
        P1IE &= ~0x010;     // temporarily disable port interrupt
       
        //set up timer A for use with debouncing interrupt
        TACCTL0 &= ~CCIFG;
        TACTL = TASSEL_1 + MC_1 + ID_0 + TAIE;
        CCR0 = 8192;
        TACCTL0 |= CCIE;
       
        //exits ISR, returns to active mode to execute code servicing button press
        __bic_SR_register_on_exit(LPM4_bits);
    }

     

    Now i configure timer A to count up to CCR0 which should be 250ms and then exit and return to my main

     

    Here is my timer interrupt:

    #pragma vector=TIMERA1_VECTOR
    __interrupt void TIMERA1_ISR(void)
    {
      TACCTL0 &= ~CCIFG;  //enable interrupt flag
      TACCTL0 &= ~CCIE;  //enable timer interrupt
      P1IE |= 0x010;  //enables interrupt on pin again
    }

    So whatever reason my timer interrupt isn't being processed, any clue as to what is wrong in my configuration in the port ISR?

  • Brock said:
    #pragma vector=TIMERA1_VECTOR
    __interrupt void TIMERA1_ISR(void)
    {
      TACCTL0 &= ~CCIFG;  //enable interrupt flag
      TACCTL0 &= ~CCIE;  //enable timer interrupt
      P1IE |= 0x010;  //enables interrupt on pin again
    }

    I'm not sure whether this is the right vector. TACCR0 has its own interrupt vector, while CCR1, CCR2 and TAR share a different one.
    Also, the comments are wrong, you are (intentionally) DISabling the interrupt. In Case of TACCR0, as it has its own interrupt vector, clearing the IFG flagh is not necessary. This is implicitely done as soon as the ISR is entered.

    And before re-enabling the port interrupt, you shoud clear the IFG for this port (else you'd get a new port interrupt imemdiately if there was a level change anywhere during the TimerA delay)

  • Jens-Michael Gross said:

    #pragma vector=TIMERA1_VECTOR
    __interrupt void TIMERA1_ISR(void)
    {
      TACCTL0 &= ~CCIFG;  //enable interrupt flag
      TACCTL0 &= ~CCIE;  //enable timer interrupt
      P1IE |= 0x010;  //enables interrupt on pin again
    }

    I'm not sure whether this is the right vector. TACCR0 has its own interrupt vector, while CCR1, CCR2 and TAR share a different one.
    Also, the comments are wrong, you are (intentionally) DISabling the interrupt. In Case of TACCR0, as it has its own interrupt vector, clearing the IFG flagh is not necessary. This is implicitely done as soon as the ISR is entered.

    And before re-enabling the port interrupt, you shoud clear the IFG for this port (else you'd get a new port interrupt imemdiately if there was a level change anywhere during the TimerA delay)

    [/quote]

     

    Ah yes, there is a TimerA0 vector for the TACCR0 and now I understand what you are saying about the comments. We are disabling the interrupt flag and reenabling them when leaving the port interrupt isr...

    Thanks!

  • Walking through my code step by step and monitoring the registers in the register window I think I found out why my TimerA ISR isn't triggering. My CCR0 is set at 8192, or 0x2000. While monitoring the TAR value it stops counting up once i enter the 2nd LPM4 :

     

    __bis_SR_register(LPM4_bits + GIE); //enter LPM0 and wait for port interrupt
     
      LCD_inst(CLEAR_DISPLAY);  //return from ISR and handle button press
      LCD_cursor(0x00);
      LCD_string(&stext24[0]); //"Input accepted!"
     // vWait(WDT_MDLY_4, 125);      // 1s delay
      mwp = mwp + 10;       //increase mwp value
      LCD_inst(CLEAR_DISPLAY); 
     
     
      //display MWP setpoint to screen after increase toggle
      LCD_output_result(0x00, mwp);
      LCD_cursor(0x04);
      LCD_string(&stext09[0]);
     
      __bis_SR_register(LPM4_bits + GIE); << my timer "stops" here and doesn't count up to the x2000; it stops at x092D
      LCD_inst(CLEAR_DISPLAY);  //return from ISR and handle button press
      LCD_cursor(0x00);
      LCD_string(&stext24[0]); //"Input accepted!"

     

    Wait; as I type this I think I figured out my own problem. When I reenter LPM4 that disables my ACLK so therefore my TimerA is disabled due to ACLK not being on...is this correct??!

    Looks like I should use LPM3 instead...

  • Ok so that worked as far as the timer not counting...my TAR is counting up to x2000 (8192) and starting over but I still am not entering the TimerA ISR...

  • Brock,

    not sure which device you're working on, but attached is an example of how to use timerA interrupt for 20xx or G2xxx devices.

    Here's the link to the zip package containing several other examples for timerA as well as other peripherals.

    hope that helps!

     

    Dung

  • Thanks Dung I'll take a look.

    I have the timer working properly and it is now entering the timer ISR...but upon exiting it seems it is clearing my P1IE bit therefore i can't sense the input again.

  • oh yea; i have that entier package along with a few printed out in front of me ;)...just some growing pains I'm going through; I figure out one thing and another problem arises

  • So as I leave this weekend; my problem is that when leaving the TimerA ISR somehow P1IE is getting cleared from 1 to 0 and therefore I can't sense any more inputs on the pin. My eyes need a break from this so maybe some fresh eyes could have a look over the weekend:

     

     

    #include "globals.h" 
    //#include "defines.h"
    #include "msp430f2619.h"

    int intro = 0;        //binary value that controls menu flow
    int setpoints = 0;    //binary value that controls menu flow
    int gaugepsi = 2000;  //constant for max gauge psi
    int mwp = 0;      //variable to store mwp setpoint
    int hi = 0;       //variable to store hi setpoint
    int lo = 0;       //variable to store lo setpoint
    int mwpset = 0;   //binary value that tells if MWP is set

    void main(void)
    {
      // pre initialization
     
     
      // disable watchdog timer
      //------------------------
      WDTCTL = WDTPW + WDTHOLD;               // Stop WDT

      // clock setting
      //------------------------
      DCOCTL = CALDCO_1MHZ;                  // Set DCO to 16MHz
      BCSCTL1 = CALBC1_1MHZ;                 // MCLC = SMCLK = DCOCLK = 16MHz
      BCSCTL1 |= DIVA_0;                      // ACLK = ACLK/1
      BCSCTL3 = LFXT1S_2;                     // LFXT1 = ACLK = VLO = ~12kHz
      BCSCTL3 &= ~LFXT1OF;                    // clear LFXT1OF flag, 0

     
      //LCD pin initialization
      LCD_DATA_DIR = 0xFF;                    // P2.0 to P2.7 are switched to
      LCD_DATA = 0x00;                       
     
      LCD_RS_DIR;                          // P3.0 is switched to output mode, 1                 
      SETLCD_RS;                          // output HIGH - switch on RS
      LCD_E_DIR;                          // P3.1 is switched to output mode, 1                 
      SETLCD_E;
     

      // initialize software
      _EINT();                     // enable all interrupts
     
      //LCD initialization
      LCD_init();                       // LCD Initialization
      LCD_disp_allpixels();
      LCD_init_CGRAM();
      LCD_inst(CLEAR_DISPLAY);          // clear display
     
      //set up port for interrupt capabilities
      P1IES &= ~0x010;     //P1.4 Hi/Lo edge
      P1IE |= 0x010;    //P1.4 interrupt enabled
     
      P1IFG &= ~0x010;  //P1.4 IFG cleared
     
     
     
      LCD_inst(CLEAR_DISPLAY);
      LCD_cursor(0x00);
      LCD_string(&stext23[0]);  //"Input Pressure"
     
      __bis_SR_register(LPM4_bits + GIE); //enter LPM0 and wait for port interrupt
     
      LCD_inst(CLEAR_DISPLAY);  //return from ISR and handle button press
      LCD_cursor(0x00);
      LCD_string(&stext24[0]); //"Input accepted!"
     // vWait(WDT_MDLY_4, 125);      // 1s delay
      mwp = mwp + 10;       //increase mwp value
      LCD_inst(CLEAR_DISPLAY); 
     
     
      //display MWP setpoint to screen after increase toggle
      LCD_output_result(0x00, mwp);
      LCD_cursor(0x04);
      LCD_string(&stext09[0]);
     
      __bis_SR_register(LPM3_bits + GIE);
      LCD_inst(CLEAR_DISPLAY);  //return from ISR and handle button press
      LCD_cursor(0x00);
      LCD_string(&stext24[0]); //"Input accepted!"
     
    }


    /*****************************************************************************
    // Port Interrupt ISR clears interrupt flag register, temporarily disables
    // port interrupts, and configures timer A as a 250ms interval counter
    // to reenable port interrupts; then exits back to main function to service
    // the button press.
    *****************************************************************************/
    #pragma vector=PORT1_VECTOR
    __interrupt void Port_1(void)
    {
        P1IFG &= ~0x010;    // clear port interrupt flag
        P1IE &= ~0x010;     // temporarily disable port interrupt
       
        //set up timer A for use with debouncing interrupt
        TACCR0 = 8192;  //sets count value for timer to count up to
        TACCTL0 |= CCIE; //enables interrupt
        TACTL = TASSEL_1 + MC_1 + ID_0 + TAIE;
       
       
        //exits ISR, returns to active mode to execute code servicing button press
        __bic_SR_register_on_exit(LPM4_bits);
    }
    #pragma vector = _VECTOR
    __interrupt void vTimerWDT_IntMode(void)
    {
       bWDTFLAG = TRUE;                      // set timer status flag
    }

    #pragma vector=TIMERA0_VECTOR
    __interrupt void TIMERA1_ISR(void)
    {
      TACCTL0 &= ~CCIFG;  //clear interrupt flag
      TACCTL0 &= ~CCIE;  //disable timer interrupt
      //TACTL = MC_0; //halts timer
      P1IFG &= ~0x010;  //P1.4 IFG cleared
      P1IE |= 0x010;  //enables interrupt on pin again
      P1IE |= 0x001;
      __bic_SR_register_on_exit(LPM0_bits);
    }

  • In your port ISR, you do not determine which port pin has caused the interrupt. But in your TImerA ISR, you enable interrupt for P1.4 AND P1.0 (which might immediately trigger an interrupt for P1.0 and clear the P1.4IE in your port ISR)

    To avoid glitches, I'd re-structure the code so the port ISR will only trigger TimerA and return to LPM, and when the timer expires, you can read the current (debounced) port state within the TimerA ISR. This way, the CPU can remain in LPM after the port has sensed a level change and the TimerA ISR will only clear the LPM bits, if the port pin is still active after the debounce delay.
    250ms is bit much, as some people might intentionally press the button more often than 4 times per second. For debouncing, 100ms should be sufficient. But for testing, 250ms is okay.

    Also, in your port ISR, you enable the interrupt for TACCR0 as well as for TAIV, which means that you'll get two interrupts, first the TACCR0 interrupt, then the TA overflow interrupt one timer tick later. Yet you don't have an ISR for it. You should not set the TAIE flag in TACTL.

  • Jens-Michael Gross said:
    In your port ISR, you do not determine which port pin has caused the interrupt. But in your TImerA ISR, you enable interrupt for P1.4 AND P1.0 (which might immediately trigger an interrupt for P1.0 and clear the P1.4IE in your port ISR)

    I thought this might be a problem but wasn't totally sure how the IFG register worked. If I used a variable to hold the button, something like " 'buttonpress' = P1IFG; " upon entering the port ISR and then clearing the IFG and IE bits of that particular register I think that would work better.

     

    Jens-Michael Gross said:
    Also, in your port ISR, you enable the interrupt for TACCR0 as well as for TAIV, which means that you'll get two interrupts, first the TACCR0 interrupt, then the TA overflow interrupt one timer tick later. Yet you don't have an ISR for it. You should not set the TAIE flag in TACTL.

    I realized I had this Friday, but i wasn't sure if not having a ISR for the TA overflow would have an effect; but as you said no point in enabling it when I can just omit that code.

     

    Thanks Jens!

  • Jens-Michael Gross said:
    you enable interrupt for P1.4 AND P1.0

    oh by the way, enabling the 1.0 interrupt was just me messing around testing something; that isn't a part of the code, I should have taken that out.

**Attention** This is a public forum