C编程按下按钮执行任务一次(锁定)

时间:2013-04-30 18:47:01

标签: c button raspberry-pi debouncing

我对c和Raspberry Pi相对较新,我正在尝试简单的程序。我想要的是当按下按钮时printfs一次并且不再打印,直到再次按下按钮,即使按下按钮(类似闩锁)。我想可能会添加第二个while循环来解决这个问题,但有时它仍然没有检测到按下按钮。

#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;
}

3 个答案:

答案 0 :(得分:2)

你的逻辑是正确的,如果按钮完美,这将有效。但他们不是。您必须去抖动按钮的信号。实现这一目标的两种方法(组合时效果最佳):

予。在按钮的两个引脚之间添加一个电容(或尝试an even more sophisticated button debouncer circuitry)和/或

II。使用软件去抖(伪C):

while (1) {
    while (!button_pressed)
        ;

    printf("Button pressed!\n");


    while (elapsed_time < offset)
        ;
}

编辑:正如@jerry所指出的那样,按住按钮时,上述操作无法正常工作。以下是您可以用来满足所有要求的几个more professional code snippets

答案 1 :(得分:1)

以下功能以1毫秒的间隔轮询按钮,并要求状态保持“释放”到20个连续的民意调查。这通常足以在保留响应性的同时去抖大多数交换机。

通过调用while(bcm2835_gpio_lev(PIN)){}替换您的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++ ; 
        }
    }
}

您可能会发现还需要去除按钮按下以及释放按钮。这是以相同的方式完成的,但计算相反的状态:

void waitButtonPress()
{
    int debounce = 0 ;

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

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

也许是一个去抖动状态的功能:

#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 ; 
        }
    }
}

鉴于此最后一个函数,您的主while循环可能如下所示:

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

        waitButton( false ) ;
    }

如果您可以访问数字存储示波器,则可以直接探测开关信号,以准确查看开关反弹的样子。它可以帮助您理解问题,并根据特定开关的特性定制去抖动。

答案 2 :(得分:1)

对于像这样的简单程序,使用像你已经完成的繁忙循环很好。但是,我建议戒掉这个习惯,因为除了玩具项目之外,这通常是不可接受的。

有很多方法可以去除按钮,因为有人在编写代码。在某些情况下,在硬件中执行它可能是一种方法,但它并非没有缺点。在任何情况下,由于这是一个编程站点,我们假设您不能(或不想)更改硬件。

快速而肮脏的修改是定期检查主循环中的按钮,并且仅在其已更改时才执行。由于你是C和嵌入式编程的新手,我会避免使用定时器和中断,但是一旦你了解它们就知道你可以使代码更容易理解和维护。

#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;
}