C запрограммировать кнопку на однократное выполнение задачи при нажатии (защелка)

Я относительно новичок в c и Raspberry Pi и пробую простые программы. Я бы хотел, чтобы при нажатии кнопки она печаталась один раз и не печаталась снова, пока кнопка не будет нажата снова, даже если кнопка удерживается нажатой (своего рода защелка). Я думал, что, возможно, добавление второго цикла 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;
}

person Marmstrong    schedule 30.04.2013    source источник
comment
Возможно, вам поможет поиск в Google по запросу «отмена дребезга кнопок».   -  person Carl Norum    schedule 30.04.2013
comment
Все дело в том, чтобы знать, что такое технический термин, чтобы я мог его найти. Спасибо   -  person Marmstrong    schedule 30.04.2013
comment
Рад помочь - я не думал, что это требует ответа, но правильный термин для поиска иногда действительно помогает!   -  person Carl Norum    schedule 01.05.2013


Ответы (3)


Для простой программы, подобной этой, использование циклов занятости, как вы сделали, вполне нормально. Тем не менее, я бы посоветовал избавиться от этой привычки, потому что это часто неприемлемо для чего-то большего, чем игрушечный проект.

Существует столько же способов устранить дребезг кнопки, сколько людей пишет код. В некоторых случаях это может быть аппаратно, но это не лишено недостатков. В любом случае, поскольку это сайт программирования, давайте предположим, что вы не можете (или не хотите) менять аппаратное обеспечение.

Быстрая и грязная модификация состоит в том, чтобы периодически проверять кнопку в основном цикле и действовать только в том случае, если она изменилась. Поскольку вы новичок в программировании на 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;
}
person jerry    schedule 30.04.2013
comment
Я попытался реализовать этот фрагмент кода. Там все еще был небольшой отскок, поэтому я изменил BUTTON_DEBOUNCE_CHECKS. Я нашел хорошее значение около 300 (моя кнопка, вероятно, не такая чистая). Чаще всего именно отпускание кнопки вызывало отскок. (Также пробовал BUTTON_DEBOUNCE_CHECKS = 5000 для удовольствия, заметное отставание, но без отказов). Являются ли аппаратные решения более надежными?? Спасибо за вашу помощь! - person Marmstrong; 01.05.2013
comment
Проблема с этим решением заключается в том, что значение, необходимое для BUTTON_DEBOUNCE_CHECKS, зависит не только от характеристик коммутатора, но также от скорости процессора и времени выполнения остального кода вашего основного вида. Он также занят ожиданием - без другого блокирующего вызова в основном цикле загрузка ЦП достигнет 100%, что приведет к голоданию других процессов, что приведет к вялости системы в целом. Если вы хотите иметь возможность выполнять другую работу во время отслеживания нажатия кнопки, может быть лучше использовать отдельный поток с вызовом usleep(), чтобы свести нагрузку на ЦП к минимуму. - person Clifford; 01.05.2013
comment
Я абсолютно согласен с хрупкой природой значения BUTTON_DEBOUNCE_CHECKS и сказал об этом как в тексте, так и в коде. Тем не менее, я чувствовал, что лучше избегать слишком длинных дискуссий о таймерах, прерываниях и планировании для тех, кто только изучает устранение дребезга. Я не согласен с кодом, занятым ожиданием. Ничто не мешает остальной части кода уступать или спать. Как написано, она высосет все такты процессора только если это чисто кооперативная многозадачная ОС или других задач нет. Я недостаточно знаком с Raspberry Pi, чтобы знать, какую ОСРВ он использует (если есть). - person jerry; 02.05.2013
comment
@Clifford, что касается usleep(), выгода будет значительно уменьшена или устранена, если нет других выполняемых задач (хорошо, это может помочь потреблять мощность и связанное с этим выделение тепла в некоторых системах). Это очень простая тестовая программа, ОП может не планировать когда-либо расширять ее для работы с другими задачами. Это определенно заблокирует вызывающий поток, хотя и более вежливо, чем грубое ожидание вращения. Даже если вы сделаете это в отдельном потоке, ваш основной поток все равно должен будет уступить или заснуть, что, как я уже сказал, уже можно сделать. Кстати, означает ли это, что ОС Raspberry Pi совместима с POSIX? - person jerry; 02.05.2013
comment
@Marmstrong Если ваша кнопка очень асимметрична, вы можете использовать разную длину отскока для каждого направления. У аппаратного обеспечения может быть такая же проблема с поиском подходящего компромисса между откликом и устранением дребезга. Он также занимает место на плате, влияет на стоимость единицы продукции и менее гибок. В отличие от программного обеспечения, вы не можете удаленно изменить аппаратное обеспечение [ну, может быть, FPGA, если учитывать это аппаратное обеспечение :)]. Тем не менее, это снимает нагрузку с вашего MCU, и вы даже можете поместить кнопку на контакт прерывания (если таковой имеется), что может позволить вам перейти в режим пониженного энергопотребления и возобновить работу при нажатии кнопки. - person jerry; 02.05.2013
comment
@Jerry Спасибо за постоянную помощь. Что касается вашего комментария о расширении его для работы с другими задачами, я планирую использовать эту простую программу в качестве трамплина (или, может быть, что-то более медленное) для более продвинутых настроек. Так что не стесняйтесь говорить о более сложных функциях. Я слышал о таймерах и прерываниях только по названию. Знаете ли вы какие-либо хорошие ресурсы, которые охватывают эти темы. Я планирую изучить как аппаратные, так и программные решения (прошлой ночью я уже потратил 4 часа на изучение аппаратных решений). Спасибо еще раз. - person Marmstrong; 02.05.2013
comment
@Jerry: RaspberryPi — это компьютер на базе ARM11, способный работать под управлением Linux. Наиболее часто используемая ОС (используемая для предполагаемого образовательного рынка и рекомендованная фондом) — это производный от Debian дистрибутив под названием Raspbian. Он также будет работать под управлением Debian (с плавающей запятой), Arch Linux и RiscOS. На форуме RPi есть группа по работе с «голым железом», где ясно, что на некоторых из них, среди прочего, также работает FreeRTOS. raspberrypi.org - person Clifford; 02.05.2013

Ваша логика верна, и это сработало бы, если бы кнопки были идеальными. Но это не так. Вы должны устранить дребезг сигнала кнопки. Два метода для достижения этого (лучше всего работают в сочетании):

I. Добавьте конденсатор между двумя контактами кнопки (или попробуйте более сложная схема подавления дребезга кнопок), и/или

II. использовать программное устранение дребезга (псевдо-C):

while (1) {
    while (!button_pressed)
        ;

    printf("Button pressed!\n");


    while (elapsed_time < offset)
        ;
}

и т.п.

Изменить: как указал @jerry, вышеприведенное не работает «правильно», когда кнопка удерживается. Вот несколько более профессионального кода фрагменты, которые можно использовать для выполнения всех требований.

person Community    schedule 30.04.2013
comment
Если кнопку удерживать, этот код будет бесконечно повторять printf - person jerry; 30.04.2013
comment
@джерри Верно. Добавил это, а также ссылку на лучшие альтернативы. - person ; 30.04.2013
comment
Если вы измените while (elapsed_time < offset) на while (elapsed_time < offset || button_pressed), это решит эту конкретную проблему. - person jerry; 01.05.2013

Следующая функция опрашивает кнопку с номинальным интервалом в 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 ) ;
    }

Если у вас есть доступ к цифровому запоминающему осциллографу, вы можете проверить сигнал переключателя напрямую, чтобы точно увидеть, как выглядит дребезг переключателя. Это может помочь вам понять проблему, а также адаптировать подавление дребезга к характеристикам вашего конкретного коммутатора.

person Clifford    schedule 30.04.2013
comment
Вызов usleep(1000) должен быть внутри цикла while. В противном случае вы не собираетесь делать много debouncing. - person jerry; 01.05.2013