Освой Arduino играючи
/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000
01.Basics: DigitalReadSerial (Чтение цифрового вывода)
02.Digital: Button
02.Digital: StateChangeDetection
02.Digital: Debounce (Дребезг)
Кнопки очень часто используются в электронике. На первый взгляд, работа с ними не таит сюрпризов, но и тут есть "подводные камни".
Хотя у кнопки есть четыре ножки, фактически можно рассматривать их два участка цепи, который замыкается сверху. Следите за правильностью подключения, чтобы цепь была корректной.
Подключим кнопку без использования контроллера, пропустив ток из 5V. При нажатии кнопки цепь замкнётся и светодиод будет светиться. Ничего неожиданного.
В реальности нам нужно считывать сигнал с кнопки и реагировать на него. Поэтому попробуем изменить схему. Соединим один вывод кнопки с питанием и выводом 3 на плате. С вывода 3 мы будем считывать информацию: логический ноль или логическая единица. При нажатии на кнопку цепь замыкается, на выводе 3 будет логическая единица и мы включим светодиод.
int buttonPin = 12;
int ledPin = 3;
void setup() {
pinMode(ledPin, OUTPUT);
pinMode(buttonPin, INPUT);
}
void loop() {
bool val = digitalRead(buttonPin);
digitalWrite(ledPin, val);
}
Код прекрасно работает при нажатии кнопки. А когда мы отпускаем кнопку и создаём разрыв в цепи, то возникает проблема. Вывод 12 становится свободным и висит в неопределённом состоянии в режиме INPUT (вспоминаем урок про цифровые выводы). В результате мы получаем случайные значения и светодиод то включается, то выключается от наводок.
Чтобы избежать этой проблемы, можно добавить резистор от 10 до 100 кОм и прижать кнопку к земле. В этом случае цепь будет замкнута даже при отпущенной кнопке. В этом случае резистор называют стягивающим (pull down). Это рабочая схема, которую можно использовать в учебной программе.
Несмотря на рабочую схему с стягивающим резистором, мы получаем проблему при работе со сложным проектом. Дело в том, что возможна ситуация, когда многие устройства в схеме используют разные значения питания. И тогда придётся к каждой кнопке устройства подавать свой отдельный стягивающий резистор. На практике принято подключаться не к питанию, а к земле, которая всегда одинакова и равно 0. В этом случае сам резистор следует подключить к питанию - подтянуть. Резистор в этом случае является подтягивающим (pull up). Правда, при этом возникает другая проблема - поведение светодиода изменилось противоположным образом - при нажатии светодиод выключается, а при отпускании - включается. Решается проблема просто - меняем одну строчку кода.
digitalWrite(ledPin, !val);
Мы просто меняем значение переменной на противоположное. Это стандартный подход при работе с кнопкой. Теперь вам будет легче разобраться с примерами из Arduino IDE.
Стоит отметить, что у платы Arduino у выводов уже есть встроенные подтягивающие резисторы (кроме вывода 13) и мы можем убрать внешний резистор. Но тогда надо также явно указать использование данного резистора через код с параметром INPUT_PULLUP.
int buttonPin = 12;
int ledPin = 3;
void setup() {
pinMode(ledPin, OUTPUT);
pinMode(buttonPin, INPUT_PULLUP);
}
void loop() {
bool val = digitalRead(buttonPin);
digitalWrite(ledPin, !val);
delay(100);
}
Изучим пример DigitalReadSerial из File | Examples | 01.Basics.
Мы изучили, как правильно подключать кнопку и можем изучать встроенные примере. Будем считывать сигнал, идущий с цифрового вывода при нажатии кнопки.
Приблизительно собранная схема может выглядеть следующим образом:
Вкратце опишу на словах данную схему. Вставляем в центре макетной платы кнопку таким образом, чтобы между парными ножками проходил жёлоб макетной платы. Далее соединяем перемычками питание 5V и землю GND на Arduino с рельсами на макетной плате. Потом соединяем перемычкой цифровой вывод под номером 2 на Arduino с одной ножкой кнопки на макетной плате. Эту же ножку кнопки, но с другой стороны соединяем с резистором, который выполняет роль стягивающего резистора. После чего сам резистор соединяем с землёй. Третью ножку кнопки соединяем к положительной рельсе на макетной плате. Осталось только соединить между собой боковые рельсы на макетной плате, и мы готовы изучать новый пример.
Кнопка выполняет очень важную функцию - она замыкает цепь при нажатии. Когда кнопка не нажата, то ток не проходит между ножками кнопки, и не можем поймать сигнал с цифрового вывода под номером 2. Поэтому состояние вывода определяется системой как LOW или 0. При нажатии на кнопку его две ножки соединяются, позволяя току пройти от питания к цифровому выводу 2, а система считывает проходящий сигнал как HIGH или 1.
Разберём код по кусочкам
// Второй вывод связан с кнопкой
int pushButton = 2;
void setup() {
Serial.begin(9600);
pinMode(pushButton, INPUT);
}
void loop() {
int buttonState = digitalRead(pushButton);
Serial.println(buttonState);
delay(1); // задержка для стабильности
}
В функции setup() устанавливаем связь с портом для считывания данных на скорости 9600 бит в секунду с Arduino на ваш компьютер: Serial.begin(9600).
Вторая строчка нам уже знакома, но здесь теперь используется параметр INPUT - мы устанавливаем второй цифровой вывод на режим чтения данных, поступающих с кнопки: pinMode(pushButton, INPUT);
В цикле считываем поступающую информацию. Для начала нам понадобится новая переменная buttonState, которая будет содержать значения 0 или 1, поступающие от функции digitalRead().
Чтобы мы могли видеть поступающую информацию, нужно вывести получаемые результаты в окно Serial Monitor при помощи команды println().
Для большей стабильности при чтении данных установим минимальную задержку.
Если вы сейчас запустите программу и откроете также окно Serial Monitor (меню Tools | Serial Monitor), то на экране увидите бесконечные нули. Программа постоянно опрашивает состояние нашей конструкции и выводит результат - отсутствие тока. Если нажать на кнопку и удерживать её, то увидите, что цифры сменяются с 0 на 1. Значит в нашей цепи появился ток и информация изменилась.
Работа с кнопкой рассматривается также в примере File | Examples | 02.Digital | Button. Кнопка соединяется с выводом 2, а светодиод с выводом 13. К кнопке также следует подвести питание и землю через резистор на 10K. Сам принцип работы остался без изменений. Только на этот раз мы не будем выводить информацию о состоянии кнопки на экран, а будем включать светодиод. Такой вариант более наглядный. При нажатии и отпускании кнопки встроенный светодиод должен загораться или гаснуть.
const int buttonPin = 2; // вывод для кнопки
const int ledPin = 13; // вывод для светодиода
int buttonState = 0; // статус кнопки - нажата или отпущена
void setup() {
// режим вывода для светодиода
pinMode(ledPin, OUTPUT);
// режим ввода для кнопки
pinMode(buttonPin, INPUT);
}
void loop() {
// считываем состояние кнопки
buttonState = digitalRead(buttonPin);
// если кнопка нажата, то её состояние HIGH:
if (buttonState == HIGH) {
// включаем светодиод
digitalWrite(ledPin, HIGH);
} else {
// иначе выключаем светодиод
digitalWrite(ledPin, LOW);
}
}
Допустим, мы хотим изменить поведение - если кнопка не нажата - светодиод горит, а при нажатии - светодиод не горит. Достаточно изменить одну строчку кода.
if (buttonState == LOW)
А теперь загадка! Вы загрузили первый вариант скетча на плату, и вдруг ваш компьютер сломался. Вы не можете отредактировать скетч, чтобы использовать второй вариант. Как можно выйти из положения?
Нужно поменять полярность цепи! Провод от резистора, который на землю, нужно воткнуть в 5V, а провод, который шёл из 5V к кнопке, перекинуть на землю. При включении ток пойдёт из питания на вывод 2 без всяких помех и будет получено значение HIGH. При нажатии кнопки получится другая цепь, и вывод 2 останется без питания.
В примере File | Examples | 02.Digital | StateChangeDetection идёт подсчёт щелчков кнопки и состояние кнопки (включён или выключен). Схема осталась прежней. Кнопка соединяется с выводом 2, а светодиод с выводом 13 (можно использовать встроенный). К кнопке также следует подвести питание и стягивающий резистор к земле на 10K.
const int buttonPin = 2; // кнопка на выводе 2
const int ledPin = 13; // светодиод на выводе 13
int buttonPushCounter = 0; // счётчик нажатия кнопки
int buttonState = 0; // текущее состояние кнопки
int lastButtonState = 0; // предыдущее состояние кнопки
void setup() {
// устанавливаем режим ввода для кнопки
pinMode(buttonPin, INPUT);
// устанавливаем режим вывода для светодиода
pinMode(ledPin, OUTPUT);
// включаем последовательную передачу данных
Serial.begin(9600);
}
void loop() {
// считываем показания с вывода кнопки
buttonState = digitalRead(buttonPin);
// сравниваем состояние с предыдущим состоянием
if (buttonState != lastButtonState) {
// если состояние изменилось, увеличиваем счётчик
if (buttonState == HIGH) {
// если текущее состояние HIGH, значит кнопка включена
buttonPushCounter++;
Serial.println("on");
Serial.print("number of button pushes: ");
Serial.println(buttonPushCounter);
} else {
// если текущее состояние LOW, значит кнопка выключена
Serial.println("off");
}
// небольшая задержка для устранения эффекта дребезга
delay(50);
}
// сохраняем текущее состояние как последнее состояние для следующего раза
lastButtonState = buttonState;
// включаем светодиод при каждом четвёртом нажатии, проверяя деление по остатку счётчика нажатий
if (buttonPushCounter % 4 == 0) {
digitalWrite(ledPin, HIGH);
} else {
digitalWrite(ledPin, LOW);
}
}
У кнопок существует такой эффект, как "дребезг". При замыкании и размыкании между пластинами кнопки возникают микроискры, провоцирующие до десятка переключений за несколько миллисекунд. Явление называется дребезгом (англ. bounce). Это нужно учитывать, если необходимо фиксировать «клики». Поэтому первичным показаниям верить нельзя. По этой причине часто в скетчах делают небольшую задержку, а уже потом считывают показания. В обычном состоянии, когда мы не нажимаем кнопку или держим кнопку нажатой, эффекта дребезга не наблюдается. Иногда для этих целей в учебных примерах используют функцию delay(), но на практике следует использовать функцию millis(), как в примере File | Examples | 02.Digital | Debounce. Схема подключения остаётся без изменений.
const int buttonPin = 2; // кнопка на выводе 2
const int ledPin = 13; // светодиод на выводе 13
int ledState = HIGH; // текущее состояние светодиода
int buttonState; // текущее состояние вывода для кнопки
int lastButtonState = LOW; // предыдущее состояние вывода для кнопки
// используем тип без знака, чтобы использовать большие числа
unsigned long lastDebounceTime = 0; // последнее время
unsigned long debounceDelay = 50; // задержка, увеличьте в случае необходимости
void setup() {
pinMode(buttonPin, INPUT);
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, ledState);
}
void loop() {
// считываем состояние кнопки
int reading = digitalRead(buttonPin);
// если нажали кнопку,
// то немного ожидаем, чтобы исключить дребезг
// если состояние изменилось (дребезг или нажатие)
if (reading != lastButtonState) {
// сбрасываем таймер
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > debounceDelay) {
// whatever the reading is at, it's been there for longer than the debounce
// delay, so take it as the actual current state:
// если состояние кнопки изменилось
if (reading != buttonState) {
buttonState = reading;
// переключаем светодиод, если новое состояние кнопки стало HIGH
if (buttonState == HIGH) {
ledState = !ledState;
}
}
}
// устанавливаем светодиод
digitalWrite(ledPin, ledState);
// сохраняем состояние кнопки. В следующий раз в цикле это станет значением lastButtonState:
lastButtonState = reading;
}
У цифровых выводов уже есть резисторы на 20 кОм, которые можно использовать в качестве подтягивающих при работе с кнопками. Рассмотрим пример File | Examples | 02.Digital | DigitalInputPullup.
Схема подключения - соединим первый вывод кнопки с выводом 2 на плате, а второй вывод кнопки с выводом GND. Во время работы скетча будем считывать показания второго вывода.
void setup() {
Serial.begin(9600);
// настроим вывод 2 на режим ввода и включим встроенный подтягивающий резистор
pinMode(2, INPUT_PULLUP);
pinMode(13, OUTPUT); // светодиод
}
void loop() {
// снимаем показания кнопки
int sensorVal = digitalRead(2);
// выводим в Serial Monitor
Serial.println(sensorVal);
// Помните, что при использовании подтягивающего к питанию резистора, состояние кнопки инвертировано
// В не нажатом состоянии кнопка имеет значение HIGH, и LOW при нажатии.
// При нажатии на кнопку включим светодиод, при отпускании - выключим
if (sensorVal == HIGH) {
digitalWrite(13, LOW);
} else {
digitalWrite(13, HIGH);
}
}
Если запустить скетч, то увидим, что на монитор выводятся числа 1 (HIGH). При нажатии на кнопку значения поменяются на 0 (LOW).