Вы находитесь в самом начале Вашего удивительного путешествия в мир Arduino, и мы Вас с этим поздравляем! В этом руководстве мы на практике подробно рассмотрим, как работать с платой Arduino Uno, как писать под нее код и как настроить её взаимодействие с различными модулями и датчиками. Впереди нас ждут 20 увлекательных экспериментов разной степени сложности, поэтому, давайте не будем задерживаться и сразу перейдем к практике!

Предварительная подготовка

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

Разработка скетчей для Arduino ведется в программе, именуемом интегрированной средой разработки (IDE). Эту среду разработки предварительно нужно скачать и установить:

  1. Переходим по ссылке https://www.arduino.cc/en/Main/Software;
  2. Листаем до раздела Download the Arduino IDE;
  3. Выбираем нашу операционную систему (Windows, macOs или Linux);
  4. На следующей странице выбираем Just Download;
  5. Устанавливаем приложение;
  6. Готово! Вы восхитительны и готовы к программированию Arduino.

Настройка Arduino IDE

Как же нам теперь написать скетч и загрузить его в Arduino? Это очень и очень просто:

  1. Подключаем плату Arduino Uno к компьютеру кабелем, который идет в комплекте (на плате загорится светодиод, сообщая, что на плату поступает питание, она готова выполнять заложенные в нее команды и готова к загрузке скетча);
  2. Открываем установленную среду разработки Arduino;
  3. В верхней панели открываем Инструменты -> Плата -> Выбираем Arduino/Genuino Uno;
  4. В верхней панели открываем Инструменты -> Порт -> Выбираем Порт, к которому подключена наша плата (обычно напротив нужного порта будет стоять название подключенной платы);
  5. Пишем код нашего скетча в окне редактора;
  6. Проверяем наш скетч на ошибки;
  7. Загружаем скетч в плату;
  8. Готово! Наша плата Arduino прошита и сразу начнет выполнять загруженные в нее команды.

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

Библиотеки Arduino

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

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

  1. Скачиваем библиотеку на компьютер (ссылки на библиотеки будут указаны в экспериментах, где они нужны);
  2. В верхней панели Arduino IDE открываем Скетч -> Подключить библиотеку -> Добавить .ZIP библиотеку;
  3. Выберите загруженный ZIP-файл и нажмите кнопку Open (Открыть);
  4. Готово! Теперь вы можете пользоваться функционалом библиотеки в своем скетче.

Начинаем практику!

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

№1: Мигающий светодиод

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

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

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

Выполните следующие шаги, чтобы провести тестирование:

  1. Вставьте длинную ножку (также известную как +5V, положительный контакт или анод) светодиода в контакт 13 на плате Arduino, как показано на рисунке:

  2. Подключите короткую ножку (также известную как отрицательный контакт или катод) к контакту GND (рядом с контактом 13), который также называется землей (ground);
  3. Подключите Вашу Arduino Uno к компьютеру с помощью USB-кабеля;
  4. Введите следующий код в окне среды разработки Arduino IDE:

    // Проект мигающего светодиода
    int led = 13;
    
    void setup() {
      pinMode(led, OUTPUT);
    }
    
    void loop() {
      digitalWrite(led, HIGH);
      delay(1000);
      digitalWrite(led, LOW);
      delay(1000);
    }
  5. Нажмите кнопку Verify (Проверить) в виде галочки и убедитесь, что программа работает правильно.
  6. Теперь нажмите кнопку Upload (Загрузка), чтобы загрузить Ваш скетч в Arduino.
  7. После загрузки скетча, он начнет сразу выполняться и светодиод начнет мигать.

Рассмотрим, что происходит в каждой строке скетча:

1.  // Проект мигающего светодиода
2.  int led = 13;
3.  void setup() {
4.    pinMode(led, OUTPUT);
5.  }
6.  void loop() {
7.    digitalWrite(led, HIGH);
8.    delay(1000);
9.    digitalWrite(led, LOW);
10.   delay(1000);
11. }
  1. Это комментарий. Каждая строка программы, которая начинается с символов //, предназначена для чтения только пользователем и игнорируется Arduino. Таким образом Вы можете описывать что происходит в Вашем коде (этот процесс называется комментированием кода). Если комментарий занимает больше одной строки, начните его с символов /* и закончите символами */. Строки текста внутри этих символов будут так же проигнорированы Arduino:
    /*
      Эти стороки являются комментарием
      и будут проигнорированы
      платой Arduino.
    */
  2. Создаем переменную led и записываем в нее значение нашего пина 13. Теперь каждое упоминание led в скетче обозгначает 13 контакт.
  3. Тут начинается код Настройки. Код, который расположен между фигурными скобками {} после инструкции setup() будет выполнен однократно при запуске платы. С открывающей фигурной скобки { начинается код Настройки.
  4. Сообщает Arduino, что контакт 13 работает в режиме выхода (таким образом мы будем подавать питание на светодиод).
  5. Закрывающая фигурная скобка } обозначает конец кода Настройки.
  6. Создает бесконечный цикл. Код, который находится между фигурными скобками {}, после инструкции loop() будет повторяться, пока на Arduino подается питание.
  7. Инструктирует Arduino подать на пин 13 Высокий сигнал (HIGH), таким образом пин 13 будет выдавать 5 вольт. Через светодиод пойдет ток и заставит его светиться.
  8. Инструктирует Arduino подождать 1 секунду. Т.к. время в Arduino отсчитывается в миллисекундах, а 1 секунда = 1000 миллисекунд, здесь указано значение 1000.
  9. Инструктирует Arduino подать на пин 13 Низкий сигнал (LOW), таким образом пин 13 будет выдавать 0 вольт. Эта инструкция отключает светодиод.
  10. Инструктирует Arduino опять подождать 1 секунду.
  11. Этой закрывающей фигурной скобкой заканчивается наш бесконечный цикл. После этой фигурной скобки скетч возвращается к началу цикла (строка 6).

Вы протестировали Вашу плату Arduino, поняли принцип работы скетча и процесс его загрузки. Неплохо для начала, правда? Впереди нас ждут еще более интересные знания и открытия!

Видео

№2: Управляемый светодиод

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

Принцип работы тактовой кнопки:

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

     
  • Контакты А и С всегда замкнуты, так же как B и D.
  • При нажатии кнопка замыкает цепь, подавая питание. Как только кнопка отпущена, она размыкает цепь, прерывая питание.

Схема подключения

Скетч

В этом скетче мы определяем контакт кнопки и переводим его в режим входа - INPUT, а контакт светодиода - в режим выхода - OUTPUT.

Код инструктирует Arduino включить светодиод, пока нажата кнопка замыкающая цепь.

const int buttonPin = 2; // Контакт, к которому подключена кнопка
const int ledPin = 13; // Контакт, к которому подключен светодиод
int buttonState = 0; // Задание значения кнопки

void setup() {
  pinMode(ledPin, OUTPUT); // Перевод контакта светодиода в режим выхода
  pinMode(buttonPin, INPUT); // Перевод контакта кнопки в режим входа
}
void loop() {
  buttonState = digitalRead(buttonPin); // Чтение сигнала с контакта 2
  if (buttonState == HIGH) { // Если кнопка нажата
    digitalWrite(ledPin, HIGH); // Устанавливается состояние HIGH (Включение светодиода)
  } else { // В противном случае
    digitalWrite(ledPin, LOW); // отключаем светодиод
  }
}

Видео

№3: Диммер освещения

В этом проекте Вы познакомитесь с потенциометром, аналоговым сигналом и понятим ШИМ-модуляции! Разве это не круто?

Итак, потенциометр управляет постоянным аналоговым сигналом. Но что это такое?

Проще всего это объяснить на примере:

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

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

Центральный контакт потенциометра посылает сигнал на аналоговый вход Arduino (любой контакт с метками от A0 до A5) для считывания значения.

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

Чтобы создать этот эффект, в Arduino используется технология, называемая широтно-импульсной модуляцией (ШИМ). Arduino создает импульсы, очень быстро включая и выключая питание. Длительность включения и отключения питания (так называемая ширина импульса) в цикле определяет средний уровень выхода, и, изменяя значение ширины импульса, устройство может симулировать значения напряжения в диапазоне от 0 до 5 В (т.е. от отключенного до включенного состояния).

Если питание от Arduino подается в течение половины времени и отключается также на половину времени, средний уровень выхода составит 2.5 В, т.е. по центру между 0 и 5 В. Если сигнал подается 80% всего времени, а отключен в течение 20%, средний уровень выхода составит 4 В, и т.д.

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

Обратите внимание! У платы Arduino только контакты 3, 5, 6, 9, 10 и 11 поддерживают режим ШИМ-модуляции.

На рисунке ниже показаны примеры работы ШИМ в виде формы сигнала.

Очень много теории и мало что понятно? Ничего страшного, на практике все окажется совсем не сложно, а знания, которые Вы получите окажутся бесценными!

Схема подключения

Не имеет значения, какой из внешних контактов потенциометра подключать к плюсу, а какой к минусу.

Скетч

При работе этого скетча контакт А0 настроен на работу с потенциометром, а контакт 9 служит выводом для питания светодиода.

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

Напряжение регулируется в пределах от 0 до 5 В, тем самым и устанавливается яркость светодиода.

int potPin = A0; // Контакт аналогового входа, подключенный к потенциометру
int potValue = 0; // Значение, считываемое с потенциометра
int led = 9; // Контакт 9 подключен к светодиоду (поддерживает ШИМ)

// Выполняется однократно в начале программы
void setup() {
  pinMode(led, OUTPUT); // Перевод контакта 9 в режим вывода
}

// Непрерывное выполнение
void loop() {
  potValue = analogRead(potPin); // Чтение значения потенциометра с контакта A0
  analogWrite(led, potValue / 4); // Отправка значения потенциометра на светодиод для управления яркостью с помощью ШИМ
  delay(10); // Задержка 10 мс
}

Видео

№4: Светодиодная панель

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

Когда Вы поворачиваете ручку потенциометра в одну сторону, светодиоды загораются по очереди, пока не загорятся все, как показано на рисунке «А». Когда вы поворачиваете ручку в другую сторону, они поочередно выключаются, пока не погаснут все, как показано на рисунке «Б».

Схема подключения

Скетч

const int analogPin = A0; // Контакт, к которому подключен потенциометр
const int ledCount = 9; // Количество светодиодов
int ledPins[] = {2, 3, 4, 5, 6, 7, 8, 9, 10}; // Контакты, к которым подключены светодиоды

void setup() {
  for (int thisLed = 0; thisLed < ledCount; thisLed++) {
    pinMode(ledPins[thisLed], OUTPUT); // Перевод контактов, к которым подключены светодиоды, в режим вывода
  }
}

void loop() { // Начало цикла
  int sensorReading = analogRead(analogPin); // Аналоговый вход
  int ledLevel = map(sensorReading, 0, 1023, 0, ledCount);
  for (int thisLed = 0; thisLed < ledCount; thisLed++) {
    if (thisLed < ledLevel) {
      // Последовательное включение светодиодов
      digitalWrite(ledPins[thisLed], HIGH);
    } else {
      // Последовательное выключение светодиодов
      digitalWrite(ledPins[thisLed], LOW);
    }
  }
}

Видео

№5: Дискотечный стробоскоп

Мигание светодиодами может быть веселым! Давайте попробуем создать дискотечный стробоскоп?

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

Схема подключения

Скетч

Скетч начинает работу с настройки контакта Arduino, принимающего аналоговый сигнал с потенциометра, на режим входа, а контактов, к которым подключены светодиоды, на режим выхода.

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

const int analogInPin = A0; // Аналоговый контакт, к которому подключен потенциометр
int sensorValue = 0; // Значение, полученное от потенциометра
int timer = 0; // Задержка

// Перевод контактов 11 и 12 в режим вывода
void setup() {
  pinMode(12, OUTPUT);
  pinMode(11, OUTPUT);
}

// Начало цикла, включающего/выключающего светодиоды с настраиваемой задержкой
void loop() {
  sensorValue = analogRead(analogInPin); // Считывание значения от потенциометра (от 0 до 1023)
  timer = map(sensorValue, 0, 1023, 10, 500); // Устанавливаем задержка 10 - 500 мс *
  digitalWrite(12, HIGH); // Подача напряжения на светодиоды
  delay(timer); // Задержка в зависимости от значения потенциометра
  digitalWrite(12, LOW); // Прекращение подачи напряжения на светодиоды
  delay(timer);
  digitalWrite(12, HIGH);
  delay(timer);
  digitalWrite(12, LOW);
  digitalWrite(11, HIGH);
  delay(timer);
  digitalWrite(11, LOW);
  delay(timer);
  digitalWrite(11, HIGH);
  delay(timer);
  digitalWrite(11, LOW);
}

* Функция map(value, fromLow, fromHigh, toLow, toHigh) преобразовывает значение переменной из одного диапазона в другой. Т.е. значение переменной value, равное fromLow, будет преобразовано в число toLow, а значение fromHigh - в toHigh. Все промежуточные значения value масштабируются относительного нового диапазона [toLow; toHigh].

№6: Контроль полива домашних растений

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

Как показано на рисунках ниже, датчик состоит из двух частей: сам датчик (А) и его контроллер (Б).

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

  • Малые показатели датчика указывают на присутствие влаги в почве, а высокие — на ее сухость;
  • Если замеры выдали значение, равное или превышающее 900, у вашего растения серьезный недостаток влаги.
  • В случае недостаток влаги загорится светодиод, и пьезодинамик издаст звук.

Схема подключения

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

Скетч

Сначала скетч настраивает контакт A0 платы Arduino на считывание показаний датчика влажности.

Затем он настраивает контакт 11 платы Arduino на вывод на пьезоизлучатель, а контакт 13 — на вывод на светодиод.

Функция Serial.Println() используется для передачи показаний датчика в монитор порта среды разработки Arduino. Вы можете посмотреть эти показания, нажав на кнопку в правом верхнем углу:

В своем проекте измените значение в строке if (analogRead(0) > 900) { таким образом, чтобы оно соответствовало показанию сухого датчика (у нас получилось значение 900).

Когда почва влажная, значение будет ниже 900, поэтому светодиод и пьезоизлучатель останутся выключенными.

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

const int moistureAO = A0; // Контакт, к которому подключен выход A0 контроллера датчика влажности
int AO = 0; // Значение аналогового контакта
int tmp = 0;
int buzzPin = 11; // Контакт, к которому подключен пьезоизлучатель
int LED = 13; // Контакт, к которому подключен светодиод

void setup () {
  Serial.begin(9600);
  Serial.println("Soil moisture sensor"); // При старте платы, выводим сообщение в монитор порта
  pinMode(moistureAO, INPUT);
  pinMode(buzzPin, OUTPUT); // Перевод контакта в режим выхода
  pinMode(LED, OUTPUT); // Перевод контакта в режим выхода
}
void loop () {
  tmp = analogRead(moistureAO); // Считываем показания
  if (tmp != AO) {
    AO = tmp;
    Serial.print("A = "); // Вывести значение сопротивления датчика в IDE
    Serial.println(AO);
  }
  delay (1000);
  if (analogRead(0) > 900) { // Если показание датчика превышает 900,
    tone(buzzPin, 300); // Передаем сигнал с частотой 300 Гц на пищалку
    digitalWrite(LED, HIGH); // и на светодиод
    delay(1000); // Ожидание в 1 секунду
    noTone(buzzPin); // Отключаем пищалку
    digitalWrite(LED, LOW); // и светодиод
  } else {
    // Если показание датчика меньше 900, питание на пьезоизлучатель и светодиод не подается
    noTone(buzzPin);
    digitalWrite(LED, LOW);
  }
}

№7: Проигрыватель простых мелодий

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

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

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

Схема подключения

Уточнения к скетчу

В данном скетче используются не только, уже знакомые нам, функции setup() и loop(), а так же функции playNote() и playTone().

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

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

Функция должна быть описана, и после этого может вызываться. Функция должна быть описана вне других функций! В общем виде функция имеет следующую структуру:

[тип_данных] [имя_функции] (набор_параметров) {
	тело_функции
}
  • Тип данных – это тип данных, который возвращает функция
  • Имя функции – имя, по которому функция вызывается
  • Имя функции – имя, по которому функция вызывается
  • Набор параметров – необязательный набор входных переменных
  • Тело функции – набор операций, которые выполняет функция

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

[имя_функции](параметр1, параметр2, ...параметрN);

Если параметры не передаются, нужно указать пустые скобки в любом случае!

имя_функции();

Функция может принимать параметры, может не принимать, может возвращать какое-то значение, может не возвращать.

Подробнее о функциях можно почитать тут: https://alexgyver.ru/lessons/functions

Скетч

Давайте воспроизведем простую мелодию.

В строке 2 мы сообщаем Arduino, что мелодия будет состоять из 15 нот. Затем мы сохраняем ноты мелодии в символьном массиве в виде текстовой строки в том порядке, в котором они должны воспроизводится.

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

В строке 5 мы определяем темп, в котором будет воспроизводится мелодия.

Чтобы изменить мелодию, достаточно поменять значения в строках 2, 3, 4, 5.

int speakerPin = 9; // Контакт, к которому подключен пьезоизлучатель
int length = 15; // Число нот
char notes[] = "ccggaagffeeddc "; // Ноты мелодии в символьном массиве, пробел - пауза
int beats[] = { 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 4 }; // Длительности звучания каждой ноты
int tempo = 300;

void playTone(int tone, int duration) { // Создавая функцию типа void мы указываем компилятору, что никаких значений возвращаться не будет
  for (long i = 0; i < duration * 1000L; i += tone * 2) { // L - означает большое значение, без этого параметра максимальное значение будет 32.767
    digitalWrite(speakerPin, HIGH);
    delayMicroseconds(tone);
    digitalWrite(speakerPin, LOW);
    delayMicroseconds(tone);
  }
}

// Определение timeHigh для определенной ноты
void playNote(char note, int duration) {
  char names[] = { 'c', 'd', 'e', 'f', 'g', 'a', 'b', 'C' };
  int tones[] = { 1915, 1700, 1519, 1432, 1275, 1136, 1014, 956 };
  for (int i = 0; i < 8; i++) { // Воспроизведение тона, соответствующего ноте
    if (names[i] == note) {
      playTone(tones[i], duration);
    }
  }
}

void setup() {
  pinMode(speakerPin, OUTPUT); // Переводим speakerPin в режим вывода
}

// Воспроизведение тона
void loop() {
  for (int i = 0; i < length; i++) {
    if (notes[i] == ' ') {
      delay(beats[i] * tempo); // Конец мелодии
    } else {
      playNote(notes[i], beats[i] * tempo);
    }
    delay(tempo / 2); // Пауза между нотами
  }
}

№8: Игра TouchMe (Atari)

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

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

Если игрок ошибется при повторении комбинации, игра сбрасывается на начальный уровень.

Схема подключения

Скетч

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

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

Для корректной компиляции скетча нужно подключить библиотеку Tone (Скачать).

#include <Tone.h>
Tone speakerpin;
int starttune[] = {NOTE_C4, NOTE_F4, NOTE_C4, NOTE_F4, NOTE_C4, NOTE_F4, NOTE_C4, NOTE_F4, NOTE_G4, NOTE_F4, NOTE_E4, NOTE_F4, NOTE_G4};
int duration2[] = {100, 200, 100, 200, 100, 400, 100, 100, 100, 100, 200, 100, 500};
int note[] = {NOTE_C4, NOTE_C4, NOTE_G4, NOTE_C5, NOTE_G4, NOTE_C5};
int duration[] = {100, 100, 100, 300, 100, 300};
int button[] = {2, 3, 4, 5}; // Контакты, к которым подключены кнопки
int ledpin[] = {8, 9, 10, 11}; // Контакты, к которым подключены светодиоды
int turn = 0; // Счетчик включений
int buttonstate = 0; // Проверка состояния кнопки
int randomArray[100]; // Массив, содержащий до 100 вводов
int inputArray[100];

void setup() {
  Serial.begin(9600);
  speakerpin.begin(12); // Контакт, к которому подключен пьезоизлучатель

  for (int x = 0; x < 4; x++) {
    pinMode(ledpin[x], OUTPUT); // Перевод контактов, к которым подключены светодиоды, в режим вывода
  }

  for (int x = 0; x < 4; x++) {
    pinMode(button[x], INPUT); // Перевод контактов, к которым подключены кнопки, в режим ввода
    digitalWrite(button[x], HIGH); // Включение внутреннего подтягивания; кнопки активны в верхнем положении (логическая инверсия)
  }

  // Генерация "большей случайности" функции randomArray, чтобы последовательность каждый раз менялась
  randomSeed(analogRead(0));

  for (int thisNote = 0; thisNote < 13; thisNote ++) {
    speakerpin.play(starttune[thisNote]); // Воспроизведение следующей ноты

    if (thisNote == 0 || thisNote == 2 || thisNote == 4 || thisNote == 6) {
      digitalWrite(ledpin[0], HIGH);
    }

    if (thisNote == 1 || thisNote == 3 || thisNote == 5 || thisNote == 7 || thisNote == 9 || thisNote == 11) {
      digitalWrite(ledpin[1], HIGH);
    }

    if (thisNote == 8 || thisNote == 12) {
      digitalWrite(ledpin[2], HIGH);
    }

    if (thisNote == 10) {
      digitalWrite(ledpin[3], HIGH);
    }

    delay(duration2[thisNote]);
    speakerpin.stop(); // Остановка для воспроизведения следующей ноты
    digitalWrite(ledpin[0], LOW);
    digitalWrite(ledpin[1], LOW);
    digitalWrite(ledpin[2], LOW);
    digitalWrite(ledpin[3], LOW);
    delay(25);
  }
  delay(1000);
}

void loop() {
  // Генерация массива, назначаемого игроку
  for (int y = 0; y <= 99; y++) {
    digitalWrite(ledpin[0], HIGH);
    digitalWrite(ledpin[1], HIGH);
    digitalWrite(ledpin[2], HIGH);
    digitalWrite(ledpin[3], HIGH);

    // Воспроизведение следующей ноты
    for (int thisNote = 0; thisNote < 6; thisNote ++) {
      speakerpin.play(note[thisNote]);
      // Продолжение ноты
      delay(duration[thisNote]); // Остановка для воспроизведения
      // следующей ноты
      speakerpin.stop();
      delay(25);
    }

    digitalWrite(ledpin[0], LOW);
    digitalWrite(ledpin[1], LOW);
    digitalWrite(ledpin[2], LOW);
    digitalWrite(ledpin[3], LOW);
    delay(1000);

    // Ограничения переменной turn
    for (int y = turn; y <= turn; y++) {
      Serial.println("");
      Serial.print("Turn: ");
      Serial.print(y);
      Serial.println("");
      randomArray[y] = random(1, 5);

      // Присвоение случайного номера (1-4)
      // Включение светодиодов в случайном порядке
      for (int x = 0; x <= turn; x++) {
        Serial.print(randomArray[x]);
        for (int y = 0; y < 4; y++) {
          if (randomArray[x] == 1 && ledpin[y] == 8) {
            digitalWrite(ledpin[y], HIGH);
            speakerpin.play(NOTE_G3, 100);
            delay(400);
            digitalWrite(ledpin[y], LOW);
            delay(100);
          }

          if (randomArray[x] == 2 && ledpin[y] == 9) {
            digitalWrite(ledpin[y], HIGH);
            speakerpin.play(NOTE_A3, 100);
            delay(400);
            digitalWrite(ledpin[y], LOW);
            delay(100);
          }

          if (randomArray[x] == 3 && ledpin[y] == 10) {
            digitalWrite(ledpin[y], HIGH);
            speakerpin.play(NOTE_B3, 100);
            delay(400);
            digitalWrite(ledpin[y], LOW);
            delay(100);
          }

          if (randomArray[x] == 4 && ledpin[y] == 11) {
            digitalWrite(ledpin[y], HIGH);
            speakerpin.play(NOTE_C4, 100);
            delay(400);
            digitalWrite(ledpin[y], LOW);
            delay(100);
          }
        }
      }
    }
    input();
  }
}

// Проверка ввода на соответствие последовательности
void input() {
  for (int x = 0; x <= turn;) {
    for (int y = 0; y < 4; y++) {
      buttonstate = digitalRead(button[y]);

      // Проверка нажатия кнопки
      if (buttonstate == LOW && button[y] == 2) {
        digitalWrite(ledpin[0], HIGH);
        speakerpin.play(NOTE_G3, 100);
        delay(200);
        digitalWrite(ledpin[0], LOW);
        inputArray[x] = 1;
        delay(250);
        Serial.print(" ");
        Serial.print(1);
        // Проверка введенного пользователем значения на соответствие
        // сгенерированному массиву
        if (inputArray[x] != randomArray[x]) {
          fail(); // Если нет, вызывается функция fail
        }
        x++;
      }

      if (buttonstate == LOW && button[y] == 3) {
        digitalWrite(ledpin[1], HIGH);
        speakerpin.play(NOTE_A3, 100);
        delay(200);
        digitalWrite(ledpin[1], LOW);
        inputArray[x] = 2;
        delay(250);
        Serial.print(" ");
        Serial.print(2);
        if (inputArray[x] != randomArray[x]) {
          fail();
        }
        x++;
      }

      if (buttonstate == LOW && button[y] == 4) {
        digitalWrite(ledpin[2], HIGH);
        speakerpin.play(NOTE_B3, 100);
        delay(200);
        digitalWrite(ledpin[2], LOW);
        inputArray[x] = 3;
        delay(250);
        Serial.print(" ");
        Serial.print(3);
        if (inputArray[x] != randomArray[x]) {
          fail();
        }
        x++;
      }

      if (buttonstate == LOW && button[y] == 5) {
        digitalWrite(ledpin[3], HIGH);
        speakerpin.play(NOTE_C4, 100);
        delay(200);
        digitalWrite(ledpin[3], LOW);
        inputArray[x] = 4;
        delay(250);
        Serial.print(" ");
        Serial.print(4);
        if (inputArray[x] != randomArray[x]) {
          fail();
        }
        x++;
      }
    }
  }
  delay(500);
  turn++; // Увеличение значения переменной turn
}

// Функция, используемая в случае, если игрок ошибся в последовательности
void fail() {
  for (int y = 0; y <= 2; y++) {
    // Индикация светодиодами
    // для обозначения ошибки
    digitalWrite(ledpin[0], HIGH);
    digitalWrite(ledpin[1], HIGH);
    digitalWrite(ledpin[2], HIGH);
    digitalWrite(ledpin[3], HIGH);
    speakerpin.play(NOTE_G3, 300);
    delay(200);
    digitalWrite(ledpin[0], LOW);
    digitalWrite(ledpin[1], LOW);
    digitalWrite(ledpin[2], LOW);
    digitalWrite(ledpin[3], LOW);
    speakerpin.play(NOTE_C3, 300);
    delay(200);
  }
  delay(500);
  turn = -1; // Сбросить значение переменной turn для начала новой игры
}

№9: Электронный привратник

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

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

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

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

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

const int quietKnock = 10;
const int loudKnock = 20;

Если вы постучите слишком слабо или слишком громко, стук не будет принят. Вам нужно сделать три «правильных» удара, чтобы заставить рычаг сервопривода переместился. Светодиоды, показанные на рисунке, служат индикаторами состояния блокировки:

  • Красный светодиод загорается, если стук неверный и рычаг сервопривода не перемещается;
  • Желтый светодиод мигает, когда регистрируется стук;
  • Зеленый светодиод загорается и сервопривод перемещается после трех правильных ударов;

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

Схема подключения

Обратите внимание, что пьезоизлучатель подключен через резистор с сопротивлением 1 МОм

Скетч

Для корректной компиляции скетча требуется библиотека Servo, но она уже встроена в Arduino IDE

Вначале вызывается библиотека Servo и настраивается контакт 9 платы Arduino, предназначенный для управления сервоприводом.

Светодиоды подключены к контактам 3, 4 и 5 платы Arduino и будут загораться в зависимости от правильности стука.

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

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

Красный светодиод загорается, если стук неправильный, а желтый — если правильный.

#include <Servo.h>
Servo servo9; // Контакт, к которому подключен контакт управления сервопривода
const int piezo = A0; // Контакт, к которому подключен пьезоизлучатель
const int yellowLed = 3; // Контакт, к которому подключен желтый светодиод
const int greenLed = 4; // Контакт, к которому подключен зеленый светодиод
const int redLed = 5; // Контакт, к которому подключен красный светодиод

int knockVal; // Значение силы стука
const int quietKnock = 10; // Установка минимального допустимого значения
const int loudKnock = 20; // Установка максимального допустимого значения
const int maxTime = 1400; // Сколько милисекунд дается на секретный стук
unsigned long firstKnockTime = 0;
boolean locked = true; // Логическая переменная
int numberOfKnocks = 0; // Значение количества стуков

void setup() {
  servo9.attach(9);
  // Перевод контактов, к которым подключены светодиоды, в режим вывода
  pinMode(yellowLed, OUTPUT);
  pinMode(greenLed, OUTPUT);
  pinMode(redLed, OUTPUT);
  Serial.begin(9600);
  digitalWrite(redLed, HIGH); // Подается питание на зеленый светодиод, если комбинация стуков корректна
  servo9.write(90);
  Serial.println("The box is locked!");
}

void loop() {
  knockVal = analogRead(piezo); // Значение стука, считываемое аналоговым контактом

  if (millis() - firstKnockTime > maxTime) { // Время на воспроизведение секретного стука истекло
    numberOfKnocks = 0;
  }

  if (knockVal > 0 && numberOfKnocks < 3) { // Проверка корректности количества ударов
    if (checkForKnock(knockVal) == true) {
      if (numberOfKnocks == 0) { // Устанавливаем время первого стука
        firstKnockTime = millis();
        Serial.println("Set firstKnockTime");
      }
      numberOfKnocks++;
      Serial.println(numberOfKnocks);
    }
  }
  if (numberOfKnocks >= 3) {
    if (locked) {
      openDoor();
    } else {
      closeDoor();
    }
  }
}

void closeDoor() {
  locked = true;
  servo9.write(90);
  delay(20);
  digitalWrite(greenLed, LOW);
  Serial.println("The box is locked!");
  numberOfKnocks = 0;
  delay(300);
  digitalWrite(redLed, HIGH);
}

void openDoor() {
  locked = false;
  servo9.write(0);
  delay(20);
  digitalWrite(redLed, LOW);
  Serial.println("The box is unlocked!");
  numberOfKnocks = 0;
  delay(300);
  digitalWrite(greenLed, HIGH);
}

boolean checkForKnock(int value) { // Проверка силы стука
  if (value >= quietKnock && value <= loudKnock) { // Значение силы стука должно находиться в указанном диапазоне
    digitalWrite(yellowLed, HIGH);
    delay(30);
    digitalWrite(yellowLed, LOW);
    Serial.print("Valid knock of value ");
    Serial.println(value);
    return true;
  } else {
    return false;
  }
}

№10: Лазер управляемый джойстиком

Сервоприводы — это небольшие моторы, которые могут точно поворачивать свои рычаги в любое положение в диапазоне от 0 до 180 градусов.

В этом проекте мы поместим сервоприводы в корпус с поворотным устройством (так называемый pan-and-tilt корпус), который имеет две оси вращения. Такой корпус значительно упрощает присоединение лазера к сервоприводу.

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

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

  • Красный - питание (плюс);
  • Черный или коричневый - заземление (минус);
  • Третий провод (обычно желтый, оранжевый или белый) - для передачи сигнала управления.

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

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

Обычно джойстик имеет пять штырьковых контактов:

  • VRx - сигнал по оси X;
  • VRy - сигнал по оси Y;
  • SW - сигнал нажатия кнопки;
  • GND - заземление (минус);
  • 5V - питание (плюс).

В нашем проекте, когда джойстик перемещается влево или вправо (по оси X), соответствующий сервопривод перемещается в этом направлении; когда джойстик перемещается вверх или вниз (по оси Y), другой сервопривод перемещается вверх или вниз.

Сборка корпуса Pan and tilt

Для этого проекта мы рекомендуем Вам собрать корпус pan-and-tilt, который идет в комплекте Вашего набора MAXI.

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

Обратите внимание! Сервоприводы в комплекте могут иметь более длинные установочные пластиковые рычаги, чем нужны для pan-and-tilt комплекта. Если вдруг произошла такая ситуация, не переживайте, Вы всегда можете укоротить рычаги ножницами.

Схема подключения

Скетч

Сначала в скетче вызывается библиотека Servo, а затем определяются два сервопривода для наклона и поворота с именами tilt и pan соответственно.

#include <Servo.h>
Servo tilt, pan; // Создание объекта servo
#define LED_PIN 3 // Пин лазерного модуля
#define BUTTON_PIN 2 // Пин кнопки джойстика
boolean buttonWasUp = true; // Показатель того, нажата кнопка или нет
boolean ledEnabled = false; // Показатель того, включен лазер или нет
int joyX = A0; // Аналоговый контакт, к которому подключен штырь VRx джойстика
int joyY = A1; // Аналоговый контакт, к которому подключен штырь VRy джойстика
int x, y; // Переменные для считываемых значений
void setup() {
  tilt.attach(9); // Подключение сервопривода tilt (наклона), подключенного к контакту 9, к объекту servo
  pan.attach(10); // Подключение сервопривода pan (поворота), подключенного к контакту 10, к объекту servo
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
}
void loop() {
  boolean buttonIsUp = digitalRead(BUTTON_PIN); // Считываем нажатие кнопки джойстика
  /*
    При нажатии на кнопку, мы можем получить ложный сигнал (дребезг), возникающий
    в момент замыкания/размыкания пластин кнопки.
    Следующая конструкция поможет нам избежать этого.
  */
  if (buttonWasUp && !buttonIsUp) {
    // Даём кнопке успокоиться...
    delay(10);
    // ... и считываем сигнал снова
    buttonIsUp = digitalRead(BUTTON_PIN);
    if (!buttonIsUp) { // Если кнопка все таки нажата, переключаем наш лазерный модуль в другое состояние (вкл - выкл)
      ledEnabled = !ledEnabled; 
      digitalWrite(LED_PIN, ledEnabled);
    }
  }
  buttonWasUp = buttonIsUp;
  x = map(analogRead(joyX), 0, 1023, 0, 180); // Шкалирование значений для применения к сервоприводам в виде значения в диапазоне от 0 до 180 градусов
  y = map(analogRead(joyY), 0, 1023, 0, 180);
  tilt.write(x); // Установка сервопривода в позицию, соответствующую шкалированному значению
  pan.write(y);
  delay(15); // Ожидание, пока сервоприводы занимают новое положение
}

№11: Дистанционное управление сервоприводами

В этом проекте Вы познакомитесь с ИК-датчиком и как с его помощью можно считать сигнал от пульта дистанционного управления!

Сначала мы научимся получать и декодировать сигналы пульта дистанционного управления с помощью инфракрасного датчика.

Когда Вы нажимаете кнопку на пульте, он передает некое числовое значение, которое принимает ИК-датчик. Значения различны для каждой кнопки. С помощью первого скетча мы научимся распознавать значение каждой кнопки, а затем присваивать их соответствующим контактам платы Arduino, чтобы управлять выходными устройствами — в данном случае двумя сервоприводами.

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

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

Чтобы вычислить коды кнопок необходимо Установить ИК-приемник на макетную плату, штырь OUT приемника подключить к контакту 11 платы Arduino, штырь GND - к контакту GND Arduino, а штырь VCC - к контакту 5V платы Arduino.

После этого загрузите следующий скетч в плату и откройте монитор порта.

Схема подключения для настройки

Скетч настройки

Для успешной компиляции скетча необходимо установить библиотеку IRremote (Скачать)

#include <IRremote.h> // Импорт библиотеки
int receiver = 11; // Контакт, к которому подключен ИК-датчик
IRrecv irrecv(receiver);
decode_results results;

void setup() {
  Serial.begin(9600); // Вывод кодов нажатых кнопок в монитор порта Arduino IDE
  irrecv.enableIRIn(); // Включение датчика
}

void loop() {
  if (irrecv.decode(&results)) { // Если получены входные данные, декодировать значение
    Serial.println(results.value, HEX); // Передача значения кнопки в монитор порта в шестнадцатеричном виде
    irrecv.resume(); // Получение следующего значения
  }
}

Вначале код скетча обращается к библиотеке IRremote, с помощью которой перехватывается сигнал с ИК-датчика и полученные данные передаются на плату Arduino. ИК-датчику назначается контакт 11 платы Arduino, и код скетча формирует канал связи со средой разработки Arduino. Теперь при нажатии той или иной кнопки на пульте, её значение сразу отображается в окне монитора порта.

Скетч продолжает работать в зацикленном режиме, ожидая нажатия кнопок и передавая соответствующие значения в среду разработки Arduino.

Откройте окно монитора порта в среде разработки Arduino IDE. Направьте пульт дистанционного управления на ИК-датчик и попробуйте нажать несколько кнопок. Их значения отобразятся в окне монитора порта в шестнадцатеричном виде.

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

Сборка основного проекта

  • Обязательно проверьте, что в коде ниже Вы указали значения, полученные при помощи скетча настройки, а не те, которые приведены в этом руководстве;
  • При изменении значений на собственные не удаляйте символы 0x, которые определяют начало шестнадцатеричного кода;
  • Например, если мы получили значение кнопки FFA05F, то в скетче его необходимо записать следующим образом: unsigned long Value1 = 0xFFA05F.

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

Полный код скетча

#include <Servo.h> // Импорт библиотеки Servo
#include <IRremote.h> // Импорт библиотеки IRremote

unsigned long Value1 = 0x8808541; // Измените это значение на собственное
unsigned long Value2 = 0x8808440; // Измените это значение на собственное
unsigned long Value3 = 0x8810089; // Измените это значение на собственное
unsigned long Value4 = 0x880834F; // Измените это значение на собственное

int RECV_PIN = 11;
IRrecv irrecv(RECV_PIN);
decode_results results;
Servo servo1;
Servo servo2;

void setup() {
  Serial.begin(9600);
  irrecv.enableIRIn(); //Включение ИК -датчика
  servo1.attach(10); // Контакт, к которому подключен сервопривод 1
  servo2.attach(9); // Контакт, к которому подключен сервопривод 2
}
void loop() {
  if (irrecv.decode(&results)) {
    Serial.println(results.value, HEX);
    irrecv.resume(); // Получение следующего значения
  }
  if (results.value == Value1) {
    /*
      Если полученный код соответствует значению Value1,
      управлять соответствующим сервоприводом в указанном
      направлении
    */
    servo1.write(160);
  } else if (results.value == Value2) {
    /*
      Если полученный код соответствует значению Value2,
      управлять соответствующим сервоприводом в указанном
      направлении
    */
    servo1.write(70);
  } else if (results.value == Value3) {
    servo2.write(70);
  } else if (results.value == Value4) {
    servo2.write(160);
  }
}

№12: Вывод данных на экран

Пора нам научится выводить данные не только в монитор порта, но и на реальный дисплей!

Жидкокристаллический дисплей (ЖКД, LCD) сделан из двух подложек поляризационного материала с раствором жидких кристаллов между ними. Ток, проходящий через раствор, создает изображение или, в нашем случае, символы.

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

Схема подключения

Скетч

В данном проекте мы будем использовать библиотек LiquidCrystal, которая уже предустановлена в Arduino IDE.

#include <LiquidCrystal.h>
// Инициализация библиотеки номерами контактов интерфейса
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);

String text = "arduinko.ru"; // Строка, которую выводим на дисплей
unsigned int textLength = text.length(); // Высчитываем длину строки

void setup() {
  // Установка количества столбцов и строк ЖК-дисплея
  lcd.begin(16, 2);
  // Вывод сообщения на ЖК-дисплей
  lcd.print(text);
  delay(1000);
}

void loop() {
  // Прокрутка на 11 позиций (длина строки) влево, чтобы скрыть строку
  for (int positionCounter = 0; positionCounter < textLength; positionCounter++) {
    // Прокрутка на 1 позицию влево
    lcd.scrollDisplayLeft();
    // Задержка
    delay(150);
  }
  // Прокрутка на 27 позиций (длина дисплея + длина строки) вправо, чтобы скрыть строку
  for (int positionCounter = 0; positionCounter < 16 + textLength; positionCounter++) {
    // Прокрутка на 1 позицию вправо
    lcd.scrollDisplayRight();
    // Задержка
    delay(150);
  }
  // Прокрутка на 16 позиций (длина дисплея) влево, чтобы выровнять строку по центру
  for (int positionCounter = 0; positionCounter < 16; positionCounter++) {
    // Прокрутка на 1 позицию влево
    lcd.scrollDisplayLeft();
    // Задержка
    delay(150);
  }
  // Задержка в конце полного цикла
  delay(1000);
}

№13: Метеостанция

Теперь Вы знаете, как выводить текст на LCD дисплей, пора этим воспользоваться и вывести реальные показания с датчиков устройства.

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

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

В реальных условиях, для достижения наилучших результатов, рекомендуется установить датчик снаружи помещения на открытом воздухе. А ЖК-дисплей расположить в помещении либо упаковать в прозрачный непромокаемый чехол или корпус, чтобы защитить от влияния внешних факторов.

Схема подключения

Скетч

Для корректной компиляции скетча Вам потребуется библиотека DHT (Скачать)

Библиотека DHT позволяет управлять работой датчика, а библиотека LiquidCrystal (предустановлена в Arduino IDE) выводит полученные данные на дисплей.

#include <LiquidCrystal.h>
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);

#include <DHT.h> // Вызов библиотеки DHT
#define DHTPIN 4 // Контакт, к которому подключен датчик DHT
#define DHTTYPE DHT11 // Определение типа модуля DHT
DHT dht(DHTPIN, DHTTYPE); // Команда библиотеке DHT.h

void setup() {
  Serial.begin(9600);
  dht.begin();  // Включение датчика
  lcd.begin(16, 2); // ЖК-дисплей отображает 16 символов в 2 строках
}

void loop() {
  float h = dht.readHumidity();  // Значение влажности

  float t = dht.readTemperature(); // Значение температуры

  if (isnan(t) || isnan(h)) { // Проверка работоспособности датчика DHT
    lcd.setCursor(0, 0);
    // Если датчик DHT не работает, выводится это сообщение
    lcd.print("Failed to read from DHT");
  } else {
    // В противном случае на дисплей выводятся результаты замеров
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Humidity: ");
    lcd.print(h);
    lcd.print("%");
    lcd.setCursor(0, 1);
    lcd.print("Temp: ");
    lcd.print(t);
    lcd.print("C");
  }
  delay(3000);
}

№14: Игра на скорость

Создадим игру, которая проверит Вашу реакцию!

Как играть

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

Пьезоизлучатель будет отвлекать Вас, издавая звуки в случайном порядке. Если Вы отпустите кнопку слишком рано, на ЖК-дисплее появится соответствующее сообщение, и вам придется начать игру заново.

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

Схема подключения

Скетч

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

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

PSE = random(500, 1200);
#include <LiquidCrystal.h>
LiquidCrystal lcd(7, 8, 9, 10, 11, 12);

int LEDR = 4; // Контакт, к которому подключен красный выход светодиодного модуля
int LEDB = 3; // Контакт, к которому подключен синий выход светодиодного модуля
int LEDG = 2; // Контакт, к которому подключен зеленый выход светодиодного модуля
int Button = 6; // Контакт, к которому подключена кнопка
int COLOR; // Переменная цвета

int Beep;
int PSE; // Переменная паузы
int TME; // Время
int RTME = 0; // Время реакции

void setup() {
  lcd.begin(16, 2);
  pinMode(LEDR, OUTPUT); // Перевод контактов светодиода в режим вывода
  pinMode(LEDB, OUTPUT);
  pinMode(LEDG, OUTPUT);
  pinMode(Button, INPUT); // Перевод контактов кнопки в режим ввода
  digitalWrite(LEDR, HIGH); // Включение всех цветов светодиода
  digitalWrite(LEDB, HIGH);
  digitalWrite(LEDG, HIGH);
}

void loop() {
  lcd.clear(); // Очистка дисплея
  lcd.print("Hold Button to"); // Вывод сообщения на ЖК-дисплей
  lcd.setCursor(0, 1); // Переход на вторую строку
  lcd.print("start.");
  while (digitalRead(Button) == LOW) { // Тест не начнется, пока не будет нажата (и удержана) кнопка
    tone(13, 1200, 30);
    delay(1400);
    noTone(13);
  }
  lcd.clear();
  digitalWrite(LEDR, LOW); // Прекращение исходного свечения
  digitalWrite(LEDB, LOW);
  digitalWrite(LEDG, LOW);
  randomSeed(analogRead(0)); // Случайный шум с контакта 0
  COLOR = random(1, 4); // Генерация случайного цвета
  PSE = random(500, 1200); // Установка паузы случайной длительности между свечениями.

  // Повторять этот цикл, пока цвет зеленый или синий, а кнопка нажата
  while (COLOR != 1 && digitalRead(Button) == HIGH) {
    digitalWrite(LEDG, LOW);
    digitalWrite(LEDB, LOW);
    delay(PSE);
    randomSeed(analogRead(0));
    Beep = random(1, 4); // Случайный сигнал пьезоизлучателя (звучит 1 - 3 раз)

    PSE = random(750, 1200); // Пауза случайной длительности между свечениями (усиливает эффект неожиданности)

    if (Beep == 1) {
      tone(13, 1600, 350);
      delay(750);
      noTone(13);
    }
    if (COLOR == 2) {
      digitalWrite(LEDG, HIGH);
    }
    if (COLOR == 3) {
      digitalWrite(LEDB, HIGH);
    }
    delay(PSE);
    randomSeed(analogRead(0));
    COLOR = random(1, 4); // Выбор случайного цвета
  }
  // Выполнить цикл, если цвет красный
  if (COLOR == 1 && digitalRead(Button) == HIGH) {
    digitalWrite(LEDG, LOW);
    digitalWrite(LEDB, LOW);
    delay(PSE);
    TME = millis(); // Запись времени с момента запуска программы
    digitalWrite(LEDR, HIGH);
    while (digitalRead(Button) == HIGH) { // Продолжение, пока кнопка не отпущена, с записью времени реакции
      delay(1);
    }
    lcd.display();
    RTME = millis() - TME; // Время реакции в мс
    lcd.print("Reaction Time:");
    lcd.setCursor(0, 1);
    lcd.print(RTME); // Вывод значения на ЖК-дисплей
  }
  // Выполнить, если цвет не красный, но кнопка отпущена
  if (COLOR != 1) {
    lcd.print("Released too");
    lcd.setCursor(0, 1); // Переход на вторую строку
    lcd.print("soon!!!");
    tone(13, 3000, 1500);
    delay(500);
    noTone(13);
  }
  // Игра не перезапуститься, пока кнопка не будет однократно нажата
  while (digitalRead(Button) == LOW) {
    delay(10);
  }
  digitalWrite(LEDR, HIGH); // Сброс всех цветов к исходному состоянию
  digitalWrite(LEDB, HIGH);
  digitalWrite(LEDG, HIGH);
  lcd.clear();
  lcd.print("Hold Button to");
  lcd.setCursor(0, 1);
  lcd.print("start.");
  int Time = 0;
  delay(1000);
}

№15: Увеличение количества выходов Arduino

В этом проекте мы познакомимся с выходным сдвиговым регистром 74HC595. Сдвиговый регистр - это небольшая интегральная схема с последовательным логическим счетчиком, который позволяет увеличить количество выходов микроконтроллера Arduino путем «сдвига» и хранения данных. Звучит сложно? На самом деле это не так и скоро мы в этом убедимся.

Сдвиговый регистр имеет 16 контактов; на одном конце регистра вы увидите точку или полукруг, которая отмечает контакт 1 слева. Контакты пронумерованы против часовой стрелки. На рис. показана распиновка, а в таблице описывается функция каждого контакта.

7O4UYi4lmC_vakjR0aKoacPTOQtCEUQtP-u7PzZVqhrmFPiZ9dGZ17x7Nhw1_6JJbOtZ0iFsZLEEQ7nnW5h7rpINkxkyvqDiCGrrpwq1EVJzdAq-R1oNt8DNYUGhxE0mGHlke_rf
Контакт Обозначение Предназначение
Контакт 1-7,15 Q0-Q7 Параллельные выходы
Контакт 8 GND Заземление VSS
Контакт 9 Q7 Выход для последовательного соединения регисторов
Контакт 10 MR Сброс значений регистра при получении сигнала LOW
Контакт 11 SH_CP Вход для тактовых импульсов (контакт CLOCK)
Контакт 12 ST_CP Синхронизация выходов (контакт LATCH)
Контакт 13 OE Вход для переключения состояния выходов из высокоомного в рабочее
Контакт 14 DS Вход для последовательных данных (контакт DATA)
Контакт 16 VCC Питание

Схема подключения

Подготовительный скетч

// Определяем пины (тактовая линия - clock, данные - data, защёлка - latch):
#define clock 13
#define data 12
#define latch 10

void setup(){
  // Обозначаем наши пины как выходы и сразу ставим защёлке высокий уровень, чтобы регистр не принимал сигналов
  pinMode(clock, OUTPUT);
  pinMode(data, OUTPUT);
  pinMode(latch, OUTPUT);
  digitalWrite(latch, HIGH); // Теперь регистр не принимает сигналов
}

void loop() {
  // Скоро мы добавим сюда инструкции
}

Отправляем данные на регистр

Давайте попробуем что-нибудь отправить на регистр в цикле loop():

1. Ставим LOW на защёлку (Начинаем передачу данных. Теперь регистр принимает сигналы с Arduino).

digitalWrite(latch, LOW);

2. Теперь отправляем данные (т.е. отправляем байт в цифровом или двоичном виде. В двоичном проще, т.к. каждый из 8 битов отвечает за свой разряд в регистре. Проще сориентироваться):


shiftOut(data, clock, LSBFIRST, 0b10000000);

3. В конце выставляем HIGH на защёлку (заканчиваем передавать данные).

digitalWrite(latch, HIGH);

Управление одним светодиодом

// Определяем пины (тактовая линия - clock, данные - data, защёлка - latch):
#define clock 13
#define data 12
#define latch 10

void setup() {
  // Обозначаем наши пины как выходы и сразу ставим защёлке высокий уровень, чтобы регистр не принимал сигналов
  pinMode(clock, OUTPUT);
  pinMode(data, OUTPUT);
  pinMode(latch, OUTPUT);
  digitalWrite(latch, HIGH); // Теперь регистр не принимает сигналов
}

void loop() {
  digitalWrite(latch, LOW); // Начинаем передачу данных, теперь регистр принимает сигналы с Arduino
  shiftOut(data, clock, LSBFIRST, 0b10000000); // Должен загореться первый светодиод
  digitalWrite(latch, HIGH); // Закончили передавать данные
}

Загружаем скетч в плату Arduino и смотрим на результат - загорелся первый светодиод.

Если у Вас загорелся не первый, а последний светодиод, то в функции shiftOut поменяйте LSBFIRST на MSBFIRST и всё заработает, как надо.

Немного поиграем (Бегущий огонек)

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

// Определяем пины (тактовая линия - clock, данные - data, защёлка - latch):
#define clock 13
#define data 12
#define latch 10

byte path[4]= { // Массив наших байтов
  0b10000001,
  0b01000010,
  0b00100100,
  0b00011000
};

void setup() {
  // Обозначаем наши пины как выходы
  pinMode(clock, OUTPUT);
  pinMode(data, OUTPUT);
  pinMode(latch, OUTPUT);
  digitalWrite(latch, HIGH);
}

void loop() {
  for (int i = 0; i < 4; i++) {
    digitalWrite(latch, LOW);
    shiftOut(data, clock, LSBFIRST, path[i]);
    digitalWrite(latch, HIGH);
    delay(50);
  }
  for (int i = 0; i < 4; i++) {
    digitalWrite(latch, LOW);
    shiftOut(data, clock, LSBFIRST, path[3-i]);
    digitalWrite(latch, HIGH);
    delay(50);
  }
}

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

Создадим функцию sendbyte, которая будет отправлять данные на регистр

void sendbyte(byte value) {
  digitalWrite(latch, LOW);
  shiftOut(data, clock, LSBFIRST, value);
  digitalWrite(latch, HIGH);
}

Эта функция отправляет регистру состояние всех разрядов сразу (как мы делали выше), поэтому мы можем упростить наш скетч. Давайте включим первый и последний светодиоды:

// Определяем пины (тактовая линия - clock, данные - data, защёлка - latch):
#define clock 13
#define data 12
#define latch 10

void setup() {
  // Обозначаем наши пины как выходы и сразу ставим защёлке высокий уровень, чтобы регистр не принимал сигналов
  pinMode(clock, OUTPUT);
  pinMode(data, OUTPUT);
  pinMode(latch, OUTPUT);
  digitalWrite(latch, HIGH); // Теперь регистр не принимает сигналов
}

void loop() {
  sendbyte(0b10000001); // Теперь мы можем использовать созданную нами функцию, чтобы включить первый и последний светодиоды
}

void sendbyte(byte value) { // Определяем нашу функцию sendbyte
  digitalWrite(latch, LOW);
  shiftOut(data, clock, LSBFIRST, value);
  digitalWrite(latch, HIGH);
}

Сдвиговый регистр как расширитель портов Arduino

Мы написали отличную функцию, которая упрощает нам работу со сдвиговым регистром, но, чтобы использовать регистр как расширитель портов, нужно управлять каждым разрядом по-отдельности (аналогично функции digitalWrite).

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

boolean states[8];

Каждая переменная в данном массиве обозначает свой разряд.

Теперь напишем функцию sendpin, которая будет принимать 2 значения: номер разряда, и уровень, который нам надо этому разряду приписать: высокий(HIGH) или низкий(LOW).

void sendpin(int pin, boolean state) {
pin--; // Из-за того, что счёт начинается с нуля, нам придётся называть первый пин нулевым, второй первым и т.д.
states[pin] = state; // Отмечаем изменения в нашем массиве

// Теперь надо сформировать из 8 битов байт и отправить его на регистр.
byte value = 0; // Тот байт, который будем отправлять. (по умолчанию его нужно сделать нулём)
byte add = 1; // Это переменная, которая будет хранить в себе байт текущего разряда. для первого разряда это байт 1 (0b10000000)
for (int i=0; i < 8; i++) { // Пройдемся по всему массиву и сформируем байт
  // Итак, каждый раз мы проверяем очередной разряд в массиве. Если он должен иметь высокий уровень, то мы прибавляем к value add и переходим на следующий разряд в цепочке (как бы сдвигаемся на разряд выше (левее). Т. е., в двоичном коде всё просто: было так: 0b01000000; сдвинули единичку влево и получилось так: 0b10000000. А вот в цифровом виде всё по-другому. Сдвиг влево аналогичен умножению на 2 (а вправо -делению на 2))
  if (states[i] == HIGH) {
    value += add;
  }
  add *= 2;
}
// Посылаем данные на регистр
digitalWrite(latch, LOW);
shiftOut(data, clock, LSBFIRST, value);
digitalWrite(latch, HIGH)}

Наша функция для управления пинами написана и мы почти готовы к испытаниям!

Функция очистки регистра

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

void cleanreg() {
  for (int i=0; i < 8; i++) {
    states[i]=LOW;
  }
  digitalWrite(latch, LOW);
  shiftOut(data, clock, LSBFIRST, 0);
  digitalWrite(latch, HIGH);
}

Скетч (включаем 2, 4, 6, и 8 разряды отдельно)

// Определяем пины (тактовая линия - clock, данные - data, защёлка - latch):
#define clock 13
#define data 12
#define latch 10

boolean states[8]; // Значения нашего байта

void setup() {
  // Обозначаем наши пины как выходы
  pinMode(clock, OUTPUT);
  pinMode(data, OUTPUT);
  pinMode(latch, OUTPUT);
  cleanreg(); // Очищаем регистр
}

void loop() {
  // Включим 2, 4, 6, и 8 разряды отдельно
  sendpin(2, HIGH);
  sendpin(4, HIGH);
  sendpin(6, HIGH);
  sendpin(8, HIGH);
}

void sendpin(int pin, boolean state) {
  pin--;
  states[pin] = state;

  byte value = 0;
  byte add = 1;
  for (int i = 0; i < 8; i++) {
    if (states[i] == HIGH) {
      value += add;
    }
    add *= 2;
  }

  sendbyte(value);
}

void sendbyte(byte value) {
  digitalWrite(latch, LOW);
  shiftOut(data, clock, MSBFIRST, value); // Обратите внимание, что мы поменяли параметр LSBFIRST на MSBFIRST для того, чтобы светодиоды светились у нас в том же порядке, что и в предыдущих скетчах.
  digitalWrite(latch, HIGH);
}

void cleanreg() { // Функция очистки регистра
  for(int i = 0; i < 8; i++) {
    states[i]=LOW;
  }
  digitalWrite(latch, LOW);
  shiftOut(data, clock, LSBFIRST, 0);
  digitalWrite(latch, HIGH);
}

Таким образом, задействовав всего три пина Arduino, мы получили целых 8 выходов!

№16: Датчик вторжения

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

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

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

Схема подключения

Скетч

Вы можете изменить область действия дальномера в следующей строке кода скетча:

if (distance <= 15)

Ножка Trig дальномера подключена к контакту 12 платы Arduino и служит для передачи ультразвукового сигнала. Когда сигнал достигает объекта, он возвращается к дальномеру и передается на контакт 13 платы Arduino. Разница во времени между двумя сигналами позволяет определить расстояние от дальномера до объекта.

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

Для компиляции скетча необходимо установить библиотеку NewPing (Скачать)

#include <NewPing.h> // Вызов библиотеки NewPing
#include <Servo.h> // Вызов библиотеки Servo
#define trigPin 12 // Контакт Trig подключен к контакту 12 Arduino
#define echoPin 13 // Контакт Echo подключен к контакту 13 Arduino

#define MAX_DISTANCE 500
NewPing sonar(trigPin, echoPin, MAX_DISTANCE); // Установка библиотеки
int greenLed = 3, redLed = 2; // Назначение зеленого светодиода контакту 3, а красного - контакту 2
int pos = 20;
Servo myservo;

void setup() {
  Serial.begin (9600);
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  pinMode(greenLed, OUTPUT);
  pinMode(redLed, OUTPUT);
  myservo.attach(9); // Сервопривод подключен к контакту 9
}

void loop() {
  int duration, distance, pos = 0, i;
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  duration = pulseIn(echoPin, HIGH);
  // Контакт Trig посылает пинг
  // Контакт Echo получает ответ
  distance = (duration / 2) / 29.1;
  Serial.print(distance);
  Serial.println(" cm");
  // Если дальномер обнаруживает объект в пределах 15 см
  if (distance <= 15) {
    digitalWrite(greenLed, LOW);
    // Выключение зеленого светодиода
    digitalWrite(redLed, HIGH);
    // Включение красного светодиода
    myservo.write(180);
    // Поворот сервопривода на 180 градусов
    delay(450);
    digitalWrite(redLed, LOW);
    // Выключение красного светодиода
    myservo.write(90);
    delay(450);
    digitalWrite(redLed, HIGH);
    myservo.write(0);
    delay(450);
    digitalWrite(redLed, LOW);
    myservo.write(90);
  }
  // Иначе
  else {
    digitalWrite(redLed, LOW); // Выключение красного светодиода
    digitalWrite(greenLed, HIGH); // Включение зеленого светодиода
    myservo.write(90);
  }
  delay(450);
}

№17: Датчик движения

Этот проект основан на датчике движения и может быть адаптирован под огромное количество проектов!

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

Схема подключения

Скетч

Обратите внимание! Для корректной работы датчика HC-SR505, необходимо чтобы он разогрелся в течении одной минуты.

Обратите внимание! У инфракрасного датчика движения HC-SR505 при каждом срабатывании на выходе появляется высокий импульс длительностью 6 - 12 секунд. Мы напишем наш скетч таким образом, чтобы звуковой сигнал срабатывал один раз при обнаружении движения, а светодиод будет гореть все время, пока не пройдет это время и движение не завершится.

В скетче контакт 13 платы Arduino устанавливается как выход для светодиода, контакт 2 — как вход для датчика движения, а контакт 10 — как выход для пьезоизлучателя. Когда срабатывает датчик движения, на Arduino посылается сигнал HIGH, который, в свою очередь, инициирует включение светодиода и пьезоизлучателя.

int ledPin = 13; // Контакт, к которому подключен светодиод
int inputPin = 2; // Контакт, к которому подключен датчик движения
int pirState = LOW; // Начало с состояния датчика движения LOW (нет движения)
int val = 0; // Переменная для считывания состояния контакта
int pinSpeaker = 10; // Контакт, к которому подключен пьезоизлучатель

void setup() {
  pinMode(ledPin, OUTPUT); // Перевод контакта светодиода в режим выхода
  pinMode(inputPin, INPUT); // Перевод контакта датчика в режим входа
  pinMode(pinSpeaker, OUTPUT);
  Serial.begin(9600);
}
void loop() {
  val = digitalRead(inputPin);  // Считывание входного значения датчика
  if (val == HIGH) {            // Проверка, является ли значение входа HIGH
    digitalWrite(ledPin, HIGH); // Если нет, включается светодиод
    if (pirState == LOW) {      // Передача на монитор порта, если обнаружено движение
      Serial.println("Motion detected!");
      playTone(300, 160); // Воспроизводим звук один раз при первом обнаружении движения
      pirState = HIGH;
    }
    delay(1000);
  } else {
    digitalWrite(ledPin, LOW);  // Если входное значение не HIGH,
    // выключение светодиода
    playTone(0, 0);
    if (pirState == HIGH) {
      Serial.println("Motion ended!");
      pirState = LOW;
    }
  }
}

void playTone(long duration, int freq) {
  duration *= 1000;
  int period = (1.0 / freq) * 1000000;
  int i = 0;
  while (i < 3) {
    long elapsed_time = 0;
    while (elapsed_time < duration) {
      digitalWrite(pinSpeaker, HIGH);
      delayMicroseconds(period / 2);
      digitalWrite(pinSpeaker, LOW);
      delayMicroseconds(period / 2);
      elapsed_time += (period);
    }
    delay(300);
    i += 1;
  }
}

№18: Ввод с клавиатуры

Может организуем открытие двери по паролю? Приступим!

В этом проекте используется 4 × 4 мембранная клавиатура со шлейфом из восьми проводов снизу. К устройству подключен сервопривод, который открывает замок.

Клавиатура представляет собой группу кнопок, которые возвращают число или символ в зависимости от того, какая кнопка нажата. Для клавиатуры, обращенной лицевой стороной, провода нумеруются от 1 до 8 слева направо. Первые четыре провода соответствуют строкам кнопок, а последние четыре — столбцам.

Мы подключим клавиатуру к сервоприводу и двум светодиодам, чтобы создать систему защиты. Чтобы получить доступ, введите пароль и нажмите кнопку «звездочка» (*) для подтверждения. Если введенный код соответствует указанному в скетче паролю, замигает зеленый светодиод и сервопривод повернется на 90 градусов. Если код неправильный, загорится красный светодиод.

Используйте кнопку с символом хэша (#) для повторного ввода пароля.

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

Схема для проверки клавиатуры

Скетч для проверки клавиатуры

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

Для компиляции скетча необходима библиотека Keypad (Скачать)

#include <Keypad.h>

const byte ROWS = 4; // Установка 4 строк
const byte COLS = 4; // Установка 4 столбцов

char keys[ROWS][COLS] = { // Определение схемы кнопок
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};

byte rowPins[ROWS] = { 9, 8, 7, 6 }; // Контакты, к которым подключена клавиатура ROW0, ROW1, ROW2 и ROW3
byte colPins[COLS] = { 5, 4, 3, 2, }; // Контакты, к которым подключена клавиатура COL0, COL1 и COL2

// Создание клавиатуры
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

void setup() {
  Serial.begin(9600);
}

void loop() {
  char key = keypad.getKey(); // Считываем нажатую клавишу
  if (key != NO_KEY) {
    Serial.println(key); // Выводим значение нажатой клавиши в монитор порта
  }
}

Загрузите этот код в плату и откройте окно монитора порта в среде разработки Arduino IDE.

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

Схема подключения

Финальный скетч

Сначала в коде скетча вызываются библиотеки Keypad, Servo и Password.

Затем мы настраиваем восемь контактов, принимающих ввод с клавиатуры, и определяем контакты 11 и 12 платы Arduino для управления светодиодами, а контакт 13 — для управления сервоприводом.

Плата Arduino ждет ввода пароля с клавиатуры и нажатия кнопки * для подтверждения ввода. Как только вы нажали кнопку *, скетч проверяет соответствие пароля записанному в коде. Если пароль не соответствует записи, контакт красного светодиода переводится в режим HIGH, и светодиод начинает светиться; если пароль соответствует записи, контакт зеленого светодиода переводится в режим HIGH, и светодиод начинает светиться, а сервопривод повернется.

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

Password password = Password("2468");

Пароль по умолчанию — 2468.

Для компиляции скетча необходимы библиотеки Keypad (Скачать) и Password (Скачать)

#include <Password.h>
#include <Keypad.h>
#include <Servo.h>
Servo myservo;
Password password = Password("2468"); // Записанный пароль

const byte ROWS = 4; // Установка 4 строк
const byte COLS = 4; // Установка 4 столбцов

char keys[ROWS][COLS] = { // Определение схемы кнопок
  {'1', '2', '3', 'A'},
  {'4', '5', '6', 'B'},
  {'7', '8', '9', 'C'},
  {'*', '0', '#', 'D'}
};

byte rowPins[ROWS] = { 9, 8, 7, 6 }; // Контакты, к которым подключена клавиатура ROW0, ROW1, ROW2 и ROW3
byte colPins[COLS] = { 5, 4, 3, 2, }; // Контакты, к которым подключена клавиатура COL0, COL1 и COL2

// Создание клавиатуры
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);

void setup() {
  Serial.begin(9600);
  delay(200);
  pinMode(11, OUTPUT); // Перевод контакта зеленого светодиода в режим выхода
  pinMode(12, OUTPUT); // Перевод контакта красного светодиода в режим выхода
  myservo.attach(13); // Контакт, к которому подключен сервопривод
  keypad.addEventListener(keypadEvent); // Добавление слушателя событий для обнаружения нажатия кнопок
}

void loop() {
  keypad.getKey();
  myservo.write(0);
}

void keypadEvent(KeypadEvent eKey) {
  switch (keypad.getState()) {
    case PRESSED:
      Serial.print("Pressed: ");
      Serial.println(eKey);
      switch (eKey) {
        case '*': checkPassword(); break;
        case '#': password.reset(); break;
        default: password.append(eKey);
      }
  }
}

void checkPassword() {
  if (password.evaluate() ) { // Если пароль правильный...
    Serial.println("Success");
    myservo.write(90); // Поворот сервопривода на 90 градусов
    digitalWrite(11, HIGH); // Включение зеленого светодиода
    delay(5000);// Задержка 5 секунд
    digitalWrite(11, LOW); // Выключение зеленого светодиода
  } else { // Если пароль неправильный...
    Serial.println("Wrong");
    myservo.write(0);
    digitalWrite(12, HIGH); // Включение красного светодиода
    delay(5000); // Задержка 5 секунд
    digitalWrite(12, LOW); // Выключение красного светодиода
  }
}

№19: Бесконтактный электронный пропуск

А не хотите ли сделать свою собственную пропускную систему или домофон? Сейчас разберемся как!

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

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

Существует два типа RFID-систем: пассивная и активная. Каждая RFID-система использует радиоволны определенной частоты для обмена сигналами между RFID-модулем и метками или картами. Этот сигнал содержит уникальный код метки или карты, и если RFID-модуль распознает этот код, он реагирует соответствующим образом.

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

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

Мы будем использовать пассивную RFID-систему, состоящую из RFID-модуля, пустой карты или брелока.

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

Схема для получения UID карты

Скетч для получения UID карты

Пожалуйста, предварительно, установите библиотеку MFRC522 (Скачать)

#include <SPI.h>
#include <MFRC522.h>

#define RST_PIN 9
#define SS_PIN 10
unsigned long uidDec, uidDecTemp; // Для храниения номера метки в десятичном формате
MFRC522 rfid(SS_PIN, RST_PIN); // Инициализация RFID библиотеки

void setup() {
  Serial.begin(9600);
  SPI.begin(); // Начало последовательной связи между RFID-модулем и компьютером
  rfid.PCD_Init(); // Инициализация RFID-модуля
}

void loop() {
  // Сканируем наличие карты
  if ( ! rfid.PICC_IsNewCardPresent()) {
    return;
  }

  // Проверяем, что можем считать данные с карты
  if ( ! rfid.PICC_ReadCardSerial()) {
    return;
  }

  uidDec = 0;

  // Выдача серийного номера метки.
  for (byte i = 0; i < rfid.uid.size; i++) {
    uidDecTemp = rfid.uid.uidByte[i];
    uidDec = uidDec * 256 + uidDecTemp;
  }
  Serial.println("Card UID: ");
  Serial.println(uidDec); // Выводим UID метки в консоль.
}

После загрузки скетча в Вашу плату, откройте монитор порта и поднесите карту или брелок к считывателю. Вы увидите UID Вашей карты. Запишите это значение, оно нам еще пригодится.

Схема подключения

Финальный скетч

Скетч начинается с вызова библиотек SPI, RFID, Servo, Pitches и Wire для обеспечения обмена данными между Arduino, RFID-модулем и сервоприводом.

В показанной ниже строке нужно добавить шестнадцатеричное значение вашей карты:

unsigned long card = 3009530634;

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

Для успешной компиляции скетча необходимо установить библиотеки MFRC522 (Скачать) и Pitches (Скачать)

#include <SPI.h>
#include <MFRC522.h>
#include <Servo.h>
#include "pitches.h"
#include <Wire.h>
#define SS_PIN 10
#define RST_PIN 9
unsigned long uidDec, uidDecTemp;  // Для хранения номера метки в десятичном формате
MFRC522 rfid(SS_PIN, RST_PIN); // Инициализация RFID библиотеки

// Замените строку ниже на код вашей карты в шестнадцатеричном виде
unsigned long card = 3009530634;

// Определение мелодий для успешной авторизации и отказа
int access_melody[] = {NOTE_G4, 0, NOTE_A4, 0, NOTE_B4, 0, NOTE_A4, 0, NOTE_B4, 0, NOTE_C5, 0};
int access_noteDurations[] = {8, 8, 8, 8, 8, 4, 8, 8, 8, 8, 8, 4};
int fail_melody[] = {NOTE_G2, 0, NOTE_F2, 0, NOTE_D2, 0};
int fail_noteDurations[] = {8, 8, 8, 8, 8, 4};

int LED_access = 2; // Контакт, к которому подключен зеленый светодиод
int LED_intruder = 3; // Контакт, к которому подключен красный светодиод

int speaker_pin = 8; // Контакт, к которому подключен пьезоизлучатель
int servoPin = 5; // Контакт, к которому подключен сервопривод

Servo doorLock; // Определение сервопривода

void setup() {
  doorLock.attach(servoPin);  // Назначение контакта сервопривода
  Serial.begin(9600);
  SPI.begin(); // Начало последовательной связи между RFID-модулем и компьютером
  rfid.PCD_Init(); // Инициализация RFID-модуля
  delay(1000);
  pinMode(LED_access, OUTPUT);
  pinMode(LED_intruder, OUTPUT);
  pinMode(speaker_pin, OUTPUT);
  pinMode(servoPin, OUTPUT);
  doorLock.write(180);
}
void loop() {
  // Сканируем наличие карты
  if ( ! rfid.PICC_IsNewCardPresent()) {
    return;
  }

  // Проверяем, что можем считать данные с карты
  if ( ! rfid.PICC_ReadCardSerial()) {
    return;
  }

  uidDec = 0;

  // Выдача серийного номера метки.
  for (byte i = 0; i < rfid.uid.size; i++) {
    uidDecTemp = rfid.uid.uidByte[i];
    uidDec = uidDec * 256 + uidDecTemp;
  }

  Serial.println(uidDec); // Выведем UID карты в монитор порта, для тестирования

  if (uidDec == card) { // Обнаружение карты с правами доступа
    Serial.println("Hello!");
    digitalWrite(LED_access, HIGH);
    doorLock.write(0);
    for (int i = 0; i < 12; i++) {
      // Воспроизведение приветственной мелодии
      int access_noteDuration = 1000 / access_noteDurations[i];
      tone(speaker_pin , access_melody[i], access_noteDuration);
      int access_pauseBetweenNotes = access_noteDuration * 1.30;
      delay(access_pauseBetweenNotes);
      noTone(speaker_pin);
    }
    doorLock.write(180);
    digitalWrite(LED_access, LOW);
  } else { // Если карта не распознана
    Serial.println("Card not recognized! Contact administrator!");
    digitalWrite(LED_intruder, HIGH); // Включение красного светодиода
    for (int i = 0; i < 6; i++) {
      // Воспроизведение мелодии отказа
      int fail_noteDuration = 1000 / fail_noteDurations[i];
      tone(speaker_pin, fail_melody[i], fail_noteDuration);
      int fail_pauseBetweenNotes = fail_noteDuration * 1.30;
      delay(fail_pauseBetweenNotes);
      noTone(speaker_pin);
    }
    delay(1000);
    digitalWrite(LED_intruder, LOW); // Выключение красного светодиода
  }
  Serial.println();
  delay(500);
}

№20: Самодельная плата arduino

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

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

Принцип работы

Плата из нашего проекта работает точно так же, как и Arduino. В ее основе используется микроконтроллер ATMEL ATmega328P, к которому мы подключим дополнительные компоненты. Микроконтроллер ATmega — это «мозг» Arduino, выполняющий инструкции из загруженного скетча.

DwUDYJFxYAyZ7-yq1O2V-RDxc3k6ZEOq93B_btZDtxztXbC_0s-ETtgb_85k25i1BMQg7gArYEW2sJKnw8TLKyl4bxrigWlun8fAFQdlK4UKcwwgQFDBdnjFaiYASs7Tt3RMr8w4

5-вольтовый стабилизатор напряжения L7805cv контролирует напряжение и снижает напряжение тока от 9-вольтовой батареи до 5 В, т.е до уровня, на котором работает микросхема ATmega, тем самым защищая микроконтроллер и дополнительные компоненты. Кварцевый генератор с частотой 16 МГц позволяет Arduino вычислять время, а конденсаторы действуют как фильтр для выравнивания напряжения.

nJSfxRwDryGUwnxFWRsFECRdjz72nBwtrFTh3KboXJf_yaoNtPR37Wuo5d3ix3rSLg4Ad2fNmhbltPhl_WCNeuIvGQdApFwK_L0iJ_FcBQqkfQYC65414SExqRTFZ96ivLzKedqz

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

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

hfsbselTYGwtk09rNM-o075mgGqEvSEpYo8Zk6GT2VMYcwJ3QAvpOb70i74vKGrMUktIAlQzaGzcLS0IwV1emY0K9dFLsTt_6njs744OiIONFSllsf9TmNq2ndtduoowkB8MwqXs

Соответствие контактов ATmega328P и Arduino

Контакт Atmega Функция Arduino Контакт Atmega Функция Arduino
1 Сброс 15 Контакт 9
2 Контакт 0 16 Контакт 10
3 Контакт 1 17 Контакт 11
4 Контакт 2 18 Контакт 12
5 Контакт 3 19 Контакт 13
6 Контакт 4 20 BCC
7 VCC 21 AREF
8 GND 22 GND
9 Кварц 23 A0
10 Кварц 24 A1
11 Контакт 5 25 A2
12 Контакт 6 26 A3
13 Контакт 7 27 A4
14 Контакт 8 28 A5

Подготовка микроконтроллера

У нашей самодельной платы нет USB-разъема для подключения микросхемы непосредственно к компьютеру, поэтому, для загрузки скетча в микроконтроллер, Вам необходимо взять полноценную плату Arduino Uno и загрузить скетч на свой микроконтроллер ATmega следующим образом:

  1. Осторожно снимите микроконтроллер Arduino ATmega с оригинальной платы Arduino и замените его своим микроконтроллером ATmega.

    DEQEaV0PGj9wteLA7MlorxkH3v0QyhmzS7oXyaFqRDm0aDvOulmptjt83D4YHj31n0ERLe6smSHAIgWEf0RFL7L0SKl3V4ln75BlhbQX2Za79VadKw3N0j5RCbYqyJ7FLngW-qzY
  2. Подключите Arduino к компьютеру с помощью USB-кабеля.
  3. Откройте среду разработки Arduino IDE
  4. Загрузите следующий скетч на микроконтроллер:
    // Проект мигающего светодиода
    int led = 13;
    
    void setup() {
      pinMode(led, OUTPUT);
    }
    
    void loop() {
      digitalWrite(led, HIGH);
      delay(1000);
      digitalWrite(led, LOW);
      delay(1000);
    }
  5. После загрузки скетча, отключите Arduino от компьютера, аккуратно снимите Ваш микроконтроллер с платы и замените его на оригинальную микросхему Arduino ATmega.

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

Теперь вы готовы к сборке собственной платы.

Сборка собственной платы Arduino

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

2. Подключите контакты 7, 20 и 21 микроконтроллера ATmega к ближайшей шине питания макетной платы, а контакты 8 и 23 — к шине заземления. Используйте перемычки для соединения шин питания и заземления по обеим сторонам платы, как показано на рисунке.

3. Подключите одну ножку кварцевого генератора к контакту 9 микросхемы ATmega, а другую — к контакту 10. Подключите ножки одного дискового конденсатора емкостью 22 пФ к контактам 9 и GND, а ножки второго — к контактам 10 и GND, как показано на рисунке.

4. Установите кнопку на макетную плату слева от микросхемы ATmega так, чтобы перекрыть канавку. Используя перемычки, подключите нижний правый контакт кнопки к контакту 1 микроконтроллера ATmega, а нижний левый контакт — к контакту GND, как показано на рис. 25.9. Подключите резистор с сопротивлением 220 Ом к нижнему правому контакту кнопки, а другой контакт этого резистора — к шине заземления. Эта кнопка будет выполнять функцию сброса.

5. Установите 5-вольтовый стабилизатор напряжения L7805cv в верхний левый угол макетной платы, чтобы его номер был обращен к вам, как показано на рисунке — его контакты 1–3 пронумерованы слева направо. Установите один электролитический конденсатор емкостью 100 мкФ в верхнюю часть макетной платы, одной ножкой в шину питания, а второй — в шину заземления. Подключите второй конденсатор емкостью 100 мкФ к контактам 1 и 2 стабилизатора напряжения. Затем подключите контакт 2 стабилизатора напряжения к шине заземления, а контакт 3 — к шине питания.

6. Установите красный светодиод на макетную плату, подключив длинную ножку (анод) через резистор с сопротивлением 220 Ом к шине питания, короткую ножку (катод) — к шине заземления. Затем установите зеленый светодиод, подключив короткую ножку (катод) к контакту 21 микроконтроллера ATmega, а длинную (анод) через резистор с сопротивлением 220 Ом к шине питания, как показано на рисунке.

7. Подключите положительный контакт (+) батареи 9 В к контакту 1 стабилизатора напряжения, а отрицательный (—) соедините с контактом 2 стабилизатора напряжения.

8. Ваша плата готова! Она должна выглядеть как на рисунке. Красный светодиод загорается, когда на шины макетной платы подается питание, оповещая, что Arduino включен и работает, а зеленый светодиод загорается, реагируя на скетч «Мигающий светодиод», загруженный в микроконтроллер ATmega.

Используя таблицу «‎Соответствие контактов ATmega328P и Arduino»‎, Вы можете применять эту цепь так же, как и Arduino Uno, подключая компоненты к контактам микросхемы ATmega. На созданной плате вы можете воспроизвести любой эксперимент из этого руководства! Не забудьте при сборке загрузить скетч в микросхему ATmega с помощью оригинальной платы Arduino.

На сегодня это все!

К сожалению, наше руководство подошло к концу, но Ваш путь только начинается! Читайте литературу, развивайтесь и, самое главное, создавайте!
Удачи Вам и до новых встреч!