【问题标题】:C program a button to perform a task once when pressed (latch)C 编写一个按钮以在按下时执行一项任务(闩锁)
【发布时间】:2013-04-24 18:53:23
【问题描述】:

我对 c 和 Raspberry Pi 比较陌生,正在尝试简单的程序。我想要的是当按钮被按下时它 printfs 一次并且在再次按下按钮之前不会再次 printf,即使按钮被按住(有点像闩锁)。我想也许添加第二个 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;
}

【问题讨论】:

  • 您可能会发现谷歌搜索“按钮去抖动”会有所帮助。
  • 这完全是为了知道技术术语是什么,这样我就可以查到它。谢谢
  • 乐于提供帮助 - 我认为这不值得回答,但有时使用正确的搜索词确实会有所帮助!

标签: c button raspberry-pi debouncing


【解决方案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 ) ;
    }

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

【讨论】:

  • usleep(1000) 调用应该在 while 循环内。否则你不会做太多去抖动。
【解决方案2】:

对于像这样的简单程序,使用您所做的繁忙循环就可以了。不过,我建议改掉这个习惯,因为除了玩具项目之外,这通常是不可接受的。

有多少人编写代码,就有多少方法可以消除按钮的抖动。在某些情况下,在硬件中执行此操作可能是可行的方法,但并非没有缺点。无论如何,由于这是一个编程站点,我们假设您不能(或不想)更改硬件。

一个快速而肮脏的修改是定期检查主循环中的按钮,只有在它发生变化时才采取行动。由于您是 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;
}

【讨论】:

  • 我尝试实现这段代码。仍然有一点反弹,所以我更改了“BUTTON_DEBOUNCE_CHECKS”。我发现一个不错的值是 300 左右(我的按钮可能不是那么“干净”)。通常情况下,它是释放导致弹跳的按钮。 (还尝试了“BUTTON_DEBOUNCE_CHECKS”= 5000 来获得乐趣,明显滞后但没有反弹)。硬件解决方案更可靠吗?感谢您的帮助!
  • 这个解决方案的问题是BUTTON_DEBOUNCE_CHECKS 需要的值不仅取决于开关的特性,还取决于处理器的速度,以及“其余主程序”的执行时间看代码”。它也“忙于等待”——在主循环中没有其他阻塞调用,CPU 使用率将达到 100%,并且使其他进程饿死,从而导致整个系统运行缓慢。如果您希望能够在监视按钮按下的同时做其他工作,最好使用带有usleep() 调用的单独线程来保持CPU 负载最小。
  • 我完全同意 BUTTON_DEBOUNCE_CHECKS 值的脆弱性,并在文本和代码中都说了这么多。但是,对于刚刚学习去抖动的人,我觉得最好避免过多讨论定时器、中断和调度。我不同意代码忙等待。没有什么可以阻止“其余代码”屈服或休眠。正如所写的那样,只有在它是一个纯粹的协作多任务操作系统或没有其他任务时,它才会占用所有处理器周期。我对 Raspberry Pi 不够熟悉,不知道它使用什么 RTOS(如果有的话)。
  • @Clifford 至于usleep(),如果没有任何其他可运行的任务,好处将大大减少或消除(好吧,它可能有助于某些系统中的功耗和相关的热量产生)。这是一个非常简单的测试程序,OP 可能不打算将其扩展为与其他任务一起运行。它肯定会阻塞调用线程,尽管比粗略的旋转等待更礼貌。即使您在单独的线程中执行此操作,您的主线程仍然需要让步或休眠,正如我所说,这已经可以完成。顺便问一下,这是否意味着 Raspberry Pi 操作系统符合 POSIX 标准?
  • @Marmstrong 如果您的按钮非常不对称,您可以为每个方向使用不同的去抖动长度。在响应和去抖动之间找到合适的折衷方案时,硬件可能会遇到同样的问题。它还占用电路板空间,影响单位成本,并且灵活性较差。与软件不同,您不能远程更改硬件 [好吧,也许是 FPGA,如果您考虑硬件 :) ]。但是,它确实减轻了 MCU 的工作量,您甚至可以将按钮放在中断引脚上(如果有的话),这可以让您进入低功耗模式并在按下按钮时恢复。
【解决方案3】:

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

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

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

while (1) {
    while (!button_pressed)
        ;

    printf("Button pressed!\n");


    while (elapsed_time < offset)
        ;
}

等等

编辑:正如@jerry 指出的那样,当按住按钮时,上述内容不能“正确”工作。这里有几个more professional code snippets,您可以使用它们来满足所有要求。

【讨论】:

  • 如果按住按钮,这段代码会无限重复printf
  • @jerry 没错。补充说,以及更好的替代品的链接。
  • 如果您将while (elapsed_time &lt; offset) 更改为while (elapsed_time &lt; offset || button_pressed),它将解决该特定问题
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-04-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-30
  • 1970-01-01
相关资源
最近更新 更多