Advice on button debounce interrupts

Thread Starter

StrongPenguin

Joined Jun 9, 2018
307
I'm having a bit of trouble getting this little circuit working, and I think it's because of switch bouncing (couple that with that the switches them selves are of very poor quality)

I choose only to focus on "Take/Accept Command" (SW1, left half) for bridge, and "Take/Accept Command" (SW2, right half) for engine room. The mission is to send the engine command between bridge and engine control room.

When ECR pushes on the switch, and bridge is in control, the engine room LED is supposed to flash indicating request to take the engine, and the bridge then has to receipt. Like pong.

The problem: The lamps never turn off, and the flashing just shifts from bridge to ECR. If I deactivate (is there a way to debug without having to // the delays?) the delays and run the program step by step, things seem to work fine.

I have a suspicion, that even though I tried to save the bounce with an interrupt, things still bounce. I tried to deactivate the interrupt while inside the ISR, and then activate it before exit ISR. That seems to get the program more stable, but still not fixed.

Some hits would be lovely.

Code:
#include <msp430.h> 

#define ECR_CMD_LED         BIT1    //Port 1.1
#define ECR_M               BIT4    //Port 1.4
#define ENGINE_ON           BIT5    //Port 1.5
#define ECR_TAKE            BIT0    //Port 1.0
#define BRIDGE_TAKE         BIT0    //Port 2.0
#define BRIDGE_M            BIT1    //Port 2.1
#define BRIDGE_CMD_LED      BIT2    //Port 2.2
#define SPEAKER_ON          BIT3    //Port 2.3

int main(void)
{
    WDTCTL = WDTPW | WDTHOLD;    // stop watchdog timer
    /*
    P1DIR = 0x00;
    P2DIR = 0x00;
    P1REN = 0x00;
    P1OUT = 0x00;
    P2OUT = 0x00;
    */
    P1OUT = ECR_M | ECR_TAKE;
    P1DIR = ECR_CMD_LED | ENGINE_ON ;        //Port 1 outputs (rest are inputs)
    P1REN = ECR_M | ECR_TAKE;           //Enable internal pull-up resistor (this only enables it, it does not dictate direction)

    P2OUT = BRIDGE_M | BRIDGE_TAKE;
    P2DIR = BRIDGE_CMD_LED | SPEAKER_ON;     //Port 1 outputs (rest are outputs)
    P2REN = BRIDGE_M | BRIDGE_TAKE;   //Enable internal pull-up resistor (this only enables it, it does not dictate direction)

     //| ECR_M_DOWN;
    P1IES |= ECR_TAKE; // | ECR_M_DOWN;
    P1IFG |= ECR_TAKE; // & ~ECR_M_DOWN;

     // | BRIDGE_M_DOWN;       //Enable interrupt
    P2IES |= BRIDGE_TAKE; // | BRIDGE_M_DOWN;      //Set interrupt HIGH TO LOW
    P2IFG |= BRIDGE_TAKE; // & ~BRIDGE_M_DOWN;    //Clear interrupt flag due to being set by PxOUT line


    P1OUT |= ECR_CMD_LED;                  //Start command station. Make the command LED's random someday

    _BIS_SR(GIE);          //Enable ISR (interrupt service routine)

    while(1)
    {
        if((P2IN & BRIDGE_TAKE) == 0)
            {P2IE |= BRIDGE_TAKE;}     //Go to interrupt

        if((P1IN & ECR_TAKE) == 0)
            {P1IE |= ECR_TAKE;}    //Go to Port_1 interrupt

    }

    return 0;
}

#pragma vector = PORT1_VECTOR;
__interrupt void Port_1(void)   //Engine - for engine to gain control, bridge must accept!
{
    P2IE &= ~BRIDGE_TAKE;
    P1IE &= ~ECR_TAKE;    //Disable interrupt while bounce
    __delay_cycles(22000);
    while((P2IN & BRIDGE_TAKE) == 1)    //Loop While NOT pressed is it supposed to be
        {P1OUT ^= ECR_CMD_LED;           //Toggle engine command while waiting for bridge to accept
        __delay_cycles(220000);}            //Blink, blink
    P1OUT |= ECR_CMD_LED;
    P2OUT &= ~BRIDGE_CMD_LED;
    P1IFG &= ~ECR_TAKE;
    P1IE |= ECR_TAKE;
    P2IE |= BRIDGE_TAKE;       //Enable interrupt before exit
}

#pragma vector = PORT2_VECTOR;
__interrupt void Port_2(void)   //Bridge - for bridge to gain control, engine must accept!
{
    P1IE &= ~ECR_TAKE;
    P2IE &= ~BRIDGE_TAKE;
    __delay_cycles(2300);       //End bounce
    while((P1IN & ECR_TAKE) == 1)     //Loop while not pressed
        {P2OUT ^= BRIDGE_CMD_LED;     //Wait for ECR to accept
        __delay_cycles(220000);}
    P2OUT |= BRIDGE_CMD_LED;
    P1OUT &= ~ECR_CMD_LED;
    P2IFG &= ~BRIDGE_TAKE;
    P1IE |= ECR_TAKE;
    P2IE |= BRIDGE_TAKE;
}
 

Attachments

Thread Starter

StrongPenguin

Joined Jun 9, 2018
307
I was also going for the software solution to give myself a challenge, but I've had so much trouble with it, that I consider just debouncing it in hardware now. I can do this with just two caps? Thought I had to use 555 timers.
 

MaxHeadRoom

Joined Jul 18, 2013
28,688
It would have been better if I could have used the Schmidt trigger inputs instead of the TTL (outputs are CMOS), but I only had one on this Pic and It was already in use for the PWM.
Max.
 

Thread Starter

StrongPenguin

Joined Jun 9, 2018
307
"Port P1 Pin Schematic: P1.5 to P1.7, Input/Output With Schmitt Trigger" (says for the other ports too) from the MSP430G2553 datasheet. So it should be good.
 

mckenney

Joined Nov 10, 2018
125
The problem: The lamps never turn off, and the flashing just shifts from bridge to ECR.
Code:
    P1IES |= ECR_TAKE; // | ECR_M_DOWN;
    P1IFG |= ECR_TAKE; // & ~ECR_M_DOWN;
You want to clear the IFG(s) as the last thing before enabling. I think this will allow you to remove the IE settings in the while(1) loop:
Code:
    P1IES |= ECR_TAKE; // High->low
    P1IFG &= ~ECR_TAKE; // Clear stale
    P1IE |= ECR_TAKE;       // Enable
------------------------------------
Code:
    P1IFG &= ~ECR_TAKE;
    P1IE |= ECR_TAKE;
    P2IE |= BRIDGE_TAKE;       //Enable interrupt before exit
}
This always leaves P2IFG:BRIDGE_TAKE set (since you waited for it above), so the next thing will always be a call to the PORT2 ISR, which will suppose that BRIDGE_TAKE was pushed again.
You should clear BRIDGE_TAKE as well, since you've already dealt with it:
Code:
P2IFG &= ~BRIDGE_TAKE; // The bridge has now taken
------------------------------------
Code:
    P2IE &= ~BRIDGE_TAKE;
    P1IE &= ~ECR_TAKE;    //Disable interrupt while bounce
    __delay_cycles(22000);
Keep in mind that switches bounce when they are released as well. Thus the general form of a simplified debounce is:
1) Delay some time, call it 2 ms (2000 clocks)
2) Clear the IFG bit, since we don't expect further bounces
3) If the button is Pressed (usually Low), take some action.
If the bouncing was happening on the Release, no action is taken in step 3.
------------------------------------
Code:
    while((P2IN & BRIDGE_TAKE) == 1)    //Loop While NOT pressed is it supposed to be
Unsolicited: This only works by accident, since BRIDGE_TAKE==BIT0==1. If you move the button wiring, this will stop working. A safer form:
Code:
    while((P2IN & BRIDGE_TAKE) != 0)    //Loop While NOT pressed is it supposed to be
(Edit: Fixed some wording.)
 
Last edited:

mckenney

Joined Nov 10, 2018
125
When ECR pushes on the switch, and bridge is in control, the engine room LED is supposed to flash indicating request to take the engine, and the bridge then has to receipt. Like pong.
This is the kind of thing that lends itself well to a state machine. And not a complex one. Consider 4 states:
0) Bridge owns (no requests)
1) ECR requesting
2) ECR owns (no requests)
3) Bridge requesting
Pushing the BRIDGE_TAKE button in state (1) moves to state (2). In state (2) it moves to state (3). Otherwise it does nothing.
Similarly, pushing the ECR_TAKE button in state (0) moves to state (1), and in state (3) moves to state (0). Else nothing.

In state (1) main blinks the ECR_CMD_LED. In state (3) main blinks the BRIDGE_CMD_LED.
 

Thread Starter

StrongPenguin

Joined Jun 9, 2018
307
"This always leaves P2IFG:BRIDGE_TAKE set (since you waited for it above), so the next thing will always be a call to the PORT2 ISR, which will suppose that BRIDGE_TAKE was pushed again."

@mckenney Don't know why I added this. Thanks for your help, pretty sure this will push it in the right direction. I will also clean the interrupts up a bit more, they look messy.
 

Thread Starter

StrongPenguin

Joined Jun 9, 2018
307
This is starting to annoy me. I've run through it 100 times, breakpoints here and there, and in debug, the code works just fine, but when I run it real time, it just jumps straight to the next LED without waiting for accept. I probably could do this without interrupt, but now I've gotten stubborn and cant accept defeat.

I also debounced the buttons using this recipe, schmitt hex inverter one. It's as if it still were not debounced properly. And I also implemented what you suggested, @mckenney

Code:
#include <msp430.h>

#define ECR_CMD_LED         BIT1    //Port 1.1
#define ECR_M               BIT4    //Port 1.4
#define ENGINE_ON           BIT5    //Port 1.5
#define ECR_TAKE            BIT0    //Port 1.0
#define BRIDGE_TAKE         BIT0    //Port 2.0
#define BRIDGE_M            BIT1    //Port 2.1
#define BRIDGE_CMD_LED      BIT2    //Port 2.2
#define SPEAKER_ON          BIT3    //Port 2.3

int main(void)
{
    WDTCTL = WDTPW | WDTHOLD;    // stop watchdog timer
    /*
    P1DIR = 0x00;
    P2DIR = 0x00;
    P1REN = 0x00;
    P1OUT = 0x00;
    P2OUT = 0x00;
    */
    P1OUT = ECR_M | ECR_TAKE;
    P1DIR = ECR_CMD_LED | ENGINE_ON ;        //Port 1 outputs (rest are inputs)
    P1REN = ECR_M | ECR_TAKE;           //Enable internal pull-up resistor (this only enables it, it does not dictate direction)

    P2OUT = BRIDGE_M | BRIDGE_TAKE;
    P2DIR = BRIDGE_CMD_LED | SPEAKER_ON;     //Port 1 outputs (rest are outputs)
    P2REN = BRIDGE_M | BRIDGE_TAKE;   //Enable internal pull-up resistor (this only enables it, it does not dictate direction)

     //| ECR_M_DOWN;
    P1IES |= ECR_TAKE; // | ECR_M_DOWN;
    P1IFG = 0x00; // & ~ECR_M_DOWN;
    P1IE |= ECR_TAKE;

     // | BRIDGE_M_DOWN;       //Enable interrupt
    P2IES |= BRIDGE_TAKE; // | BRIDGE_M_DOWN;      //Set interrupt HIGH TO LOW
    P2IFG = 0x00; // & ~BRIDGE_M_DOWN;    //Clear interrupt flag due to being set by PxOUT line
    P2IE |= BRIDGE_TAKE;

    P1OUT |= ECR_CMD_LED;                  //Start command station. Make the command LED's random someday

    _BIS_SR(GIE);          //Enable ISR (interrupt service routine)

    while(1)
    {
        if((P2IN & BRIDGE_TAKE) != 0);
            //{P2IE |= BRIDGE_TAKE;}     //Go to interrupt

        if((P1IN & ECR_TAKE) != 0);
            //{P1IE |= ECR_TAKE;}    //Go to Port_1 interrupt

    }

    return 0;
}

#pragma vector = PORT1_VECTOR;
__interrupt void Port_1(void)   //Engine - for engine to gain control, bridge must accept!
{
    P1IFG = 0x00;
    P2IFG = 0x00;
    while((P2IN & BRIDGE_TAKE) != 1)    //Loop While NOT pressed is it supposed to be
        {P1OUT ^= ECR_CMD_LED;           //Toggle engine command while waiting for bridge to accept
        __delay_cycles(220000);}            //Blink, blink
    P1OUT |= ECR_CMD_LED;
    P2OUT &= ~BRIDGE_CMD_LED;
    P1IFG &= ~ECR_TAKE;
    P2IFG &= ~BRIDGE_TAKE;
}

#pragma vector = PORT2_VECTOR;
__interrupt void Port_2(void)   //Bridge - for bridge to gain control, engine must accept!
{
    P1IFG = 0x00;
    P2IFG = 0x00;
    while((P1IN & ECR_TAKE) != 1)     //Loop while not pressed
        {P2OUT ^= BRIDGE_CMD_LED;     //Wait for ECR to accept
        __delay_cycles(220000);}
    P2OUT |= BRIDGE_CMD_LED;
    P1OUT &= ~ECR_CMD_LED;
    P2IFG &= ~BRIDGE_TAKE;
    P1IFG &= ~ECR_TAKE;
}
 
Last edited:

mckenney

Joined Nov 10, 2018
125
when I run it real time, it just jumps straight to the next LED without waiting for accept.
Code:
    while((P2IN & BRIDGE_TAKE) != 1)    //Loop While NOT pressed is it supposed to be
This should be:
Code:
    while((P2IN & BRIDGE_TAKE) != 0)    //Loop While NOT pressed is it supposed to be
Changing only the "==" and not the "1" reversed the sense of the test, so (in the typical test case) the while() exits immediately, implicitly accepting the request.
 

Sensacell

Joined Jun 19, 2012
3,448
Break the problem down, that always makes troubleshooting easier and faster.

1) Are you 100% sure your de-bounce circuit is really working? Check it with a scope.

2) simulate a perfect switch input with a 555 timer oscillator that just makes clean pulses- still problems?- then it's your logic.

Trying to fight both demons at once is hard.
 

Thread Starter

StrongPenguin

Joined Jun 9, 2018
307
@mckenney Don't know why I reversed the line, I will try that. This also explains why it just steps through debug.

@Sensacell I don't own a scope, so I'm not 100% sure they are debounced. A 555 timer is my next try, if the bug above does not fix it.
 

Thread Starter

StrongPenguin

Joined Jun 9, 2018
307
Code:
    while((P2IN & BRIDGE_TAKE) != 1)    //Loop While NOT pressed is it supposed to be
This should be:
Code:
    while((P2IN & BRIDGE_TAKE) != 0)    //Loop While NOT pressed is it supposed to be
Changing only the "==" and not the "1" reversed the sense of the test, so (in the typical test case) the while() exits immediately, implicitly accepting the request.
With !=0, the code skips the while loop completely. I guess that's why I changed it to 1 again, IIRC. There is something I am missing. The BRIDGE_TAKE is set to BIT0, as is ECR_TAKE.

Could this have something to do with how interrupts work? I'm going to try with 555 timers, and if that does not work, then write it without interrupts. It should be fairly simple.
 
Last edited:

danadak

Joined Mar 10, 2018
4,057
You can start with a PC sound card based scope for free. Will give you basically
audio range scope, spectrum analyzer, and function generator all using your
PC sound card.

https://www.zeitnitz.eu/scope_en

http://www.zelscope.com/

http://www.ledametrix.com/oscope/

http://www.virtins.com/downloads.shtml


But first build a simple circuit to protect sound card inputs so you do not
ruin from transients, overvoltage. Google "protect sound card input".



For example http://makezine.com/projects/sound-card-oscilloscope/


Sound card impedance bridge -

http://www.marucchi.it/ZRLC_web/ZRLC/Steber_An_LMS_Impedance_Bridge.pdf

http://www.sillanumsoft.org/ZRLC.htm


There is low cost logic analyzer, good to several Mhz, ~ $10
https://www.ebay.com/itm/USB-Logic-...270582?hash=item25f26191f6:g:Xs4AAOSwQ7haxEa1
https://www.saleae.com/downloads/


Search ebay for USB oscilloscope, quite a few offerings that will get you >> 1 Mhz.



And function generation and others -
http://www.radio.imradioha.org/pc_based_test_gear.htm



Regards, Dana.
 

Thread Starter

StrongPenguin

Joined Jun 9, 2018
307
@danadak Dang, that looks pretty sweet! I have thought about getting a scope, but have been hesitant, since my hobbies come and go, but seeing the prices on those USB scopes was cheerful. I know nothing about scopes, if it says 20 MHz bandwidth, then I can't use it on a 25 MHz microcontroller, no? My MSP430G2553 is only 16 MHz, but If I decide to upgrade one day..
 

danadak

Joined Mar 10, 2018
4,057
Most of the time you will be looking at signals a fraction of the main clock.
Like I2C, low speed SPI, UART......analog stuff << 1 Mhz.

There are real cheap USB scopes but their triggering is so limited almost
un-usable for all but the basics.

Regards, Dana.
 

Thread Starter

StrongPenguin

Joined Jun 9, 2018
307
Ok, then I will hold out for a real one. I have enough scrappy tools as is.

I've tried without the interrupts. Nothing makes sense. No matter how I set the "!=", the result is just an opposite not working circuit.

If I step through it, it works fine. If I run it it normal and set a breakpoint where I think (after the while loop) the code breaks, step through, then activate again, it works fine. It's as if it skips lines.

Code:
    while(1)
    {

        if((P2IN & BRIDGE_TAKE) != 0)      //Bridge function
        {
            while((P1IN & ECR_TAKE) != 1)
            {
                P2OUT ^= BRIDGE_CMD_LED;
                __delay_cycles(220000);
            }
            P1OUT = 0x00;
            P2OUT |= BRIDGE_CMD_LED;
        }
        if((P1IN & ECR_TAKE) != 0)
        {
            while((P2IN & BRIDGE_TAKE) != 1)
            {
                P1OUT ^= ECR_CMD_LED;
                __delay_cycles(220000);
            }
            P2OUT = 0x00;
            P1OUT |= ECR_CMD_LED;
        }
    }

    return 0;
}
[CODE]
 

mckenney

Joined Nov 10, 2018
125
Code:
        if((P2IN & BRIDGE_TAKE) != 0)      //Bridge function
        {
            while((P1IN & ECR_TAKE) != 1)
This logic says: if the BRIDGE_TAKE button isn't pushed, then as long as the ECR_TAKE button is pushed, blink. I think this is the opposite of what you want.

As an aside:
Code:
P1OUT = 0x00;
this turns your button pullup into a pulldown, which effectively de-activates the button.
 
Top