Урок 6. Обработка дребезга контактов кнопки. Интерфейс связи между программными блоками.

Arduino UNO R3

В этом уроке научимся обрабатывать сигнал кнопки для устранения дребезга контактов.

Предыдущий урок     Список уроков     Следующий урок

В прошлом уроке мы написали простую программу для управления светодиодом с помощью кнопки. Нажата – светодиод светится, отжата - не светится. Наверное, вы решили, что кнопка очень простой компонент и работать с ней несложно. Считали состояние кнопки – что-то сделали. Но давайте напишем другую программу управления светодиодом.

 

Программа управления светодиодом.

Программа должна при каждом нажатии кнопки менять состояние светодиода, т.е. включать и выключать его. В этой программе надо:

  • считать состояние кнопки;
  • сравнить его с предыдущим состоянием;
  • если предыдущее состояние было отжата, а текущее состояние нажата – инвертировать состояние светодиода.

Т.е. программа должна отлавливать фронт кнопки или перепад состояния сигнала. Есть два фронта сигнала:

  • с высокого состояния в низкое ( --_ );
  • с низкого состояния в высокое (_--).

Нажатие кнопки будет соответствовать перепаду сигнала из высокого в низкое. Это событие мы и должны выделить.

/*  Программа sketch_6_1 урока 6
 *  На каждое нажатие кнопки инвертирует состояние светодиода
 *  Работает неправильно из-за дребезга контактов */
  
#define LED_PIN 13     // номер вывода светодиода равен 13
#define BUTTON_PIN 12  // номер вывода кнопки равен 12

boolean buttonState;      // состояние кнопки
boolean buttonPrevState;  // предыдущее состояние кнопки
boolean ledState;         // состояние светодиода

void setup() {
  pinMode(LED_PIN, OUTPUT);        // определяем вывод 13 (светодиод) как выход
  pinMode(BUTTON_PIN, INPUT_PULLUP);  // определяем вывод 12 (кнопка) как вход
}

void loop() {

  buttonState= digitalRead(BUTTON_PIN); // записываем состояние кнопки в переменную buttonState
 
  if ( (buttonPrevState == HIGH) && (buttonState == LOW) ) {
   
    // предыдущее состояние кнопки - отжата, а текущее - нажата
    ledState= ! ledState;             // инверсия состояния светодиода
    digitalWrite(LED_PIN, ledState);  // запись состояния светодиода из переменной нв выход   
  }
 
  buttonPrevState= buttonState;         // предыдущее состояние кнопки = текущему
}

Выделение события происходит в конструкции

if ( (buttonPrevState == HIGH) && (buttonState == LOW) )

&& это логическое И, производимое над условными выражениями. В результате его применения будет выработано условие истинно, если оба выражения истинны. В данном случае блок оператора if будет выполнен, если одновременно предыдущее состояние кнопки = HIGH и текущее состояние кнопки = LOW. Т.е. если кнопка была отжата в предыдущей проверке, а  сейчас нажата.

По этому условию инвертируется состояние светодиода.

В принципе, несложная программа. Но загрузите ее в плату Ардуино и проверьте, как она работает.

Неправильно она работает. Иногда, на нажатие кнопки, она инвертирует состояние светодиода, иногда нет. Часто светодиод мерцает в момент нажатия. Происходит это потому, что сигнал с кнопки совсем не такой, как мы его представляем.

 

Дребезг контактов кнопки.

Контакты кнопки это механические предметы. При замыкании они отскакивают друг от друга, касаются друг друга неровностями, поверхностями покрытыми окислами, грязью и т.п. Все это приводит к переходному процессу, называемому дребезгом контактов. Диаграмма сигнала кнопки при замыкании и размыкании выглядит примерно так.

Дребезг контактов

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

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

Надежная программа должна обязательно обрабатывать сигналы с  кнопок и механических датчиков.

Как бороться с дребезгом контактов?

Существует несколько способов обработки дребезга контактов. Описанный ниже – один из самых надежных. Еще один способ я опишу в следующих уроках.

Так вот. Решение достаточно очевидно. Надо не реагировать на частое переключение сигнала. Не может кнопка быть нажата на время, например,  0,0001 сек. Значит это дребезг. Надо дождаться, когда в течение определенного времени состояние кнопки будет стабильным, и только тогда принимать решение. А на частые переключения сигнала не реагировать.

 

Программа устранения дребезга контактов кнопки.

Давайте напишем такую программу. Чтобы быть ближе к практическому программированию, выделим в программе блоки и постараемся правильно оформить интерфейс между ними.

Существуют готовые функции обработки сигналов с кнопок. Вызываешь функцию с номером вывода в качестве аргумента, и она возвращает состояние вывода, “очищенное” от дребезга. Но операция устранения дребезга занимает значительное время, не меньше длительности переходного процесса, как правило, 10 мс. И все это время программа будет ждать результата работы функции. А кнопок может быть несколько. Да и программе, кроме чтения состояния кнопок, еще что-то надо делать, и не все процессы можно надолго прерывать.

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

Выделим обработку сигнала кнопки в отдельный программный блок. Поставим условие, что этот блок должен регулярно вызываться с периодом, например, 2 мс. Для связи с другими программными модулями создадим глобальные переменные, определяющие состояние кнопки. Таким образом, сигнал кнопки постоянно будет обрабатываться в параллельном процессе, а в любой части программы можно узнать о состоянии кнопки, проверив эти переменные.

Программа состоит из двух основных блоков в бесконечном цикле loop():

  • блок обработки сигнала кнопки;
  • блок управления светодиодом.

Что нам может потребоваться в результате обработки кнопки. Какие переменные создавать. Как правило, необходимы только два признака:

  • Один показывает  текущее состояние кнопки (нажата или отжата).
  • Второй сообщает о том, что кнопка была нажата (был перепад сигнала с высокого уровня в низкий).

Заводим глобальные переменные. Глобальные потому что, неизвестно в какой части программы мы захотим контролировать состояние кнопки.

boolean flagPress= false;    // признак кнопка в нажатом состоянии
boolean flagClick= false;    // признак нажатия кнопки (фронт)

Блок обработки сигнала кнопки мы вызываем с периодом 2 мс. Для отсчета времени устойчивого состояния сигнала необходим счетчик состояния кнопки и константа, задающее это время.

byte  buttonCount= 0;        // счетчик подтверждений состояния кнопки  
#define TIME_BUTTON 12       // время устойчивого состояния кнопки (* 2 мс)

Функции программного блока заключаются в том, что он вырабатывает признаки:

  • flagPress= true, если кнопка нажата;
  • flagPress= false, если кнопка отжата;
  • flagClick= true, если было событие – кнопка была нажата. Этот признак должен сбрасываться после обработки события. В блоке обработки он может только устанавливаться.

Если вызывать такой блок регулярно с периодом 2 мс, то признаки будут соответствовать текущему состоянию кнопки. Их могут использовать другие блоки программы в любом месте. Получается параллельное выполнение задач.

Вот скетч программы, построенной по такому принципу.

/*  Программа sketch_6_2 урока 6
 *  На каждое нажатие кнопки инвертирует состояние светодиода */

#define LED_PIN 13     // номер вывода светодиода равен 13
#define BUTTON_PIN 12  // номер вывода кнопки равен 12

// переменные и константы для обработки сигнала кнопки
boolean flagPress= false;    // признак кнопка в нажатом состоянии
boolean flagClick= false;    // признак нажатия кнопки (фронт)
byte  buttonCount= 0;        // счетчик подтверждений состояния кнопки  
#define TIME_BUTTON 12       // время устойчивого состояния кнопки (* 2 мс) 

boolean ledState;            // переменная состояния светодиода
  
void setup() {
  pinMode(LED_PIN, OUTPUT);        // определяем вывод 13 (светодиод) как выход
  pinMode(BUTTON_PIN, INPUT_PULLUP);  // определяем вывод 12 (кнопка) как вход
}

// бесконечный цикл с периодом 2 мс
void loop() {

/* блок обработки сигнала кнопки
 * при нажатой кнопке flagPress= true
 * при отжатой кнопке flagPress= false
 * при нажатии на кнопку flagClick= true */

  if ( flagPress == (! digitalRead(BUTTON_PIN)) ) {
     // признак flagPress = текущему состоянию кнопки
     // (инверсия т.к. активное состояние кнопки LOW)
     // т.е. состояние кнопки осталось прежним
     buttonCount= 0;  // сброс счетчика подтверждений состояния кнопки
  }
  else {
     // признак flagPress не = текущему состоянию кнопки
     // состояние кнопки изменилось
     buttonCount++;   // +1 к счетчику состояния кнопки

     if ( buttonCount >= TIME_BUTTON ) {
      // состояние кнопки не мянялось в течение заданного времени
      // состояние кнопки стало устойчивым
      flagPress= ! flagPress; // инверсия признака состояния
buttonCount= 0;  // сброс счетчика подтверждений состояния кнопки

      if ( flagPress == true ) flagClick= true; // признак фронта кнопки на нажатие     
     }   
  }

 
  // блок управления светодиодом
  if ( flagClick == true ) {
    // было нажатие кнопки
    flagClick= false;       // сброс признака фронта кнопки
    ledState= ! ledState;   // инверсия состояние светодиода
    digitalWrite(LED_PIN, ledState);  // вывод состояния светодиода   
  }

  delay(2);  // задержка на 2 мс
}

В блоке обработки состояния кнопки происходит следующее:

  • Если текущее состояние кнопки соответствует признаку flagPress, то ничего не делается. Только сбрасывается счетчик подтверждения состояния.
  • Если состояния кнопки и признака различны, то счетчик начинает считать время подтверждения. Любой возврат к равенству состояния кнопки и признака сбрасывает счетчик.
  • Если состояние сигнала устойчивое в течение количества циклов, определяемого константой TIME_BUTTON, то признак состояния кнопки flagPress инвертируется.
  • При этом формируется признак фронта сигнала flagClick, если кнопка стала нажатой. На отжатие кнопки этот признак не вырабатывается.

Блок управления светодиодом, думаю, пояснять не надо.

Блоки включены в бесконечный цикл с периодом 2 мс. Это утверждение не совсем корректно, но давайте пока его не обсуждать. Главное, что блок обработки сигнала кнопки вызывается регулярно с периодом примерно 2 мс.

Загрузите программу в контроллер Ардуино, проверьте, что она работает устойчиво. Ложных срабатываний не возникает.

В константе TIME_BUTTON задано время устойчивого состояния сигнала кнопки – 24 мс. Я рекомендую выбирать это время в пределах 20-30 мс.

Для проверки алгоритма обработки сигнала увеличьте значение константы TIME_BUTTON до 250 (время 500 мс). Увидите, что при быстром нажатии (менее 0,5 сек) светодиод не меняет своего состояния. Т.е. алгоритм подтверждения устойчивого состояния работает правильно.

 

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

Предыдущий урок     Список уроков     Следующий урок

80 комментариев на «Урок 6. Обработка дребезга контактов кнопки. Интерфейс связи между программными блоками.»

  1. начинаю изучать программирование контроллеров, этот материал мне подходит, а вопросы для уточнения можно задавать?

  2. Здравствуйте,а как изменить программу,чтобы светодиод в состоянии HIGH мигал?

  3. в первом примере buttonPrevState не инициализирован явно, но уже используется. Поправьте 🙂

    • По второму примеру.
      Все-таки использование функции millis() из встроенного в IDE примера debounce, выглядит привлекательнее «тупой» задержки delay().

      • В принципе Вы правы, но при объявлении переменные инициализируются нулями.
        Что касается «тупой задержки delay()» из второго примера. Задержка функцией delay используется для простой, понятной реализации периодического вызова программного блока. Другие способы еще не были представлены в уроках. Начиная с урока 10 подобные блоки вызываются в обработчике прерывания по таймеру.

  4. Эдуард,
    Большое вам спасибо за то, что вы делаете! Мало где информация подается под таким правильным «методическим» углом: не только изучить железо и как копировать решения на нем, но и выработать общие подходы к программированию МК, научиться самому выбирать и рассчитывать компоненты. Надеюсь у вас еще много есть чем поделиться! Можно бы потом из этого и книгу собрать было.

  5. Только вникаю в азы программирования, поэтому вопросы могут показаться смешными))..
    Разве в строке программы
    #define TIME_BUTTON 12
    не происходит присвоение константе TIME_BUTTON вывода под номером 12, ведь строка
    #define BUTTON_PIN 12
    та же самая, только имя константы другое

    • Строка #define TIME_BUTTON 12 означает, что везде где в тексте программы встретится имя TIME_BUTTON компилятор тупо заменит его на символы 12. А применять его к номеру вывода или к времени это решаете Вы. Просто так совпало что и номер вывода 12 и константа времени 12. В предыдущем уроке #define описывается подробно.

      • да да, я понял, перечитал 3 раза и врубился, был невнимателен, TIME_BUTTON это счетчик количества времени включенного состояния, спасибо

  6. И еще вопрос, а в блоке управления светодиодом разве не нужно вместе с flagClick= false, также обнулить переменную flagPress?
    flagPress = false
    Иначе выходить должно, что при отжатии кнопки светодиод снова переключится ?

    • Нет. Почитайте внимательно выдержку из урока:
      Функции программного блока заключаются в том, что он вырабатывает признаки:
      •flagPress= true, если кнопка нажата;
      •flagPress= false, если кнопка отжата;
      •flagClick= true, если было событие – кнопка была нажата. Этот признак должен сбрасываться после обработки события. В блоке обработки он может только устанавливаться.

      flagPress показывает текущее состояние, а flagClick — признак того, что было событие. Он запоминает его.

      • Спасибо, Эдуард, я проверил еще раз и увидел строку
        if ( flagPress == true ) flagClick= true

      • Будет ли правильным вот такой код для избежания дребезжания ?

        // мигаем диодом от кнопки.
        //Включение и выключение по отдельному нажатию

        #define PIN_DIOD 13
        #define PIN_KNOPKA 2
        boolean last_button = LOW;// предыдущее состояние
        boolean Now = LOW; // значение для светодиода
        boolean now_button = LOW; // текущее состояние

        // настройка, предустановка
        void setup()
        {
        pinMode (PIN_DIOD, OUTPUT);
        pinMode (PIN_KNOPKA, INPUT);
        }

        // бесконечный цикл
        void loop()

        {
        now_button = digitalRead(PIN_KNOPKA); // читаем состояние кнопки

        if (last_button == LOW && now_button == HIGH) // если кнопка была в лоу и стала в хай
        {
        Now = ! Now; // инвертируем значение на светодиоде

        }
        last_button=digitalRead(PIN_KNOPKA); // читаем стостояние кнопки
        delay(2);
        digitalWrite(PIN_DIOD, Now);//записываем значение в порт диода
        }

        • Думаю, правильно работать не будет. Программа выловит первый же фронт и сработает на него.

  7. Здравствуйте! Не понятно откуда берется значение переменной ledState в блоке упр. светодиодом «ledState= ! ledState; // инверсия состояние светодиода». Ведь она только объявлена как глобальная переменная и нигде в программе не установлено ее начальное значение.

    • Здравствуйте!
      Во первых, при инициализации переменных их значение равно 0.
      Во вторых, в данной программе нам не важно начальное состояние светодиода. При каждом нажатии кнопки он должен менять свое состояние.

      • 1)Все равно не понял, почему каким-то переменным задаётся нулевое значение, а каким -то нет? И тем более не понятно почему если не задаем никакого значения то по умолчанию переменным присваивается нулевое значение, зачем тогда им задавать «принудительно» нулевое значение (false)? Или есть какие-то правила?

        • Формально Вы правы. Но, как правило, все компиляторы инициализируют не заданные переменные 0. Некоторые переменные не надо инициализировать по алгоритму. Задавайте при инициализации. Это дисциплинирует, хуже не будет.

  8. 2)Если я правильно понял то 2-й пример работает сл.образом: после включения LED не горит; при нажатии на кнопку через 24 мс LED загорается; при отжатии LED продолжает гореть; при повторном нажатии через 24 мс LED гаснет и т.д. Это так?

    • В принципе все правильно. Светодиод меняет свое состояние на каждое нажатие кнопки. Именно нажатие, не отжатие. А то, что состояние меняется через 24 мс, не столь важно. Это время на обработку сигнала кнопки. Не думаю, что его в реальной программе надо учитывать.

  9. 3)Почему в выражении нет фигурных скобок: if ( flagPress == true ) flagClick= true; или при построении простого выражения они не нужны?

  10. Здравствуйте!
    Если в вашем цикле loop мы его слегка изменим, станет ли программа работать не правильно?

    void loop() {
    if ( flagPress == (! digitalRead(BUTTON_PIN)) ) {
    buttonCount= 0;
    }

    else {
    buttonCount++;

    if ( buttonCount >= TIME_BUTTON ) {
    buttonCount= 0;
    flagClick= true;
    }
    }

    if ( flagClick == true ) {
    flagClick= false;
    ledState= ! ledState;
    digitalWrite(LED_PIN, ledState);
    }

    delay(2);
    }

    а именно избавимся от строчки flagPress= ! flagPress; и не станем выполнять условие if ( flagPress == true ) нужное для присвения flagClick= true;. Выполним присвоение flagClick= true после того как выполнится условие buttonCount >= TIME_BUTTON.

    • Я боюсь ошибиться. Думаю, тогда flagClick будет вырабатываться и на нажатие, и на отжатие кнопки.

      • Извиняюсь, поторопился и ошибся. Да программа будет работать не правильно, но мне кажется, что ошибка будет выглядеть так: после того как кнопка нажата, а дребезг устранен сработает flagClick= true и произойдет инвертация значения светодиода. Однако в связи с тем что пока палец находится на кнопке условие flagPress == (! digitalRead(BUTTON_PIN) будет продолжать отправлять нас на ветку else, тем самым каждую последующую итерацию цикла loop, будет увеличиваться счетчик buttonCount. И пока палец на кнопке, каждые 12 итераций цикла loop с задержкой по 2 мс каждый (т е каждые 24 мс), будет также происходить инвертация значения светодиода. Таким образом пока кнопка нажата светодиод будет моргать, до тех пор пока палец не отпустит кнопку и светодиод не сохранит своего текущего значения, которое может быть как включенным, так и выключенным. Большое спасибо, за интересную статью и ответ.

  11. Встречал много реализаций гашения дребезга, но Ваш код самый краткий и элегантный !
    Спасибо за примеры!

  12. Здравствуйте!
    Как я понимаю, в программе предыдущего урока тоже будет
    наблюдаться явление дребезга контактов?

  13. Здравствуйте!
    А в программе из предыдущего урока тоже будет проявляться дребезг контактов?

    • В программе из предыдущего устройства дребезг мы не замечаем. В момент коммутации конечно будет дребезг, но у нас отображается состояние кнопки, а не инверсия светодиода на каждое нажатие.

  14. Далеко не сразу понял работу скетча, я начинающий…
    Я правильно понимаю, что если тупо задать время на отработку дребезга, скажем 20 мс (не используя алгоритмы устранения дребезга) и проверку состояния, то 20 мс программа будет висеть? Я хочу подключить шаговый двигатель по протоколу STEP/DIR. В схеме должны быть двигатель, кнопки, ЖК экран для установки скорости и т.п.. Я правильно понимаю, что если программа будет висеть при каждой обработке кнопки, а их предполагается несколько, то и импульсы STEP не будут поступать на шаговый двигатель и прочие функции выполняться не будут во время?

    • Анатолий, это один из начальных уроков. В нем демонстрируется принцип устранения дребезга. Я пишу, что обработка кнопки должна вызываться примерно каждые 2 мс. Задержка 2 мс реализуется функцией delay(), в которой действительно программа зависает.
      Дойдите до 10 урока. В нем совсем другая реализация обработки кнопок параллельным процессом. Там программа не зависает. В рабочей программе надо использовать обработку кнопок в прерывании по таймеру. Это совсем не сложно. Код программы будет даже меньше, чем в этом уроке.

  15. Спасибо, Эдуард, я обязательно прочту все уроки. Однако много вопросов. Один из них я озвучил. На 2 ли миллисекунды или на 20, не важно. Протокол STEP/DIR не выполняется в это время?
    Или я не правильно понимаю?

    • При реализации задач параллельными процессами (начиная с урока 10) все выполняется одновременно. В контроллере элемента Пельтье (урок 36) столько всего выполняется вместе с обработкой кнопок. Кстати, есть уроки и по STEP/DIR драйверам.

  16. В программе sketch_6_2 я никак не пойму переменные boolean, поэтому не могу двигаться дальше. Условие if ( flagPress == (! digitalRead(BUTTON_PIN)) ) мне почему-то кажется не выполнимым. Ведь переменные boolean могут принимать ТОЛЬКО значения true или false. Я пытаюсь «думать» как машина:
    объявлена некоторая переменная boolean flagPress= false;
    Далее имеем условие if ( flagPress == (! digitalRead(BUTTON_PIN)) )
    которое означает, что если ранее объявленной переменной flagPress соответствует значение функции ! digitalRead(BUTTON_PIN)), то и тд..
    Смотрим что за функция: digitalRead — функция чтения состояния контакта 12 (а к нему подключена кнопка). В момент подачи питания на кнопку скорее всего никто не нажимал, стало быть на контакте высокий уровень HIGHT, по условию мы его инвертируем на LOW. Значит нашей переменной flagPress соответствует значение LOW. Мало того, что кнопку никто не нажимал, а получается, что нажали (именно это же происходит при нажатии на кнопку, высокий уровень меняется на низкий), так ещё и значение LOW никаким боком не относится к двум возможным значениям переменной boolean, так как она может принимать ТОЛЬКО значения true или false. Как это понять?

    • Переменные типа boolean занимают в памяти один байт. Состояние false отображается нулевым значением байта, любое другое значение — true. Поэтому HIGH и LOW могут использоваться как значения типов boolean.

      А что касается if ( flagPress == (! digitalRead(BUTTON_PIN)) ).
      Это проверка — признак состояния кнопки определенный в программе ранее равен или не равен текущему состоянию кнопки. Знак ! инвертирует результат digitalRead(BUTTON_PIN), потому что нажатая кнопка у признака отображается true, а реальный сигнал нажатой кнопки низкого уровня.

      • А-а, вон оно что… Ну это меняет дело! Большое спасибо, разбираюсь дальше. Только почему-то про такое положение с булевой переменной нигде не пишут…

  17. В дополнение к вышесказанному:

    В сети «блуждает» такой пример скетча, он не очень правильный, как я понимаю, и подвешивает программу на 100мс, но понятие булевой переменной в нем не очень нарушается, как мне кажется:

    int LEDpin = 5; // Светодиод на входе 5
    int switchPin = 13; // выключатель на порту 13, замыкает на землю

    boolean running = false;

    void setup()
    {
    pinMode(LEDpin, OUTPUT);
    pinMode(switchPin, INPUT);
    digitalWrite(switchPin, HIGH); // включаем подтягивающий резистор
    }

    void loop()
    {
    if (digitalRead(switchPin) == LOW)
    { // выключатель нажат, т.к. подтягивающий резистор будет давайть HIGH на входе, если не замкнут напрямую на землю
    delay(100); // ждем 0.1сек
    running = !running; // меняем значение булевой переменной
    digitalWrite(LEDpin, running) // включаем или выключаем светодиод.
    }
    }

    Хотя в конце тоже не понятно, что записывается на контакт 5. Значение true что ли? И чему это значение соответствует в вольтах?

  18. Эдуард, пытаюсь далее понять логику…..что-то не получается.
    Почему условие if ( flagPress == (! digitalRead(BUTTON_PIN)) ) мне не кажется условием. Мы включили прибор (ведь все начинается с начала), на кнопку никто не нажимал (очень трудно представить себе, что на кнопку нажали раньше включения устройства). Оператор IF означает, что если условие выполняется, то далее выполняются операторы расположенные непосредственно за оператором IF, до оператора else, правильно? Но у нас нет условия, у нас идет присвоение переменной flagPress значения функции (! digitalRead(BUTTON_PIN)). С чем сравнивает оператор IF это присвоение, с исходным значением переменной? Но в описании оператора IF везде в справочниках указывается, что условие заключено в скобках оператора. А у нас там нет никакого условия…

    • Я не совсем понял вопрос. Я уже подзабыл эту программу, возможно, ошибусь, но алгоритм примерно такой.
      У нас есть flagPress – признак состояния кнопки, вычисленный программой. Он может не соответствовать текущему реальному состоянию кнопки, т.к. на его определение требуется время.

      У нас есть функция, которой мы определяем реальное состояние кнопки в данный момент digitalRead(BUTTON_PIN). Это состояние с дребезгом, который мы хотим устранить, отфильтровать.

      Мы сравниваем эти значения с учетом того, что реальный сигнал инверсный (когда кнопка нажата = 0).

      Если они равны, то мы ничего не делаем. Зачем что-то делать.

      Если не равны, то мы начинаем отсчитывать время
      buttonCount++; // +1 к счетчику состояния кнопки
      if ( buttonCount >= TIME_BUTTON ) {
      По прошествие определенного времени, если они все это время остаются не равны, то мы меняем flagPress.

      По моему все логично.

  19. А-а, дело в том, что используется оператор не присвоения =, а оператор сравнения == Верно?
    Получается, что переменной было что-то присвоено, а оператор IF проверяет, соответствует ли это присвоение истине или нет. Правильно?

  20. Отлично, все вроде понял, все сходится :)) А подскажите ещё, пожалуйста, в литературе пишется, что код программы выполняется «очень быстро», но это весьма расплывчато. А вот как быстро? Наверное есть какое-то среднее время выполнения оператора в программе. Я, как писал, хочу использовать шаговый двигатель с драйвером TB6600, который работает на частоте 200 кГЦ, соответственно с периодом 5 мкс. Я ранее спрашивал, в каждом ли такте STEP проходит весь цикл loop? Ну, если я хочу в каждом такте Step менять длительность периода (для разгона, например), то остро встает вопрос обработки состояния кнопок, выведения информации на ЖК индикатор и т.п., а это зависит от того, как быстро выполяется код программы…

    • К сожалению код программы не очень быстро выполняется. Быстродействия всегда не хватает.
      У каждой команды микроконтроллера есть определенное время выполнения. Но на языке высокого уровня оператор реализуется несколькими командами низкого уровня. Вычислительные задачи могут требовать несколько сотен команд. Поэтому сказать определенно время выполнения операторов C нельзя. Более того время вычислительных операций зависит еще от конкретных данных.
      Можно просто измерить время выполнения команд, функций, программных блоков. На форуме сайта у меня есть тема об измерении времени выполнения блоков кода. Там есть время выполнения некоторых стандартных функций Ардуино. Например digitalWrite() или digitalReed() выполняются за 4-6 мкс. Можно уменьшить время выпонения операций прямым обращением к регистрам микроконтроллера.

      Что касается шагового двигателя. Вы явно напутали про частоту 200 кГц. Возможно это частота внутреннего тактирования драйвера. Вам важна частота шагов или сигнала STEP. Но она не может быть 5 мкс. Я участвовал в разработке такого двигателя. Но его STEP/DIR драйвером не закрутишь. Посчитайте какая скорость вращения будет.
      Реальное время цикла управления на Aduino UNO, я думаю, не менее 100 мкс.

      И еще. Анатолий, если у Вас какие-то объемные вопросы, то открывайте тему на форуме сайта. Там удобнее давать материал и кто-то еще поделится своим опытом. Чего-то в уроках я не досказал, расскажу на форуме.

  21. Для чего в конце задержка delay(2)?

    Не понятно. Ведь от этого теряется весь смысл.

    Если использовать delay() тогда можно просто поставить его вначале первого скетча.

    buttonState= digitalRead(BUTTON_PIN); // записываем состояние кнопки в переменную buttonState
    delay(3); // и дребезга тоже не будет.
    Проверял.
    Ну а за скетч распознавания фронтов — спасибо.
    Действительно красивый код.

    • Это один из начальных уроков. В нем демонстрируется метод. В последующих уроках кнопки обрабатываются в прерывании по таймеру.

      Цитирую из урока:

      Блоки включены в бесконечный цикл с периодом 2 мс. Это утверждение не совсем корректно, но давайте пока его не обсуждать. Главное, что блок обработки сигнала кнопки вызывается регулярно с периодом примерно 2 мс.

  22. Здравствуйте, Эдуард! Большое спасибо за Ваши уроки! Поясните пожалуйста, есть ли принципиальное различие между операторами HIGH/LOW и true/falce

    • Здравствуйте!
      Размер false, true, LOW, HIGH один байт.
      false, LOW передается байтом с нулевым значением.
      true, HIGH это байт с любым ненулевым значением.
      Т.е. в принципе одно и тоже false, LOW и 0.
      Также одно и тоже true, HIGH, 1, 2 …, 255
      Бесконечный цикл можно создать
      while(true) или while(1).

      Но хороший стиль программирования предполагает в логических операциях использовать false и true, в функциях ввода вывода LOW и HIGH.

  23. Здравствуйте, Эдуард.
    Я только начинаю осваивать программирование, при изучении по вашим урокам, возник вопрос, в Программе sketch_6_1 урока 6 ,не могу понять как микроконтроллер понимает, что это предыдущее состояние кнопки и она отжата в этой строчке
    if ( (buttonPrevState == HIGH) && (buttonState == LOW) ) ?

    • Здравствуйте!
      buttonPrevState — переменная, в которой содержится предыдущее состояние кнопки,
      buttonState — переменная текущего состояния кнопки.

      if ( (buttonPrevState == HIGH) && (buttonState == LOW) )
      означает если предыдущее состояние было высоким, а текущее низкое, то выделен момент нажатия кнопки.

  24. Эдуард, я правильно понимаю, в начале программы мы создаем переменные buttonPrevState и buttonState, и им задаются нулевые значения.
    Когда программа начинает обрабатывает оператор if

    if ( (buttonPrevState == HIGH) && (buttonState == LOW) )

    buttonPrevState рано LOW
    buttonState равно HIGH- так как считывается состоянии кнопки.
    И при проверке условия на истину, не проходит,и выполняется далее строчка
    buttonPrevState= buttonState;
    В которой в переменную buttonPrevState записывает HIGH.
    Я правильно понял выполнение программы?

    • Да. Но вы описали только начальный алгоритм выполнения.

      Оператор buttonPrevState=buttonState; выполняется в каждом цикле. Получается, что buttonPrevState это задержанное на один такт значение buttonState. Нарисуйте на бумаге сдвинутые диаграммы состояния нажатой и отжатой кнопки. Вы увидите, что только в момент нажатия buttonPrevState=HIGH и buttonState=LOW. Это состояние и выделяет алгоритм.

  25. Здравствуйте,
    Вопрос по первому коду в статье по переменной
    boolean buttonPrevState; // предыдущее состояние кнопки
    В if мы сразу сравниваем buttonPrevState == HIGH
    Но до этого мы переменной ничего не присвоили.
    При первом проходе цикла переменная buttonPrevState получается вообще не определена? Объясните как это работает

  26. Здравствуйте, Эдуард.
    Попытался применить Ваш метод гашения дребезга к энкодеру. Больше половины импульсов обрабатываются в неверном направлении, либо вообще пропускаются. В чём может заключаться проблема?

    • Здравствуйте. С какой частотой вы опрашиваете сигналы энкодера?
      У меня буквально вчера появилась задача, в которой требуется энкодер. Я реализовал ее сначала библиотекой Button, затем написал библиотеку, которая вычисляет скорость изменния положения энкодера.
      Если потерпите, я напишу урок на следующей неделе.
      А ваша ошибка, наверняка, заключается в том, что энкодер необходимо опрашивать достаточно часто. Сделайте 4 подтверждения и 250 мкс период прерывания функции scanState. Думаю, заработает.

      • Будет интересно ознакомиться с уроком!
        Частота опроса установлена 4 мс. Сам энкодер 24-разрядный. При этом, если вращать ручку медленно, то сигналы обрабатываются как положено.
        Я так понимаю, при такой частоте контроллер видит зажатые выводы в неправильной последовательности, верно?

        • Да, конечно. Посчитайте сами какая должна быть частота опроса. Я использую 250 мкс и 4 подтверждения.

  27. Добрый день.
    1.Подскажите пожалуйста
    Какая связь между числом 12 в коде:
    #define TIME_BUTTON 12
    и ( * 2 мс) в комментариях к нему:
    // время устойчивого состояния кнопки (* 2 мс)
    А это строка целиком:
    #define TIME_BUTTON 12 // время устойчивого состояния кнопки (* 2 мс)
    2. И какое отношение счетчик имеет к промежутку времени
    в этом коде: if ( buttonCount >= TIME_BUTTON )
    или я чего то не понял, объясните пожалуйста!
    3. И стоит ли применять в этом случае функцию: delay(2);
    Скорей всего автор применяет это для демонстрации программы анти дребезга?
    Зарание благодарен,и проститите ежли спросил глупость
    антидребезга

  28. Добрый день.
    В начале недели отправил вопросы, но куда то все исчезло.
    Попробую еще раз и по одному вопросу.
    Вот часть кода из этого скетча:
    «#define TIME_BUTTON 12 // время устойчивого состояния кнопки (* 2 мс)»
    То есть символическая константа «TIME_BUTTON» получает свое значение «12», и в комментарии к этой части » // время устойчивого состояния кнопки (* 2 мс)». Вопрос если «TIME_BUTTON» — «время устойчивого состояния кнопки», то тогда какое отношение значение «12» имеет к части комментария «(* 2 мс)»?
    Объясните пожалуйста!

    • Здравствуйте!
      Константе TIME_BUTTON вы присваиваете число 12. А сколько это будет в реальном времени?
      В комментариях я указываю, что каждая единица TIME_BUTTON соответствует 2 мс. Т.е. время подтверждения будет 12 * 2 = 24 мс.

  29. Добрый день.
    Да теперь понятно.
    Спасибо.
    Просто читать между строк, к сожалению не научился.
    Как бы там ни говорили, все люди в своем большинстве мыслят одинаково.
    Мыслительный процесс, как бы та программа, которая оперирует некими образами или шаблонами, с биологической точки зрения, логика поведения в своем большинстве однообразна, вот только образы различны. (Это я объясняю, как большинству все понятно, а мне нет.)
    Не сочтите мое обращение к Вам как «придирки», если мои дальнейшие вопросы Вы посчитаете не важными, так тому и быть.
    Вот часть описание этого урока:
    «Программа устранения дребезга контактов кнопки.
    Давайте напишем такую программу. Чтобы быть ближе к практическому программированию, …..»
    Теперь вопрос, пример кода для обработки сигнала кнопки, это практический вариант, или все-таки показательный, учебный пример?
    Дело в том, что Вы предлагаете :
    «Поэтому давайте стараться обрабатывать состояние кнопок параллельным процессом. Сделаем первый шаг к многозадачности.»
    Тогда почему в конце используете функцию «delay(x)», насколько я понимаю, в течение промежутка времени «x», ничего происходить не должно, это пауза, контролер просто ждет. Где тогда параллельный процесс?
    Извините, конечно, может я опять чего то не понял, ведь понять другого человека, иногда не меньший труд, чем донести до него информацию, как то так….

    • Здравствуйте!
      В уроке 6 это учебный пример, поясняющий принцип действия алгоритма.
      Начиная с урока 10, когда я рассказываю о прерывании по таймеру, такой алгоритм используется в библиотеке обработки сигналов кнопок Button.h.

  30. Друзья, вы уж извините, но сломала голову, догоняя вашу логику, и все-равно считаю, что в блоке цикла в первом же операторе ошибка..
    if ( flagPress == (!digitalRead(BUTTON_PIN)) ) — здесь вы должны проверить, что кнопка все еще не нажата. По умолчанию flagPress это false, что соответствует отжатому состоянию кнопки, т.е. LOW на 12м пине. В этот состоянии (не обратном!) вы должны делать сброс счетчика. Т.е. проверка должна быть без инверсии digitalRead(BUTTON_PIN).
    Однако программа успешно работает в обоих вариантах.
    Но Вы вводите в заблуждение..

    • Здравствуйте!
      У признака flagPress активное состояние true. Т.е. если кнопка нажата, то flagPress=true. У вывода при нажатой кнопке сигнал низкого уровня digitalRead(BUTTON_PIN) = LOW.
      Вы читайте комментарии к программным блокам и старайтесь рассуждать относительно их.
      if ( flagPress == (! digitalRead(BUTTON_PIN)) ) {
      // признак flagPress = текущему состоянию кнопки
      // (инверсия т.к. активное состояние кнопки LOW)
      // т.е. состояние кнопки осталось прежним

      Этот программный блок означает:
      Если состояние кнопки осталось прежним, то мы ничего не делаем, только сбрасываем счетчик подтверждений. Где здесь нарушена логика?

  31. Здравствуйте!
    Правильно ли я понимаю, когда я нажал на кнопку, программа занимает 24 мс чтобы определить это дребезг или нажатие?

  32. Здравствуйте!
    Поясните пожалуйста, если я использую две кнопки я нажал на первую кнопку и следом на вторую то может случиться что контроллер не увидит это срабатывание , то о каких 2 мс мы говорим?

    • Здравствуйте!
      Хоть 10 кнопок одновременно. Каждая кнопка обрабатывается своей функцией параллельно. В этом уроке пример больше учебный. Начиная с 10 урока будет обработка состояния кнопки в прерывании по таймеру. Там начнется действительно параллельная обработка сигналов.

  33. Здравствуйте!
    Эдуард, по моему пониманию программа которую выполняет микроконтроллер -это последовательное выполнение операций записанных в программный блок, или я что-то не понимаю?

    • Здравствуйте!
      Да, конечно. Но если каждые, например, 2 мс последовательно обрабатывать 10 кнопок, то для пользователя они будут обрабатываться одновременно.

  34. День добрый!
    Хочу поблагадарить за столь хорошие уроки!
    А мне для собственного развития и подтверждения понимания кода стало интересно вот что:
    В данном коде ожидается уверенный (неизменный) сигнал продолжительностью, примерно, 24 миллисекунды. То есть включение светодиода произойдет через время равное времени дребезга ПЛЮС 24мс. Но на рисунке показан дребезг контактов продолжитеньностью примерно 10 мс, к тому же в нем несколько раз меняется значение (пусть, например, 10 раз). То есть можно утверждать, что если сигнал неизменен на протяжении чуть больше, 10/10=1мс, то у нас уже нет дребезга?
    Или что бы не было конфликта в понимании с delay (2), то предположим, что у нас плохая кнопка и она дребезжит аж 100 мс. При этом происходит 20 переключений (ну, например, осцилографом померили). Тогда мы можем утверждать, что если у нас на протяжении 6 мс (чуть больше, чем 100/20, и при условии одинаковости импульсов при дребезге) не меняется значение- то дребезга уже нет. Тогда и светодиод чуть быстрее загорится.
    Повторюсь, мне интересно для собственного понимания кода. Мне ясно, что на самом деле разницы я не почувствую.

    • Здравствуйте!
      Все правильно: «То есть включение светодиода произойдет через время равное времени дребезга ПЛЮС 24мс.»
      Вы ведете речь о выборе времени подтверждения состояния сигнала. Да, его можно выбирать по разным критериям. В том числе и по быстродействию. Но никто не гарантирует длительность дребезга. Лучше выбирать время подтверждения с запасом. Контакт кнопки разболтаются, и дребезг станет дольше.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *