C program a button to perform a task once when pressed (latch)

38,917

Solution 1

For a simple program like this, using busy loops like you've done is fine. However, I'd suggest getting out of the habit because it's often unacceptable in anything more than a toy project.

There are as many ways to debounce a button as there are people writing code. Doing it in hardware may be the way to go in some cases, but it's not without its drawbacks. In any case, since this is a programming site, let's assume you can't (or don't want to) change the hardware.

A quick and dirty modification is to periodically check the button in the main loop and only act if it has changed. Since you're new to C and and embedded programming, I'll avoid timers and interrupts, but know that you can make the code more understandable and maintainable once you learn about them.

#include <bcm2835.h>
#include <stdio.h>
#define PIN RPI_GPIO_P1_11

// A decent value for the number of checks varies with how "clean" your button is, how 
// responsive you need the system to be, and how often you call the helper function. That
// last one depends on how fast your CPU is and how much other stuff is going on in your
// loop. Don't pick a value above UINT_MAX (in limits.h)
#define BUTTON_DEBOUNCE_CHECKS 100

int ButtonPress()
{
    static unsigned int buttonState = 0;
    static char buttonPressEnabled = 1;

    if(bcm2835_gpio_lev(PIN))
    {
        if(buttonState < BUTTON_DEBOUNCE_CHECKS)
        {
            buttonState++;
        }
        else if(buttonPressEnabled)
        {
            buttonPressEnabled = 0;
            return 1;
        }
    }
    else if(buttonState > 0 )
    {
        buttonState--;
        // alternatively you can set buttonState to 0 here, but I prefer this way
    }
    else
    {
        buttonPressEnabled = 1;
    }

    return 0;
}

int main()
{
    if(!bcm2835_init())
        return 1;

    bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_INPT);

    while(1)
    {
        if(ButtonPress())
        {
            printf("The button has been pressed\n");
        }

        // the rest of your main loop code
    }

    bcm2835_close();
    return 0;
}

Solution 2

The following function polls the button at nominally 1 millisecond intervals and required that the state remain "releases" to 20 consecutive polls. That will typically be sufficient to debounce most switches while retaining responsiveness.

Replace your while(bcm2835_gpio_lev(PIN)){} loop with a call to waitButtonRelease().

#include <unistd.h>
#define DEBOUNCE_MILLISEC 20

void waitButtonRelease()
{
    int debounce = 0 ;

    while( debounce < DEBOUNCE_MILLISEC )
    {
        usleep(1000) ;

        if( bcm2835_gpio_lev(PIN) )
        {
            debounce = 0 ;
        }
        else
        {
            debounce++ ; 
        }
    }
}

You may find it necessary also to debounce button presses as well as releases. That is done in the same way, but counting the opposite state:

void waitButtonPress()
{
    int debounce = 0 ;

    while( debounce < DEBOUNCE_MILLISEC )
    {
        usleep(1000) ;

        if( !bcm2835_gpio_lev(PIN) )
        {
            debounce = 0 ;
        }
        else
        {
            debounce++ ; 
        }
    }
}

Or perhaps a single function to debounce either state:

#include <stdbool.h>

void waitButton( bool state )
{
    int debounce = 0 ;

    while( debounce < DEBOUNCE_MILLISEC )
    {
        usleep(1000) ;

        if( bcm2835_gpio_lev(PIN) == state )
        {
            debounce++ ;
        }
        else
        {
            debounce = 0 ; 
        }
    }
}

Given this last function, your main while loop might look like:

    while(1)
    {
        waitButton( true )
        printf("The button has been pressed\n");

        waitButton( false ) ;
    }

If you have access to a digital storage oscilloscope, you might probe the switch signal directly to see exactly what the switch bounce looks like. It may help you understand the problem and also to tailor the debounce to the characteristics of your particular switch.

Share:
38,917
Marmstrong
Author by

Marmstrong

SOreadytohelp

Updated on March 15, 2020

Comments

  • Marmstrong
    Marmstrong about 4 years

    I am relatively new to c and the Raspberry Pi and am trying simple programs. What I would like is when the button is pressed it printfs once and doesn't printf again until the button is pressed again, even if the button is held down (sort of a latch). I thought maybe adding the second while loop in would fix this, but sometimes it still doesn't detect a button press.

    #include <bcm2835.h>
    #include <stdio.h>
    #define PIN RPI_GPIO_P1_11
    
    int main()
    {
        if(!bcm2835_init())
            return 1;
    
        bcm2835_gpio_fsel(PIN, BCM2835_GPIO_FSEL_INPT);
    
        while(1)
        {
            if(bcm2835_gpio_lev(PIN))
            {
                printf("The button has been pressed\n");
            }
    
           while(bcm2835_gpio_lev(PIN)){}
        }
    
        bcm2835_close();
        return 0;
    }