Урок 9. Подключаем к Arduino кнопку. Дребезг. Программное устранение дребезга

В предыдущих уроках мы использовали цифровые контакты Arduino в качестве выходов. Сейчам рассмотрим использование этих контактов в качестве входов, что позволит подключить, например, кнопку. Кнопка – это достаточно простое устройство, замыкающее и размыкающее электрическую сеть. Нажав на кнопку, мы подаем контроллеру сигнал, который затем приводит к каким-то действиям: включаются светодиоды, издаются звуки, запускаются моторы.
Чтобы подключить нормально разомкнутую кнопку к Arduino, можно поступить самым простым способом: один свободный проводник кнопки соединить с питанием или землёй, другой – с цифровым выводом Arduino (рис. 9.1). Но, это неправильно. В моменты, когда кнопка не замкнута, на цифровом выводе Ардуино будут появляться электромагнитные наводки, и из-за этого возможны ложные срабатывания.
Рис. 9.1. Схема соединений кнопки к выводу Arduino

Чтобы избежать наводок, цифровой вывод обычно подключают через достаточно большой резистор (5-10 кОм) либо к питанию, либо к земле. В первом случае это называется "схема с подтягивающим резистором" (рис. 10.2), во втором – "схема со стягивающим резистором" (рис. 10.3). Резисторы в обеих схемах используются для установки "значения по умолчанию" из входного контакта. В схеме с подтягивающим резистором это HIGH, в схеме со стягивающим резистором – LOW.
Рис. 9.2. Подсоединение кнопки к Arduino – схема с подтягивающим резистором
Рис. 9.3. Подсоединение кнопки к Arduino – схема со стягивающим резистором.

Все выводы платы Arduino имеют внутри микроконтроллера резисторы, подключенные к 5 В. Их можно программно включать или отключать от выводов. Сопротивление этих резисторов порядка 20-50 кОм. Программное включение в Arduino IDE осуществляется так:
pinMode(3, INPUT_PULLUP); // внутренний подтягивающий резистор 20кОм подключен
На плате Easy Module shield находятся две кнопки, подключенные к контактам Arduino D2 и D3. Они подключены к контактам Arduino по схеме "с подтягивающим резистором".
Напишем программу управления светодиодом с помощью кнопки. При нажатии кнопки светодиод "горит", при отпускании "гаснет". Кнопка D2 будет управлять синим светодиодом (D13), кнопка D3 – красным светодиодом (D12).
Приступаем к написанию скрипта в программе Snap4Arduino. Подключаемся к плате Arduino (см. урок 6). Теперь в поле размещения блоков добавляем из группы Управление блоки старта скрипта и бесконечного цикла:
Риc 9.4. Добавление блоков старта скрипта и бесконечного цикла.

Далее из группы Управления добавляем блок условия (если -- иначе). При выполнении условия, записанного в если, выполняются команды, вставленные сразу за если. В противном случае выполняются команды, вставленные за иначе.
Риc 9.5. Добавление блоков старта скрипта и бесконечного цикла.

Блок digital reading из группы Arduino осуществляет чтение состояния на выбранном контакте input. Для контактов D2, D3 (подключены к контактам Arduino по схеме "с подтягивающим резистором") при нажатии кнопки на входе присутствует значение 0. Это соответствует значению ложь для функции digital reading.
Риc 9.6. Добавление блока сравнения данных с входа Arduino c 0.

Теперь добавим в раздел если (истина) включение светодида, в раздел иначе (ложь) – выключение светодиода.
Риc 9.7. Скрипт готов.

Подключаемся к плате Arduino. Нажимаем на кнопку Подключиться к Arduino в группе Arduino и выбираем порт подключения.
Затем появляется окно процесса подключения.
При появлении данного окна необходимо нажать на кнопку RESET на плате Arduino!
После появления окна об удачном подключении можно запускать скрипт.
Запускаем (нажимаем на ̎флажок̎ из блока команд управления скриптом) и проверяем работу.
Скачать данный скрипт 09_01.xml можно по ссылке https://amegakit.ru/libraries/lib_Lab_Om-Robot.zip
Насколько удобно держать кнопку постоянно нажатой для свечения светодиода? Гораздо удобнее иметь возможность нажать кнопку один раз, чтобы включить светодиод, и нажав его еще раз – выключить.
Создадим такой скрипт. Введем дополнительную переменную, которая будет отвечать за состояние светодиода (включен/выключен) и состояние ее будет меняться на противоположное при каждом нажатии кнопки.
Переходим в группу Переменные и нажимаем на кнопук Обьявить переменную. Назовем ее statusLed и нажимаем кнопку OK.
Риc 9.8. Создание переменной.

Переменная statusLed появилась в группе Переменные. Мы можем ей присвоить начальное значение, выбираем блок Установить значение и устанавливаем переменной statusLed значение 0 (светодиод выключен).
Риc 9.9. Присвоить значение переменной statusLed.

Чтобы определить момент нажатия на кнопку, неоходимо ввести еще 2 переменные, в которых сохраняется текущее значение положения кнопки, и предыдущее. Если текущее отличается от предыдущего – это момент изменения состояния кнопки (нажатие или отжатие).
В непрерывном цикле присваем переменной buttonState значение, возвращаемое блоком digital reading. Далее проверяем изменилось ли состояние кнопки (блок if) и переменной buttonStatePrev значение переменной buttonState.
Риc 9.10. Проверка состояния кнопки в непрерывном цикле.

Добавляем необходимые блоки при выполнении условия определения нажатия кнопки:
- изменение значения переменной statusLed на противоположное;
- отправка значения переменной statusLed на цифровой контакт 13.
Риc 9.11. Скрипт управления переключением светодиода по кнопке.

Запускаем (нажимаем на ̎флажок̎ из блока команд управления скриптом) и проверяем работу.
Скачать данный скрипт 09_02.xml можно по ссылке https://amegakit.ru/libraries/lib_Lab_Om-Robot.zip

Создание скетча в программе Arduino IDE.

В Arduino IDE напишем скетч, устанавливающий состояние светодиода, подключенного к выводу 13 (синий на плате Easy Module shield) в зависимости от состояния кнопки, подключенной к контакту D2. Кнопка нажата – светодиод "горит", кнопка отжата – светодиод "потушен".
Сначала объявим переменные:
// Контакт 13 для подключения светодиода
int LED=13;
// Контакт 2 для подключения кнопки
int BUTTON=2;
// переменная текущего статуса кнопки buttonState
int buttonState=true;
// переменная предыдущего статуса кнопки buttonState
int buttonStatePrev=true;
// переменная статуса светодиода
int statusLed=LOW;


В setup() мы устанановим режимы работы для контактов Arduino:
// определяем вывод LED (светодиод) как выход
pinMode(LED, OUTPUT);
// определяем вывод BUTTON (кнопка) как вход
pinMode(BUTTON, INPUT);
В основном цикле производим опрос контакта подключения кнопки. При нажатии (приходящее значение LOW, а предыдущее HIGH) значение переменной statusLed на противоположное и отправляем его на вывод 13.
// считываем состояние BUTTON входа (кнопки)
buttonState = digitalRead(BUTTON);
// если нажатие с HIGH на LOW
if(buttonState == LOW && buttonStatePrev== HIGH) {
statusLed = 1 - statusLed;
// записываем состояние из statusLed на выход LED
digitalWrite(LED, statusLed);
}
buttonStatePrev = buttonState;

Листинг 9.1
// Контакт 13 для подключения светодиода
int LED=13;
// Контакт 2 для подключения кнопки
int BUTTON=2;
// переменная текущего статуса кнопки buttonState
int buttonState=true;
// переменная предыдущего статуса кнопки buttonState
int buttonStatePrev=true;
// переменная статуса светодиода
int statusLed=LOW;


void setup() {
// определяем вывод LED (светодиод) как выход
pinMode(LED, OUTPUT);
// определяем вывод BUTTON (кнопка) как вход
pinMode(BUTTON, INPUT);
// начальное состояние светодиода
digitalWrite(LED, statusLed);
}

void loop() {
// считываем состояние BUTTON входа (кнопки)
buttonState = digitalRead(BUTTON);
// если нажатие с LOW на HIGH
if(buttonState == HIGH && buttonStatePrev==LOW) {
statusLed = 1 - statusLed;
// записываем состояние из statusLed на выход LED (светодиод)
digitalWrite(LED, statusLed);
Serial.println(statusLed);
}
buttonStatePrev = buttonState;
}

Скачать данный скетч 09_01.ino можно по ссылке https://amegakit.ru/libraries/lib_Lab_Om-Robot.zip
Загружаем скетч на плату Arduino и проверяем работу.
Если будете внимательны, то заметите, что иногда переключение не происходит, вернее при одном нажатии происходит два переключения.
Почему же так происходит? Кнопки представляют из себя механические устройства, с системой пружинного контакта. Когда Вы нажимаете на кнопку вниз, сигнал не просто меняется от низкого до высокого, он в течении нескольких миллисекунд меняет значение от одного до другого, прежде чем установится значение LOW. Рисунок 9.12. иллюстрирует отличие ожидаемого явления от реального.
Рис. 9.12. Дребезг при нажатии кнопки

Кнопка физически нажата в течении 25 мс. Вы могли бы предполагать, что можете сразу узнать о состоянии кнопки, считав значение с входа контакта, как показано на левом графике. Однако кнопка фактически возвращается вверх-вниз, пока значение не установится, как показано на правом графике. Теперь, зная как ведет себя кнопка, Вы можете написать программное обеспечение кнопки с дребезгом, которое ищет изменение состояния кнопки, ожидает возврата, чтобы закончиться, и затем читает состояние переключателя снова. Эта логика программы может быть выражена следующим образом:
1) сохраняем предыдущее состояние кнопки и текущее состояние кнопки (при инициализации LOW);
2) считываем текущее состояние кнопки;
3) если текущее состояние кнопки отличается от предыдущего состояния кнопки, ждем 5 мс, потому что кнопка, возможно, изменила состояние;
4) После 5 мс, считываем состояние кнопки и используем его в качестве текущего состояния кнопки;
5) если предыдущее состояние кнопки было LOW, а текущее состояние кнопки HIGH, переключаем состояние светодиода;
6) установливаем предыдущее состояние кнопки для текущего состояния кнопки;
7) возврат к шагу 2.
Составляем скетч по вышеприведенному алгоритму (см. листинг 1.8), загружаем на плату Arduino и проверяем. Однократное нажатие кнопки приводит к однократному изменению состояния светодиода.

Листинг 9.2.
// Контакт 13 для подключения светодиода
int LED=13;
// Контакт 2 для подключения кнопки
int BUTTON=2;
// Переменная для сохранения предыдущего состояния кнопки
boolean lastButton = LOW;
// Переменная для сохранения текущего состояния кнопки
boolean currentButton = LOW;
// Текущее состояние светодиода (включен/выключен)
boolean ledOn = false;

void setup() {
// Сконфигурировать контакт светодиода как выход
pinMode (LED, OUTPUT);
// Сконфигурировать контакт кнопки как вход
pinMode (BUTTON, INPUT);
}

void loop() {
currentButton = debounce(lastButton);
// если нажатие...
if (lastButton == LOW && currentButton == HIGH)
{
// инвертировать значение состояния светодиода
ledOn = !ledOn;
Serial.println(ledOn);
}
lastButton = currentButton;
// изменить статус состояния светодиода
digitalWrite(LED, ledOn);
}

// Функция сглаживания дребезга
// Принимает в качестве аргумента предыдущее состояние кнопки,
// выдает фактическое.
boolean debounce(boolean last) {
// Считать состояние кнопки
boolean current = digitalRead(BUTTON);
if (last != current) // если изменилось...
{
// ждем 5мс
delay(5);
// считываем состояние кнопки
current = digitalRead(BUTTON);
// возвращаем состояние кнопки
return current;
}
}

Загрузим данный скетч на плату Arduino и проверяем правильность работы.
Скачать данный скетч 09_02.ino можно по ссылке https://amegakit.ru/libraries/lib_Lab_Om-Robot.zip