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

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

Чтобы плата 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: Бегущий огонек

Давайте заставим наш маячек бегать? Для этого выставим восемь светодиодов в одну линию и заставим их включаться и выключаться в определенное время!

Алгоритм работы нашего скетча будет такой:

  1. 1. Гасим все восемь светодиодов;
  2. 2. Включаем первый светодиод;
  3. 3. Делаем паузу, чтобы увидеть как горит наш светодиод;
  4. 4. Повторяем шаги 1 - 3 для всех остальных светодиодов по очереди;
  5. 5. Возвращаемся в начало цикла loop().

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

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

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

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

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

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

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

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

Скетч

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

int time = 200;

void setup() {
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  pinMode(6, OUTPUT);
  pinMode(7, OUTPUT);
  pinMode(8, OUTPUT);
  pinMode(9, OUTPUT);
}
  
void ledsOff() {
  digitalWrite(2, LOW);
  digitalWrite(3, LOW);
  digitalWrite(4, LOW);
  digitalWrite(5, LOW);
  digitalWrite(6, LOW);
  digitalWrite(7, LOW);
  digitalWrite(8, LOW);
  digitalWrite(9, LOW);
}
  
void loop() {
  ledsOff();
  digitalWrite(2, HIGH);
  delay(time);
  ledsOff();
  digitalWrite(3, HIGH);
  delay(time);
  ledsOff();
  digitalWrite(4, HIGH);
  delay(time);
  ledsOff();
  digitalWrite(5, HIGH);
  delay(time);
  ledsOff();
  digitalWrite(6, HIGH);
  delay(time);
  ledsOff();
  digitalWrite(7, HIGH);
  delay(time);
  ledsOff();
  digitalWrite(8, HIGH);
  delay(time);
  ledsOff();
  digitalWrite(9, HIGH);
  delay(time);
  ledsOff();
}

№3: Светофоры на перекрестке

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

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

Скетч

В этом скетче мы будем использовать массивы!

// время горения светодиодов в мсек
#define TIME_GREEN 5000 // Сколько времени горят зеленый и красный
#define TIME_YELLOW 3000 // желтый
#define TIME_BLINK  300 // период мигания желтого светодиода

int pinleds[6] = {2, 3, 4, 8, 9, 10}; // Пины наших светодиодов по списку [green1, yellow1, red1, green2, yellow2, red2]     
boolean blinkyellow = true; // переменная blink для чередования мигания желтого

void setup() {
// Настроим выводы Arduino как выходы
for (int i = 0; i < 6; i++) {
    pinMode(pinleds[i], OUTPUT);
  }   
}

void loop() {
  // список состояний светодиодов 1- горит, 0- потушен
  // [green1,yellow1,red1,green2,yellow2,red2]
  // 1 - зеленый, 2 - красный
  int leds1[] = {HIGH, LOW, LOW, LOW, LOW, HIGH};
  trafficlight(leds1);
  delay(TIME_GREEN);  
  blinkyellow = true;
  // 1 - желтый с зеленым, 2- желтый c красным
  for (int i = 0; i < (TIME_YELLOW/TIME_BLINK); i++) {
    int leds2[]={HIGH, (int)blinkyellow, LOW, LOW, (int)blinkyellow, HIGH};
    trafficlight(leds2);
    delay(TIME_BLINK);
    blinkyellow = !blinkyellow;
  }
  // 1 - красный, 2 - зеленый
  int leds3[]={LOW, LOW, HIGH, HIGH, LOW, LOW};
  trafficlight(leds3);
  delay(TIME_GREEN);  
  blinkyellow = true;
  // 1- желтый c красным,  2 - желтый с зеленым
  //for(int i = 0; i < (TIME_YELLOW/TIME_BLINK); i++)
  for (int i=0; i < TIME_YELLOW; i = i + TIME_BLINK) {
    int leds4[]={LOW, (int)blinkyellow, HIGH, HIGH, (int)blinkyellow, LOW};
    trafficlight(leds4);
    delay(TIME_BLINK);
    blinkyellow = !blinkyellow;
  }
}

// функция зажигания светодиодов светофора
void trafficlight(int statled[5]) {
  for(int i = 0; i < 6; i++) {
    digitalWrite(pinleds[i],statled[i]);   
  } 
}

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

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

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

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

     
  • Контакты А и С всегда замкнуты, так же как 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); // отключаем светодиод
  }
}

Видео

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

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

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

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

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

Но цифровая информация представляет аналоговые данные, используя только числа. Чтобы принять непрерывные аналоговые данные, поступающие от потенциометра, плата 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 мс
}

Видео

№6: Радуга на RGB светодиоде

RGB светодиод отличается от обычного тем, что содержит три кристалла - Red (Красный), Green (Зеленый), Blue(Синий), которые могут синтезировать любой цвет или оттенок. Давайте попробуем сделать настоящую радугу?

Сначала познакомимся с RGB светодиодом поближе. В нашем эксперименте будет принимать участие rgb светодиод с общим минусом (катод), но на своем пути в мире ардуино вы можете встретить и светодиоды с общим плюсом (анодом).

Посмотрите внимательно на контакты нашего светодиода.


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

Цвет R G B
Красный 255 0 0
Оранжевый 255 125 0
Желтый 255 255 0
Зеленый 0 255 0
Голубой 0 255 255
Синий 0 0 255
Фиолетовый 255 0 255

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

Давайте разберем принцип работы нашей программы:

  1. Примем за начальную точку отсчета красный цвет (255,0,0).
  2. Будем постепенно увеличивать значение зеленой составляющей G до достижения значения оранжевого (255, 125, 0), а затем и желтого цвета (255, 255, 0).
  3. Постепенно уменьшим значение красной составляющей R до значения зеленого цвета (0, 255, 0).
  4. Постепенно увеличим значение синей составляющей B до значения голубого цвета (0, 255, 255).
  5. Постепенно уменьшим количество зеленой составляющей G до значения синего цвета (0, 0, 255).
  6. Постепенно увеличим значение красной составляющей R до значения фиолетового цвета (255, 0, 255).
  7. Выдерживаем паузу и переходим к шагу 1.

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

Скетч

// пауза перед каждым изменением цвета радуги
#define MAX_PAUSE 1
#define MIN_PAUSE 30

// пин подключения среднего вывода потенциометра
const int PIN_POT=A0;

int pot; // переменная для хранения значения потенциометра 

const int RED = 11; // вывод красной ноги RGB-светодиода 
const int GREEN = 10; // вывод зеленой ноги RGB-светодиода 
const int BLUE = 9; // вывод синей ноги RGB-светодиода 

int red; // переменная для хранения R-составляющей цвета 
int green; // переменная для хранения G-составляющей цвета 
int blue; // переменная для хранения B-составляющей цвета 

void setup() {
  // Нам ничего не нужно делать при включении платы
}

// Функция установки цвета RGB-светодиода
void setRGB(int r, int g, int b) {
  analogWrite(RED, r);
  analogWrite(GREEN, g);
  analogWrite(BLUE, b);
  
  pot = analogRead(PIN_POT); // Снимаем показания потенциометра
  delay(map(pot, 0, 1023, MIN_PAUSE, MAX_PAUSE)); // Скалируем показания потенциометра (от 0 до 1023) на наш диапазон паузы (от 1 до 30)
}

void loop() {
  // от красного к желтому
  red = 255;
  green = 0;
  blue = 0;

  for (green = 0; green < 255; green++) {
    setRGB(red, green, blue);
  }

  // от желтому к зеленому
  for (red = 255; red > 0; red--) {
    setRGB(red, green, blue);
  }

  // от зеленого к голубому
  for (blue = 0; blue < 255; blue++) {
    setRGB(red, green, blue);
  }
  
  // от голубого к синему
  for (green = 255; green > 0; green--) {
    setRGB(red, green, blue);
  }

  // от синего к фиолетовому
  for (red = 0; red < 255; red++) {
    setRGB(red, green, blue);
  }

  // от фиолетового обратно к красному
  for (blue = 255; blue > 0; blue--) {
    setRGB(red, green, blue);
  }
}

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

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

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

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

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

Скетч

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

В строке 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: Игра «Саймон говорит»

Электронная версия игры «Саймон говорит» была весьма популярной в конце 1970-х и начала 1980-х годов. Задачей игрока было запомнить последовательность вспышек и звуков и затем воспроизвести ее. Эта игра развивает не только зрительную и слуховую память, но и скорость реакции. Ее до сих пор можно купить в магазинах. Но гораздо интереснее собрать такую игрушку самому. Этим мы сегодня и займемся!

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

Скетч

Скетч генерирует случайную последовательность, в которой будут зажигаться светодиоды. Случайное значение переменной 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: Игра «Кто быстрее?»

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

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

Скетч

#define BUZZER_PIN   10  // пин с пищалкой
#define PLAYER_COUNT 2   // количество игроков
// вместо перечисления всех пинов по-одному, мы объявляем пару
// списков: один с номерами пинов с кнопками, другой — со
// светодиодами. Списки также называют массивами (англ. array)
int buttonPins[PLAYER_COUNT] = {3, 13};
int ledPins[PLAYER_COUNT] = {8, 12};
  
void setup() {
  pinMode(BUZZER_PIN, OUTPUT);

  for (int player = 0; player < PLAYER_COUNT; ++player) {
    // при помощи квадратных скобок получают значение в массиве
    // под указанным в них номером. Нумерация начинается с нуля
    pinMode(ledPins[player], OUTPUT);
    pinMode(buttonPins[player], INPUT_PULLUP);
  }
}
  
void loop() {
  // даём сигнал «пли!», выждав случайное время от 2 до 7 сек
  delay(random(2000, 7000));
  tone(BUZZER_PIN, 3000, 250); // 3 килогерца, 250 миллисекунд
  
  for (int player = 0; ; player = (player+1) % PLAYER_COUNT) {
    // если игрок номер «player» нажал кнопку...
    if (!digitalRead(buttonPins[player])) {
      // ...включаем его светодиод и сигнал победы на 1 сек
      digitalWrite(ledPins[player], HIGH);
      tone(BUZZER_PIN, 4000, 1000);
      delay(1000);
      digitalWrite(ledPins[player], LOW);
      break; // Есть победитель! Выходим (англ. break) из цикла
    }
  }
}

№10: Увеличение количества выходов 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 выходов!

№11: Лазерная сигнализация

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

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

Как это работает?

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

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

Внимание! Никогда не направляйте лазер в глаза людям или животным. Это может нанести непопровимый вред раговице глаза!

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

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

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

int laserPin = 3; // Контакт, к которому подключен лазер
int photoresistorPin = 0; // Аналоговый контакт к которому подключен фоторезистор.

void setup() {
  pinMode(laserPin, OUTPUT);
  Serial.begin(9600);
}

void loop() {
  digitalWrite(laserPin, HIGH);
  Serial.println(analogRead(photoresistorPin));
}

Откройте окно монитора порта в среде разработки Arduino. В нем вы увидите значение, которое считывается с фоторезистора (у нас получилось 697).

Запишите это число, оно нам еще пригодится.

Теперь посветите лазером на фоторезистор и также запишите полученное значение. На этот раз у нас получилось 997.

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

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

int buzzPin = 11; // Контакт, к которому подключен пьезоизлучатель
int ledPin = 13; // Контакт, к которому подключен светодиод
int laserPin = 3; // Контакт, к которому подключен лазер
int maxValue = 850; // Среднее значение между показателями фоторезистора.
int photoresistorPin = 0; // Аналоговый контакт к которому подключен фоторезистор.
boolean isWarning = false; // Если сработала сигнализация.

void setup() {
  pinMode(ledPin, OUTPUT);
  pinMode(laserPin, OUTPUT);
  digitalWrite(laserPin, HIGH); // Включаем лазер.
  Serial.begin(9600);
}

void loop() {
  if (analogRead(photoresistorPin) < maxValue) {
    isWarning = true; // Сработала сигнализация
  } else {
    isWarning = false;
  }
  
  if (isWarning) {
    digitalWrite(ledPin, LOW);
    tone(buzzPin, 3000, 250);
    delay(1500);
  } else {
    digitalWrite(ledPin, HIGH);
  }
}

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

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

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 "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; // Контакт, к которому подключен пьезоизлучатель

void setup() {
  Serial.begin(9600);
  SPI.begin(); // Начало последовательной связи между RFID-модулем и компьютером
  rfid.PCD_Init(); // Инициализация RFID-модуля
  delay(1000);
  pinMode(LED_access, OUTPUT);
  pinMode(LED_intruder, OUTPUT);
  pinMode(speaker_pin, OUTPUT);
}
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);
    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);
    }
    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);
}

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

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

Жидкокристаллический дисплей (ЖКД, 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);
}

№14: Шар судьбы

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

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

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

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

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

Скетч

Код в этом проекте довольно прост. Когда вы включаете Arduino, на ЖК-дисплее появляется текст Ask a Question. Встряхивание датчика вибрации приводит к выполнению кода скетча, и плата Arduino выбирает случайный ответ из восьми возможных (case в диапазоне от 0 до 7).

Чтобы добавить свои собственные ответы, измените значение 8 на количе- ство возможных ответов, затем добавьте собственные ответы в том же формате, как и остальные.

#include <LiquidCrystal.h>
LiquidCrystal lcd(7, 8, 9, 10, 11, 12); // Контакты, к которым подключен ЖК-дисплей
const int switchPin = 2; // Контакт, к которому подключен датчик
int switchState = 0;
int prevSwitchState = 0;
int reply;

void setup() {
  lcd.begin(16, 2);
  pinMode(switchPin, INPUT); // Перевод контакта датчика в режим ввода
  lcd.print("FORTUNE TELLER"); // Это выводится в строке 1
  lcd.setCursor(0, 1);
  lcd.print("Ask a Question"); // Это выводится в строке 2
}

void loop() {
  switchState = digitalRead(switchPin); // Считывание значения с контакта датчика
  
  if (switchState != prevSwitchState) {
    if (switchState == LOW) { // Если цепь разорвана, дать ответ reply = random(8);

      reply = random(8); // Выбрать варианты с 1 по 8 ниже

      lcd.clear();
      lcd.setCursor(0, 0);
      lcd.print("The answer is: "); // Это выводится на дисплей в 1 строке
      lcd.setCursor(0, 1);
      switch (reply) {
        // Ответом будет одним из следующих вариантов
        case 0:
          lcd.print("Yes");
          break;
        case 1:
          lcd.print("Probably");
          break;
        case 2:
          lcd.print("Definitely");
          break;
        case 3:
          lcd.print("Don't be silly");
          break;
        case 4:
          lcd.print("Of course");
          break;
        case 5:
          lcd.print("Ask again");
          break;
        case 6:
          lcd.print("Doubtful");
          break;
        case 7:
          lcd.print("No");
          break;
      }
    }
  }
  prevSwitchState = switchState;
}

№15: Электронные часы

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

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

Скетч для установки текущего времени

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

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

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

#include <DS1307RTC.h>
#include <Time.h>
#include <Wire.h>

const char *monthName[12] = {
  "Jan", "Feb", "Mar", "Apr", "May", "Jun",
  "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};

tmElements_t tm;

void setup() {
  bool parse=false;
  bool config=false;

  // get the date and time the compiler was run
  if (getDate(__DATE__) && getTime(__TIME__)) {
    parse = true;
    // and configure the RTC with this info
    if (RTC.write(tm)) {
      config = true;
    }
  }

  Serial.begin(9600);
  while (!Serial) ; // wait for Arduino Serial Monitor
  delay(200);
  if (parse && config) {
    Serial.print("DS1307 configured Time=");
    Serial.print(__TIME__);
    Serial.print(", Date=");
    Serial.println(__DATE__);
  } else if (parse) {
    Serial.println("DS1307 Communication Error :-{");
    Serial.println("Please check your circuitry");
  } else {
    Serial.print("Could not parse info from the compiler, Time=\"");
    Serial.print(__TIME__);
    Serial.print("\", Date=\"");
    Serial.print(__DATE__);
    Serial.println("\"");
  }
}

void loop() {
}

bool getTime(const char *str)
{
  int Hour, Min, Sec;

  if (sscanf(str, "%d:%d:%d", &Hour, &Min, &Sec) != 3) return false;
  tm.Hour = Hour;
  tm.Minute = Min;
  tm.Second = Sec;
  return true;
}

bool getDate(const char *str)
{
  char Month[12];
  int Day, Year;
  uint8_t monthIndex;

  if (sscanf(str, "%s %d %d", Month, &Day, &Year) != 3) return false;
  for (monthIndex = 0; monthIndex < 12; monthIndex++) {
    if (strcmp(Month, monthName[monthIndex]) == 0) break;
  }
  if (monthIndex >= 12) return false;
  tm.Day = Day;
  tm.Month = monthIndex + 1;
  tm.Year = CalendarYrToTm(Year);
  return true;
}

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

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

Теперь мы можем вывести данные с модуля реального времени на ЖК-дисплей.

#include <DS1307RTC.h>
#include <Time.h>
#include <Wire.h>
#include <LiquidCrystal.h>

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

void setup() {
  // Установка количества столбцов и строк ЖК-дисплея
  lcd.begin(16, 2);
  lcd.print("Electro Clocks");
  lcd.setCursor(0, 1);
  lcd.print("by arduinko.ru");
  Serial.begin(9600);
  delay(2000);
  lcd.clear();
}

void loop() {
  tmElements_t tm;
  
  if (RTC.read(tm)) {
    // Показываем текущее время
    lcd.setCursor(0, 0);
    print2digits(tm.Hour);
    lcd.print(':');
    print2digits(tm.Minute);
    lcd.print(':');
    print2digits(tm.Second);
  
    // Показываем дату
    lcd.setCursor(0,1);
    print2digits(tm.Day);
    lcd.print('.');
    print2digits(tm.Month);
    lcd.print('.');
    lcd.print(tmYearToCalendar(tm.Year));
  }
  
  delay(1000); 
}

void print2digits(int number) {
  if (number >= 0 && number < 10) {
    lcd.print('0');
  }
  
  lcd.print(number);
}

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

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