Урок 1.3. Считывание цифровых входов. Условный оператор if() else. Операторы сравнения. Работа с кнопками, устранение дребезга. Программируемый выключатель

Базовый курс "Программирование микроконтроллеров"
Модуль 1. Введение в Arduino. Работа с цифровым и аналоговым сигналом

Кнопка

Кнопка — всем известное механическое устройство, которое может замыкать и размыкать электрическую цепь по желанию человека. Есть множество видов кнопок, работающих по разным правилам. Например, тактовая кнопка (push button), используемая в этом уроке, замыкает цепь только пока палец давит на неё. Кнопка на размыкание, напротив, разрывает цепь при нажатии.

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

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

Тактовые кнопки, можно найти практически в каждом электронном приборе: в клавиатуре компьютера, в телефоне, в пульте от телевизора, и т.д.

Есть кнопки с фиксацией, работающие как кнопка на шариковой ручке: один раз нажали — цепь замкнулась, второй раз — разорвалась.  Кнопки с фиксацией удобно использовать для переключения режима работы устройства. Например, можно переключать источник питания: батарея, или блок питания.

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

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

Сборка схемы

Итак, мы будем работать с самой простой тактовой кнопкой, которую попробуем подключить к Ардуино. Обычно, при работе с беспаечными макетными платами используется кнопка с выводами под пайку. 

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

На электрических схемах кнопка изображается так:

Если посмотреть внутрь четырехтактной кнопки, то можно увидеть вот такую схему:

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

Если посмотреть внутрь четырехтактной кнопки, то можно увидеть вот такую схему:

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

 

На макетной плате оба типа тактовых кнопок обычно ставятся следующим образом:

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

Резистор R1 у светодиода номиналом 220 Ом.

Резистор R2 с кнопкой номиналом 10 кОм.

Резистор R3 у динамика не обязателен, он нужен для уменьшения громкости звука, можно выбрать номинал около 100 Ом.

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

Ознакомимся с основными функциями и операторами, которые пригодятся в написании программы.

Функция digitalRead()

В уроке по зажиганию светодиода мы познакомились с функциями настройки выводов pinMode и функцией вывода в цифровой порт digitalWrite. На этот раз нам понадобится ещё одна важная функция, которая обеспечивает ввод информации в микроконтроллер – digitalRead(). Функция считывает значение с заданного входа – HIGH или LOW.

Синтаксис

digitalRead(pin);

Параметры

pin: номер вход/выхода (pin),
который Вы хотите считать

Возвращаемые значения

HIGH или LOW

Эта функция возвращает логическое значение, которое Ардуино считала с заданного контакта. Это означает, что если на контакт подать напряжение +5В, то функция вернет истину. Если контакт соединить с землей, то получим значение ложь. В языке C++, истина и ложь эквивалентны числам 1 и 0 или переменным true и false соответственно.

Для того, чтобы интересующий нас контакт заработал в режиме ввода информации, нам нужно будет установить его в определенный режим:

pinMode( НОМЕР ПИНА, INPUT );

Обратите внимание:
- если вход не подключен, то digitalRead может возвращать значения HIGH или LOW случайным образом;
- аналоговые входы (analog pins) могут быть использованы как цифровые вход/выходы (digital pins). Обращение к ним идет по номерам от 14 (для аналогового входа 0) до 19 (для аналогового входа 5).

Оператор If

if используется в сочетании с операторами сравнения и проверяет, достигнута ли истинность условия. Например, превышает ли входное значение заданное число.

Синтаксис

if ( ЛОГИЧЕСКОЕ ВЫРАЖЕНИЕ ) 
{
  // выполняется, если логическое выражение - true
} 
else 
{
  // выполняется, если логическое выражение - false
}

Параметры

Структура проверяет логическое выражение,
и в зависимости от его значения программа
выполняется по сценарию "истина" или "ложь".

Операторы сравнения и логических операций

Операторы сравнения и логических операций:

Меньше ( < )
x < y (x меньше чем y)

Больше ( > )
x > y (x больше чем y)

Меньше или равно ( <= )
x <= y (x меньше чем или равно y)

Равно ( == )
x == y (x равно y)

Логическое НЕ или отрицание ( ! )
аналог – оператор not

Не равно ( != )
x != y  (x не равно y)

Больше или равно ( >= )
x >= y (x больше чем или равно y)

Логическое И ( && )
x && y (x И y), аналог – оператор and

Логическое ИЛИ ( || )
x || y (x ИЛИ y), аналог – оператор or

Программа считывания состояния кнопки

Поставим перед собой простую задачу: пусть при однократном нажатии кнопки Ардуино мигнет светодиодом. Обратите внимание, для анализа работы нашего устройства мы будем отправлять сообщения в монитор порта о состоянии кнопки.

const int led = 5;  //пин подключения светодиода
const int button = 6;  //пин подключения кнопки
boolean cur_state_button = false;  //логическая переменная текущего состояния кнопки

void setup()
{
  pinMode( led, OUTPUT );  //настройка пина светодиода
  pinMode( button, INPUT );  //настройка пина кнопки
  Serial.begin(9600); //инициализация последовательного соединения
}

void loop()
{
  // записываем в переменную cur_state_button текущее состояние кнопки
  cur_state_button = digitalRead( button );
  // если состояние кнопки - истина, выполняем действие
  if ( cur_state_button == HIGH )
  {
    // зажигаем светодиод
    digitalWrite( led, HIGH );
    // отправляем сообщение "ON" если кнопка нажата
    Serial.println("ON");
  }
  else
  {
    // гасим светодиод
    digitalWrite( led, LOW );
    // отправляем сообщение "OFF" если кнопка не нажата
    Serial.println("OFF");
  }
}

Динамик

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

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

В этом уроке мы поговорим о динамике. Разберем подробно его устройство и попробуем проиграть мелодию на Ардуино!

Все громкоговорители можно разделить на два подтипа: электродинамический и пьезоэлектрический

Именно от названия первого подтипа пошло хорошо известное нам название динамик.

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

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

Человеку, знакомому со школьным курсом физики, не составит труда догадаться как работает это устройство:
1.  Мы знаем, что если подать на звуковую катушку напряжение, то в её витках возникнет электрический ток. 
2. Согласно закону Ампера, на проводник с током, находящийся в магнитном поле будет действовать сила Ампера. Направление этой силы можно легко вычислить с помощью правила левой руки: если вектор магнитного поля направлен в ладонь, а пальцы направлены по току (вдоль витков провода), то большой палец будет указывать направление силы. Именно сила Ампера то притягивает катушку к основанию якоря, то отталкивает от него в зависимости от направления электрического тока.
3. То есть, подавая на катушку переменный ток, мы заставим её колебаться. Звуковая катушка прочно соединена с диффузором, так что он тоже начнет колебаться. Движение же большого диффузора приведёт к колебанию большой массы воздуха, что мы и называем звуковой волной!

Функция tone. Генерация звука по нажатию кнопки

Для генерации звука заданной частоты воспользуемся функцией tone, которая имеет следующий формат:

Синтаксис

tone(КОНТАКТ, ЧАСТОТА);

Параметры

контакт — номер вывода Ардуино к которому подключён динамик
частота — частота генерируемого звука в герцах, диапазон звуковых частот воспринимаемый человеческим от 20 до 20 000 Гц

Возвращаемые значения

нет

Как только мы вызовем функцию tone, Ардуино начнет генерировать импульсный сигнал и будет делать это, пока мы принудительно не выключим генерацию с помощью другой функции — noTone:

noTone(КОНТАКТ);

Аргумент контакт — это номер вывода Ардуино к которому подключён динамик.

Обратите внимание: важно учитывать, что Ардуино может одновременно генерировать только один тон на одном контакте. Если вызовем функцию tone для одного контакта, и пока идет генерация попытаемся вызвать tone для другого контакта, то последний вызов будет попросту проигнорирован.

Таким образом наша программа примет следующий вид:

const int led = 5;  //пин подключения светодиода
const int button = 6;  //пин подключения кнопки
const int buzzer = 3; //пин подключения динамика
boolean cur_state_button = false;  //логическая переменная текущего состояния кнопки

void setup()
{
  pinMode( led, OUTPUT );  //настройка пина светодиода
  pinMode( button, INPUT );  //настройка пина кнопки
  pinMode( buzzer, OUTPUT );  //настройка пина динамика
  Serial.begin(9600); //инициализация последовательного соединения
}

void loop()
{
  // записываем в переменную cur_state_button текущее состояние кнопки
  cur_state_button = digitalRead( button );
  // если состояние кнопки - истина, выполняем действие
  if ( cur_state_button == HIGH )
  {
    // зажигаем светодиод
    digitalWrite( led, HIGH );
    // отправляем сообщение "ON" если кнопка нажата
    Serial.println("ON");
    // включаем динамик на частоте 3000 Гц
    tone(buzzer, 3000);
  }
  else
  {
    // гасим светодиод
    digitalWrite( led, LOW );
    // отправляем сообщение "ON" если кнопка нажата
    Serial.println("OFF");
    // выключаем динамик
    noTone(buzzer);
  }
}

Изменение частоты звучания и яркости светодиода по нажатию кнопки

Усложним задачу, пуcть теперь по нажатию на кнопку светодиод и динамик не просто издает звуковой и световой сигнал, а будет это делать с разной интенсивностью в зависимости от того нажата кнопка или нет.

Таким образом, программа должна реализовать следующий алгоритм:

1. Проверить нажата ли кнопка;
2. Если кнопка нажата –  увеличить переменную для хранения частоты звука и яркости светодиода;
3. Если кнопка НЕ нажата – уменьшить переменную для хранения частоты звука и яркости светодиода;
4. Воспроизвести звук и яркость в соответствии со значением их параметров.

const int led = 5;  //пин подключения светодиода
const int button = 6;  //пин подключения кнопки
const int buzzer = 3; //пин подключения динамика
boolean cur_state_button = false;  //логическая переменная текущего состояния кнопки
int pwm = 0; // переменная для хранения значения яркости светодиода
int frq = 0; // переменная для хранения значения частоты звукового сигнала


void setup()
{
  pinMode( led, OUTPUT );  //настройка пина светодиода
  pinMode( button, INPUT );  //настройка пина кнопки
  pinMode( buzzer, OUTPUT );  //настройка пина динамика
  Serial.begin(9600); //инициализация последовательного соединения
}

void loop()
{
  // записываем в переменную cur_state_button текущее состояние кнопки
  cur_state_button = digitalRead( button );
  // если состояние кнопки - истина, выполняем действие
  if ( cur_state_button == HIGH )
  {
    // увеличиваем значение переменных
    pwm = pwm + 1;
    frq = frq + 10;
  }
  else
  {
    // уменьшаем значение переменных
    pwm = pwm - 1;
    frq = frq - 10;
  }
  // издаём звуковой и световой сигналы с расчётной яркостью и частотой
  analogWrite(led, pwm);
  tone(buzzer, frq);
  // отправляем в монитор порта сообщение со значениями переменных pwm и frq
  Serial.println("PWM = " + String(pwm) + " FRQ = " + String(frq));
}

Загружаем в программу, нажимаем на кнопку и внимательно следим за результатом. Вы можете заметить, что программа в целом работает, но с небольшим недочётом. Иногда светодиод и динамик могут светить и звучать сами по себе. Для того, чтобы разобраться в чём же дело, нам потребуется заглянуть в монитор порта и посмотреть, что же происходит с нашими переменными.

Говорят, что если долго смотреть и не нажимать кнопку, то можно увидеть как наши значения станут ОТРИЦАТЕЛЬНЫМИ! А если кнопку очень долго держать нажатой, то значения станут гораздо БОЛЬШЕ тех, что могут принять в себя функция analogWrite() или tone()!

Ограничим бесконечный рост и падение наших величин используя оператор If:

const int led = 5;  //пин подключения светодиода
const int button = 6;  //пин подключения кнопки
const int buzzer = 3; //пин подключения динамика
boolean cur_state_button = false;  //логическая переменная текущего состояния кнопки
int pwm = 0; // переменная для хранения значения яркости светодиода
int frq = 0; // переменная для хранения значения частоты звукового сигнала


void setup()
{
  pinMode( led, OUTPUT );  //настройка пина светодиода
  pinMode( button, INPUT );  //настройка пина кнопки
  pinMode( buzzer, OUTPUT );  //настройка пина динамика
  Serial.begin(9600); //инициализация последовательного соединения
}

void loop()
{
  // записываем в переменную cur_state_button текущее состояние кнопки
  cur_state_button = digitalRead( button );
  // если состояние кнопки - истина, выполняем действие
  if ( cur_state_button == HIGH )
  {
    // увеличиваем значение переменных
    pwm = pwm + 1;
    frq = frq + 10;
  }
  else
  {
    // уменьшаем значение переменных
    pwm = pwm - 1;
    frq = frq - 10;
  }
  // если наши переменные больше или меньше заданных граничных значений, 
  // то они принимают максимальные и минимальные значения соотвественно
  if (pwm > 255)
    pwm = 255;
  if (pwm < 0)
    pwm = 0;
  if (frq > 5000)
    frq = 5000;
  if (frq < 0)
    frq = 0;
  // издаём звуковой и световой сигналы с расчётной яркостью и частотой
  analogWrite(led, pwm);
  tone(buzzer, frq);
  // отправляем в монитор порта сообщение со значениями переменных pwm и frq
  Serial.println("PWM = " + String(pwm) + " FRQ = " + String(frq));
}

Для наглядности мы вывели на графике только pwm-параметр:

Обратите внимание, график не выходит за рамки указанных пределов, что хорошо видно по поведению нашего устройства!

Тренажер реакции

Немного разнообразим наше устройство и превратим его в тренажёр реакции. Алгоритм работы будет следующий:

1. При запуске контроллера или в начале нового раунда светодиод и динамик издают сигнал в момент времени, который не предугадать. Т.е. время ожидания в начале программы должно быть задано генератором случайных чисел.
2. Как только пользователь нажмёт на кнопку – устройство перестанет издавать сигналы. Начнётся новый раунд.

Для начала разберемся с тем, как сгенерировать случайное число. Для этого напишем следующую программу:

const int led = 5;  //пин подключения светодиода
const int button = 6;  //пин подключения кнопки
const int buzzer = 3; //пин подключения динамика
boolean cur_state_button = false;  //логическая переменная текущего состояния кнопки
int pwm = 0; // переменная для хранения значения яркости светодиода
int frq = 0; // переменная для хранения значения частоты звукового сигнала


void setup()
{
  pinMode( led, OUTPUT );  //настройка пина светодиода
  pinMode( button, INPUT );  //настройка пина кнопки
  pinMode( buzzer, OUTPUT );  //настройка пина динамика
  Serial.begin(9600); //инициализация последовательного соединения
  randomSeed(analogRead(3)); // запуск генератора случайных чисел
}

void loop()
{
  // записываем в переменную cur_state_button текущее состояние кнопки
  cur_state_button = digitalRead( button );
  // если состояние кнопки - истина, выполняем действие
  if ( cur_state_button == HIGH )
  {
    tone(buzzer, random(500, 5000)); // издаём звук со случайно сгенерированной частотой от 500 до 5000 Гц
    delay(50); // задержка для более "приятного или мелодичного" шума
  }
}

Загрузив программу, нажмите на кнопку. Если продолжать её удерживать, то мы можем услышать “космические сообщения от инопланетян”, которые получаются в результате проигрывания и генерации случайных частот, если отпустить кнопку – мы услышим звук с последней сгенерированной частотой.

Теперь мы можем приблизиться к написанию программы для тренажера реакции:

const int led = 5;  //пин подключения светодиода
const int button = 6;  //пин подключения кнопки
const int buzzer = 3; //пин подключения динамика
boolean cur_state_button = false;  //логическая переменная текущего состояния кнопки
int pwm = 0; // переменная для хранения значения яркости светодиода
int frq = 0; // переменная для хранения значения частоты звукового сигнала


void setup()
{
  pinMode( led, OUTPUT );  //настройка пина светодиода
  pinMode( button, INPUT );  //настройка пина кнопки
  pinMode( buzzer, OUTPUT );  //настройка пина динамика
  Serial.begin(9600); //инициализация последовательного соединения
  randomSeed(analogRead(3)); // запуск генератора случайных чисел
}

void loop()
{
  //ожидаем от 2 до 10 секунд
  delay(random(2000, 10000));
  // включаем светодиод и динамик
  digitalWrite(led, HIGH);
  tone(buzzer, 4000);
  // находясь внутри бесконечного цикла проверяем нажата ли кнопка
  while (true)
  {
    cur_state_button = digitalRead( button );
    if ( cur_state_button == HIGH )
      break; // если кнопка нажата то прерываем бесконечный цикл, выходим их него и продолжаем программу дальше
  }
  // отключаем звук и светодиод
  digitalWrite(led, LOW);
  noTone(buzzer);
  // для еще большей неожиданности еще раз подождём от 1 до 5 секунд
  delay(random(1000, 5000));
}

Загружаем программу и тренируем свою реакцию!

Обратите внимание: в данной программе мы использовали пока что неизвестную структуру while (true). В будущем мы обязательно к ней вернёмся, а пока что скажем, что это бесконечный цикл внутри которого идёт проверка состояния кнопки, и если кнопка нажата - то мы выходим из цикла.

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

Программы воспроизведения музыкальных композиций

Для знакомых с нотной грамотой может быть полезна таблица соответствия нот и частот, дробные части можно отбросить или округлить:

Нота Название Частота (Hz)
E Ми пятой октавы 5274.00
D# Ре-диез пятой октавы 4978.00
D Ре пятой октавы 4698.40
C# До-диез пятой октавы 4434.80
C До пятой октавы 4186.00
B Си четвёртой октавы 3951.00
A# Ля-диез четвёртой октавы 3729.20
A Ля четвёртой октавы 3440.00
G# Соль-диез четвёртой октавы 3332.40
G Соль четвёртой октавы 3136.00
F# Фа-диез четвёртой октавы 2960.00
F Фа четвёртой октавы 2793.80
E Ми четвёртой октавы 2637.00
D# Ре-диез четвёртой октавы 2489.00
D Ре четвёртой октавы 2349.20
C# До-диез четвёртой октавы 2217.40
C До четвёртой октавы 2093.00
B Си третьей октавы 1975.50
A# Ля-диез третьей октавы 1864.60
A Ля третьей октавы 1720.00
G# Соль-диез третьей октавы 1661.20
G Соль третьей октавы 1568.00
F# Фа-диез третьей октавы 1480.00
F Фа третьей октавы 1396.90
E Ми третьей октавы 1318.50
D# Ре-диез третьей октавы 1244.50
D Ре третьей октавы 1174.60
C# До-диез третьей октавы 1108.70
C До третьей октавы 1046.50
B Си второй октавы 987.75
A# Ля-диез второй октавы 932.32
A Ля второй октавы 880.00
Нота Название Частота (Hz)
G# Соль-диез второй октавы 830.60
G Соль второй октавы 784.00
F# Фа-диез второй октавы 739.98
F Фа второй октавы 698.46
E Ми второй октавы 659.26
D# Ре-диез второй октавы 622.26
D Ре второй октавы 587.32
C# До-диез второй октавы 554.36
C До второй октавы 523.25
B Си первой октавы 493.88
A# Ля-диез первой октавы 466.16
A Ля первой октавы 440.00
G# Соль-диез первой октавы 415.30
G Соль первой октавы 392.00
F# Фа-диез первой октавы 369.99
F Фа первой октавы 349.23
E Ми первой октавы 329.63
D# Ре-диез первой октавы 311.13
D Ре первой октавы 293.66
C# До-диез первой октавы 277.18
C До первой октавы 261.63
B Си малой октавы 246.96
A# Ля-диез малой октавы 233.08
A Ля малой октавы 220.00
G# Соль-диез малой октавы 207.00
G Соль малой октавы 196.00
F# Фа-диез малой октавы 185.00
F Фа малой октавы 174.62
E Ми малой октавы 164.81
D# Ре-диез малой октавы 155.56
D Ре малой октавы 147.83
C# До-диез малой октавы 138.59
Нота Название Частота (Hz)
C До малой октавы 130.82
B Си большой октавы 123.48
A# Ля-диез большой октавы 116.54
A Ля большой октавы 110.00
G# Соль-диез большой октавы 103.80
G Соль большой октавы 98.00
F# Фа-диез большой октавы 92.50
F Фа большой октавы 87.31
E Ми большой октавы 82.41
D# Ре-диез большой октавы 77.78
D Ре большой октавы 73.91
C# До-диез большой октавы 69.30
C До большой октавы 65.41
B Си контроктавы 61.74
A# Ля-диез контроктавы 58.26
A Ля контроктавы 55.00
G# Соль-диез контроктавы 51.90
G Соль контроктавы 49.00
F# Фа-диез контроктавы 46.25
F Фа контроктавы 43.65
E Ми контроктавы 41.21
D# Ре-диез контроктавы 38.88
D Ре контроктавы 36.95
C# До-диез контроктавы 34.65
C До контроктавы 32.70
B Си субконтроктавы 30.87
A# Ля-диез субконтроктавы 29.13
A Ля субконтроктавы 27.50
G# Соль-диез субконтроктавы 25.95
G Соль субконтроктавы 24.50
F# Фа-диез субконтроктавы 23.12
F Фа субконтроктавы 21.82

Составим программу для воспроизведения нескольких нот из гаммы первой октавы по нажатии на кнопку:

const int led = 5;  //пин подключения светодиода
const int button = 6;  //пин подключения кнопки
const int buzzer = 3; //пин подключения динамика
boolean cur_state_button = false;  //логическая переменная текущего состояния кнопки
int pwm = 0; // переменная для хранения значения яркости светодиода
int frq = 0; // переменная для хранения значения частоты звукового сигнала


void setup()
{
  pinMode( led, OUTPUT );  //настройка пина светодиода
  pinMode( button, INPUT );  //настройка пина кнопки
  pinMode( buzzer, OUTPUT );  //настройка пина динамика
  Serial.begin(9600); //инициализация последовательного соединения
  randomSeed(analogRead(3)); // запуск генератора случайных чисел
}

void loop()
{
  //ожидаем от 2 до 10 секунд
  delay(random(2000, 10000));
  // включаем светодиод и динамик
  digitalWrite(led, HIGH);
  tone(buzzer, 4000);
  // находясь внутри бесконечного цикла проверяем нажата ли кнопка
  while (true)
  {
    cur_state_button = digitalRead( button );
    if ( cur_state_button == HIGH )
      break; // если кнопка нажата то прерываем бесконечный цикл, выходим их него и продолжаем программу дальше
  }
  // отключаем звук и светодиод
  digitalWrite(led, LOW);
  noTone(buzzer);
  // для еще большей неожиданности еще раз подождём от 1 до 5 секунд
  delay(random(1000, 5000));
}

Наконец, попробуем воспроизвести полноценную мелодию из известной всем космической саги:

const int led = 5;  //пин подключения светодиода
const int button = 6;  //пин подключения кнопки
const int buzzer = 3; //пин подключения динамика
boolean cur_state_button = false;  //логическая переменная текущего состояния кнопки
int numTones = 39; //количество нот для воспроизведения

// частоты нот
int tones[numTones] = {
  392, 392, 392, 311, 466, 392, 311, 466, 392,
  587, 587, 587, 622, 466, 369, 311, 466, 392,
  784, 392, 392, 784, 739, 698, 659, 622, 659,
  415, 554, 523, 493, 466, 440, 466,
  311, 369, 311, 466, 392
};
// длительности нот
int durations[numTones] = {
  350, 350, 350, 250, 100, 350, 250, 100, 700,
  350, 350, 350, 250, 100, 350, 250, 100, 700,
  350, 250, 100, 350, 250, 100, 100, 100, 450,
  150, 350, 250, 100, 100, 100, 450,
  150, 350, 250, 100, 750
};

void setup()
{
  pinMode( led, OUTPUT );  //настройка пина светодиода
  pinMode( button, INPUT );  //настройка пина кнопки
  pinMode( buzzer, OUTPUT );  //настройка пина динамика
  Serial.begin(9600); //инициализация последовательного соединения
}

void loop()
{
  // записываем в переменную cur_state_button текущее состояние кнопки
  cur_state_button = digitalRead( button );
  // если состояние кнопки - истина, выполняем действие
  if ( cur_state_button == HIGH )
  {
    for (int i = 0; i <= COUNT_NOTES; i++ )
    {
      tone( buzzer, tones[i], durations[i] * 2 );
      delay( durations[i] * 2 );
      noTone( buzzer );
    }
  }
  // выключаем динамик
  noTone(buzzer);
}

Дополнительная информация. Дребезг контактов

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

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

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

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

Как отразится дребезг на нашем проекте? Да самым прямым образом – мы будем получать на входе совершенно случайный набор значений. Ведь если мы считываем значение с кнопки непрерывно, в каждом новом рабочем цикле функции loop, то будем замечать все “всплески” и “падения” сигнала. Потому что пауза между двумя вызовами loop составляет микросекунды и мы измерим все мелкие изменения.

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

Дополнительная информация. Подтягивающий (стягивающий) резистор

Подтягивающий (стягивающий) резистор — резистор, включённый между проводником, по которому распространяется электрический сигнал, и питанием или землей.

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

Представим, что резистор номиналом 10 кОм в схеме на рисунке отсутствует. В таком случае очевидно, что при нажатии кнопки на контакт будет подаваться высокий уровень, поскольку кнопка подключает его напрямую к питанию 5 В.

Но что будет тогда, когда кнопка не нажата? В таком случае рассматриваемый контакт не подключен к какому-либо источнику напряжения и его состояние называется “плавающим”. Кроме того, поскольку на этот контакт не подается ни низкий (0 В) ни высокий (5 В) уровень, результаты считывания его состояния будут непредсказуемыми, поскольку под влиянием электрических наводок от близлежащих контактов это состояние будет “плавать”, т. е. колебаться, между высоким и низким уровнями. Вот для того, чтобы не допустить такого “плавающего” входного состояния контакта, он и подключается на землю через понижающий резистор. Резистор называется понижающим потому, что он понижает уровень напряжения на контакте до нуля.

Теперь рассмотрим, что происходит при не нажатой кнопке при наличии в схеме понижающего резистора. В таком случае наш контакт подключен на землю через этот резистор. Хотя резистор и ограничивает протекающий через контакт ток, это неполное ограничение, и через контакт протекает достаточный ток, чтобы установить на нем низкий входной уровень. Обычно номинал понижающего резистора выбирают равным 10 кОм. Резисторы большего номинала слабее влияют на потенциал контакта, поскольку их легче обойти, а резисторы меньшего номинала сильнее снижают его, поскольку через них току легче протекать на землю. 

При нажатии кнопки входной контакт платы Arduino подключается через ее замкнутые контакты напрямую на землю. Теперь у тока с контакта есть два пути: 
– он может протекать через цепь с практически нулевым сопротивлением на шину питания 5 В;
– он может протекать через цепь с высоким сопротивлением на шину земли. 

Вспомним, что закон Ома говорит, что ток в цепи всегда выбирает путь наименьшего сопротивления. В данном случае подавляющая часть тока протекает через кнопку, поскольку она представляет путь наименьшего сопротивления. В результате на входном контакте платы Arduino создается высокий логический уровень.

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

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

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

Дополнительная информация. Программный метод устранения дребезга

Самым простым способом справиться с проблемой дребезга кнопки является выдерживание паузы. Мы просто останавливаемся и ждем, пока переходный процесс не завершится. Для этого можно использовать функцию delay() или millis(). 10-50 миллисекунд – вполне нормальное значение паузы для большинства случаев.

Логику такой программы можно выразить следующим образом: 
1. Сохраняем предыдущее и текущее значения выходного уровня кнопки (инициализированы значением низкого уровня LOW). 
2. Считываем текущее значение выходного уровня кнопки.
3. Если текущее значение состояния кнопки отличается от предыдущего значения, значит, выходной уровень кнопки изменился; 
4. Выжидаем в течение 10 мс.
5. По истечении 10 мс снова считываем состояние выходного уровня кнопки и сохраняем его как значение текущего состояния.
6. Если предыдущее состояние было на низком уровне, а текущее состояние – высокий уровень, меняем состояние контакта LED на противоположное.
7. Сохраняем текущее состояние кнопки, как предыдущее состояние.
8. Возвращаемся на шаг 2.

const int led = 2;  //пин подключения светодиода
const int button = 3;  //пин подключения кнопки
boolean cur_state_button = false;  //логическая переменная текущего состояния кнопки
boolean prev_state_button = false;  //логическая переменная предыдущего состояния кнопки

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

void loop()
{
  // записываем в переменную cur_state_button текущее состояние кнопки
  cur_state_button = digitalRead( button );
  // если состояние кнопки в данный момент - истина, а в предыдущей итерации - ложь то выполняем действие
  if ( cur_state_button == HIGH && prev_state_button == LOW)
  {
    //выжидаем 10мс для игнорирования переходного процесса кнопки
    delay(10);
    //если после ожидания времени пропуска переходного процесса состояние кнопки осталось - истина, значит кнопка нажата
    if (digitalRead( button ) == HIGH)
      // зажигаем светодиод
      digitalWrite( led, HIGH );
  }
  else
  {
    // гасим светодиод
    digitalWrite( led, LOW );
  }
  //перезаписываем переменную состояния кнопки для того, что бы контроллер смог "вспомнить" состояние пина
  //кнопки на прошлой итерации цикла
  prev_state_button = cur_state_button;
}

Дополнительная информация. Аппаратный метод устранения дребезга кнопки

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

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

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

RC фильтр

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

Ниже пример простого фильтра на базе RC-цепочки и форма сигнала после использования фильтра.

Триггер Шмитта

Сделать квадратную форму сигнала с помощью простой RC-цепочки невозможно. Для “огранения” сглаженных форм используется специальный компонент, который называется триггер Шмитта. Его особенностью является срабатывание при достижении определенного уровня сигнала. На выходе триггера Шмитта мы получим или высокий или низкий уровень сигнала, никаких промежуточных значений. Выход триггера инвертированный: при спаде входного сигнала он выдает на выходе включение и наоборот.

Как видим, мы практически полностью избавились от результатов переходных процессов, сперва превратив хаос в почти гладкую кривую линию, а затем с помощью триггера шмитта “отрубили” хвосты, придав сигналу практически идеальный вид. Подав его на вход ардуино, мы уже можем не беспокоиться о ложных срабатываниях и смело использовать в скетче метод digitalRead и прерывания.