Содержание

Электронное приложение к набору «Arduino. Восьмибитная академия»

Привет, дружище!

Добро пожаловать в уютную Восьмибитную академию.

На этой странице мы собрали всё, чтобы тебе было удобнее проходить набор:

  • Схемы проектов в электронном виде.
  • Исходный код учебных программ (копируй его в редактор Arduino IDE).
  • Дополнительные материалы: программные библиотеки, даташиты и т. п.

Обрати внимание

Именно здесь находятся самые актуальные версии кода с исправлениями. Написанному верить!

Эксперименты

В начале книги идёт вступительная часть с экспериментами №1–№6, где тебе не потребуется никакое программирование.

Поэтому в электронной версии мы приводим материалы, начиная с эксперимента №7.

№7. Цифролампа

Lamp.ino
void setup() {
  // Настраиваем пин 2 в режим выхода
  pinMode(2, OUTPUT);
  // Подаем на пин 2 высокий уровень
  digitalWrite(2, HIGH);
}
 
void loop() {
}

№8. Маячок

Blink.ino
void setup() {
  // Настраиваем пин 2 в режим выхода
  pinMode(2, OUTPUT);
}
 
void loop() {
  // Подаем на пин 2 высокий уровень
  digitalWrite(2, HIGH);
  // Ждём 200 мс
  delay(200);
  // Подаем на пин 2 низкий уровень
  digitalWrite(2, LOW);
  // Ждём 800 мс
  delay(800);
}

№9. Светофор

TrafficLight.ino
// Даём пинам понятные имена
constexpr int LED_RED_PIN = 2;
constexpr int LED_YELLOW_PIN = 3;
constexpr int LED_GREEN_PIN = 4;
 
// Константа для хранения паузы в миллисекундах
constexpr int PAUSE = 2000;
 
void setup() {
  // Настраиваем пины светодиодов в режим выхода
  pinMode(LED_RED_PIN, OUTPUT);
  pinMode(LED_YELLOW_PIN, OUTPUT);
  pinMode(LED_GREEN_PIN, OUTPUT);
}
 
void loop() {
  // Зажигаем красный светодиод
  digitalWrite(LED_RED_PIN, HIGH);
  // Ждём 2 секунды
  delay(PAUSE);
 
  // Гасим красный светодиод
  digitalWrite(LED_RED_PIN, LOW);
  // Зажигаем жёлтый светодиод
  digitalWrite(LED_YELLOW_PIN, HIGH);
  // Ждём 2 секунды
  delay(PAUSE);
 
  // Гасим жёлтый светодиод
  digitalWrite(LED_YELLOW_PIN, LOW);
  // Зажигаем зелёный
  digitalWrite(LED_GREEN_PIN, HIGH);
  // Ждём 2 секунды
  delay(PAUSE);
 
  // Гасим зелёный светодиод
  digitalWrite(LED_GREEN_PIN, LOW);
}

№10. SOS

SOS.ino
// Даём пину со светодиодом понятное имя
constexpr int LED_RED_PIN = 2;
 
void setup() {
  // Настраиваем пин со светодиодом в режим выхода
  pinMode(LED_RED_PIN, OUTPUT);
}
 
void loop() {
  // Переменная для хранения паузы в миллисекундах
  int pause = 200;
 
  // Мигаем красным светодиодом с интервалом из переменной pause
  digitalWrite(LED_RED_PIN, HIGH);
  delay(pause);
  digitalWrite(LED_RED_PIN, LOW);
  delay(pause);
  digitalWrite(LED_RED_PIN, HIGH);
  delay(pause);
  digitalWrite(LED_RED_PIN, LOW);
  delay(pause);
  digitalWrite(LED_RED_PIN, HIGH);
  delay(pause);
  digitalWrite(LED_RED_PIN, LOW);
 
  // Меняем значение паузы
  pause = 500;
 
  // Мигаем красным светодиодом с интервалом из переменной pause
  delay(pause);
  digitalWrite(LED_RED_PIN, HIGH);
  delay(pause);
  digitalWrite(LED_RED_PIN, LOW);
  delay(pause);
  digitalWrite(LED_RED_PIN, HIGH);
  delay(pause);
  digitalWrite(LED_RED_PIN, LOW);
  delay(pause);
  digitalWrite(LED_RED_PIN, HIGH);
  delay(pause);
  digitalWrite(LED_RED_PIN, LOW);
  delay(pause);
 
  // Меняем значение паузы
  pause = 200;
 
  // Мигаем красным светодиодом с интервалом из переменной pause
  digitalWrite(LED_RED_PIN, HIGH);
  delay(pause);
  digitalWrite(LED_RED_PIN, LOW);
  delay(pause);
  digitalWrite(LED_RED_PIN, HIGH);
  delay(pause);
  digitalWrite(LED_RED_PIN, LOW);
  delay(pause);
  digitalWrite(LED_RED_PIN, HIGH);
  delay(pause);
  digitalWrite(LED_RED_PIN, LOW);
 
  // Меняем значение паузы
  pause = 2000;
  // Ставим задержку перед следующим циклом мигания
  delay(pause);
}

№11. Охотники за приведениями

Схема №1

Схема №2

Ghostbusters.ino
// Даём пинам понятные имена
constexpr int LED_PIN = 2;
constexpr int WIRE_PIN = 4;
 
void setup() {
  // Настраиваем пин со светодиодом в режим выхода
  pinMode(LED_PIN, OUTPUT);
  // Настраиваем пин с подключённым проводом в режим входа
  pinMode(WIRE_PIN, INPUT);
}
 
void loop() {
  // Создаём переменную для хранения сигнала с провода
  bool wireState;
 
  // Считываем значение уровня сигнала сигнала с провода
  // Если провод соединён с линией питания, получим логическую единицу
  // Если провод соединён с линией GND, получим логический ноль
  wireState = digitalRead(WIRE_PIN);
 
  // Применим полученное значение для светодиода
  // Логическая единица — светодиод горит
  // Логический ноль — светодиод не горит
  digitalWrite(LED_PIN, wireState);
}

№12. Фонарик

Программный код №1

Flashlight-v1.ino
// Даём пинам понятные имена
constexpr int LED_PIN = 2;
constexpr int BUTTON_PIN = 4;
 
void setup() {
  // Настраиваем пин со светодиодом в режим выхода
  pinMode(LED_PIN, OUTPUT);
  // Настраиваем пин с кнопкой в режим входа
  pinMode(BUTTON_PIN, INPUT);
}
 
void loop() {
  // Создадим переменную для хранения состояния с кнопки
  // И сразу же запишем значение уровня сигнала
  // Если кнопка нажата — логический ноль
  // Если кнопка не нажата — логическая единица
  bool buttonState = digitalRead(BUTTON_PIN);
 
  // Применим полученное значение для управления светодиодом
  digitalWrite(LED_PIN, buttonState);
}

Программный код №2

Flashlight-v2.ino
// Даём пинам понятные имена
constexpr int LED_PIN = 2;
constexpr int BUTTON_PIN = 4;
 
void setup() {
  // Настраиваем пин со светодиодом в режим выхода
  pinMode(LED_PIN, OUTPUT);
  // Настраиваем пин с кнопкой в режим входа
  pinMode(BUTTON_PIN, INPUT);
}
 
void loop() {
  // Создадим переменную для хранения состояния с кнопки
  // Считываем инвертированное состояние с кнопки
  // Если кнопка нажата — логическая единица
  // Если кнопка не нажата — логический ноль
  bool buttonState = !digitalRead(BUTTON_PIN);
 
  // Применим полученное значение для управления светодиодом
  digitalWrite(LED_PIN, buttonState);
}

№13. Фонарик без посторонних

FlashlightPullUP.ino
// Даём пинам понятные имена
constexpr int LED_PIN = 2;
constexpr int BUTTON_PIN = 4;
 
void setup() {
  // Настраиваем пин со светодиодом в режим выхода
  pinMode(LED_PIN, OUTPUT);
  // Настраиваем пин с кнопкой в режим входа с подтяжкой к питанию
  pinMode(BUTTON_PIN, INPUT_PULLUP);
}
 
void loop() {
  // Создадим переменную для хранения состояния кнопки
  // Считываем инвертированное состояние с кнопки
  // Если кнопка нажата — логическая единица
  // Если кнопка не нажата — логический ноль
  bool buttonState = !digitalRead(BUTTON_PIN);
 
  // Применим полученное значение для управления светодиодом
  digitalWrite(LED_PIN, buttonState);
}

№14. Выключатель

Программный код №1

Switch-v1.ino
// Даём пинам понятные имена
constexpr int LED_PIN = 2;
constexpr int BUTTON_PIN = 4;
 
// Создаём переменную для хранения состояние светодиода
// В начале программы светодиод должен быть потушен,
// поэтому присваиваем переменной значение LOW
bool ledState = LOW;
 
void setup() {
  // Настраиваем пин со светодиодом в режим выхода
  pinMode(LED_PIN, OUTPUT);
 
  // Настраиваем пин с кнопкой в режим входа с подтяжкой к питанию
  pinMode(BUTTON_PIN, INPUT_PULLUP);
}
 
void loop() {
  // Создаём переменную для хранения состояние кнопки
  // Считываем инвертированное состояние с кнопки
  bool buttonState = !digitalRead(BUTTON_PIN);
 
  // Проверяем нажата ли кнопка сейчас
  if (buttonState == HIGH) {
    // Если да, значит это клик
    // Меняем состояние переменной для светодиода
    ledState = !ledState;
    // Применяем новое значение переменной для управления светодиодом
    digitalWrite(LED_PIN, ledState);
  }
}

Программный код №2

Switch-v2.ino
// Даём пинам понятные имена
constexpr int LED_PIN = 2;
constexpr int BUTTON_PIN = 4;
 
// Создаём переменную для хранения состояние светодиода
// В начале программы светодиод должен быть потушен,
// поэтому присваиваем переменной значение LOW
bool ledState = LOW;
 
// Создаём переменную для хранения последнего состояние кнопки
// В начале программы кнопка должна быть отпущена,
// поэтому присваиваем переменной значение LOW
bool lastButtonState = LOW;
 
void setup() {
  // Настраиваем пин со светодиодом в режим выхода
  pinMode(LED_PIN, OUTPUT);
 
  // Настраиваем пин с кнопкой в режим входа с подтяжкой к питанию
  pinMode(BUTTON_PIN, INPUT_PULLUP);
}
 
void loop() {
  // Создаём переменную для хранения состояние кнопки
  // Считываем инвертированное состояние с кнопки
  bool buttonState = !digitalRead(BUTTON_PIN);
 
  // Проверяем два условия
  // Условие 1: нажата ли кнопка сейчас
  // Условие 2: была ли кнопка отжата до фиксирования нового нажатия
  if (buttonState == HIGH && lastButtonState == LOW) {
    // Если оба условия прошли, значит это клик
    // Меняем состояние переменной для светодиода
    ledState = !ledState;
    // Применяем новое значение переменной для управления светодиодом
    digitalWrite(LED_PIN, ledState);
  }
  // Запишем в переменную новое значение состояния кнопки 
  lastButtonState = buttonState;
}

№15. Выключатель без глюков

SwitchDebounce.ino
// Даём пинам понятные имена
constexpr int LED_PIN = 2;
constexpr int BUTTON_PIN = 4;
 
 
// Создаём переменную для хранения состояние светодиода
// В начале программы светодиод должен быть потушен,
// поэтому присваиваем переменной значение LOW
bool ledState = LOW;
 
// Создаём переменную для хранения последнего состояние кнопки
// В начале программы кнопка должна быть отпущена,
// поэтому присваиваем переменной значение LOW
bool lastButtonState = LOW;
 
void setup() {
  // Настраиваем пин со светодиодом в режим выхода
  pinMode(LED_PIN, OUTPUT);
  // Настраиваем пины с кнопкой в режим входа с подтяжкой к питанию
  pinMode(BUTTON_PIN, INPUT_PULLUP);
}
 
void loop() {
  // Создаём переменную для хранения состояние кнопки
  // Считываем инвертированное состояние с кнопки
  bool buttonState = !digitalRead(BUTTON_PIN);
 
  // Проверяем два условия
  // Условие 1: нажата ли кнопка сейчас
  // Условие 2: была ли она отжата кнопка до фиксирования нового нажатия
  if (buttonState && !lastButtonState) {
    // Подождём пока дребезг кнопки затихнет
    delay(10);
    // Снова считываем инвертированное состояние с кнопки
    buttonState = !digitalRead(BUTTON_PIN); 
    // Проверяем нажата ли ещё кнопка сейчас
    if (buttonState) {
      // Если кнопка всё ещё нажата
      // Меняем состояние переменной для светодиода
      ledState = !ledState;
      // Применяем новое значение переменной для управления светодиодом
      digitalWrite(LED_PIN, ledState);
    }
  }
 
  // Запишем в переменную новое значение состояния кнопки 
  lastButtonState = buttonState;
}

№16. Рулим яркостью

LedBrightness.ino
// Даём пину со светодиодом понятное имя
constexpr int LED_PIN = 3;
 
// Константа для хранения паузы в миллисекундах
constexpr int PAUSE = 500;
 
void setup() {
  // Настраиваем пин со светодиодом в режим выхода
  pinMode(LED_PIN, OUTPUT);
}
 
void loop() {
  // Выдаём на светодиод ШИМ-сигнал
  // Для начала зажжём светодиод на 25% яркости
  // 25% от числа 255 будет примерно 64
  // 25% от напряжения 5В будет примерно 1,25 В
  analogWrite(LED_PIN, 64);
  // Ждём 250 мс
  delay(PAUSE);
 
  // Зажжём светодиод на 50% яркости
  // 50% от числа 255 будет примерно 128
  // 50% от напряжения 5В будет примерно 2,5 В
  analogWrite(LED_PIN, 128);
  // Ждём 250 мс
  delay(PAUSE);
 
  // Зажжём светодиод на 75% яркости
  // 75% от числа 255 будет примерно 192
  // 75% от напряжения 5В будет примерно 3,75 В
  analogWrite(LED_PIN, 192);
  // Ждём 250 мс
  delay(PAUSE);
 
  // Зажжём светодиод на 100% яркости
  // 100% от числа 255 будет 255
  // 100% от напряжения 5В будет 5 В
  analogWrite(LED_PIN, 255);
  // Ждём 250 мс
  delay(PAUSE);
 
  // В завершении потушим светодиод, т.е. дадим 0% яркости
  // 0% от числа 255 будет 0
  // 0% от напряжения 5В будет 0 В
  analogWrite(LED_PIN, 0);
  // Ждём 250 мс
  delay(PAUSE);
}

№17. Дыхание света

LedBrightnessSmooth.ino
// Даём пину со светодиодом понятное имя
constexpr int LED_PIN = 3;
 
void setup() {
  // Настраиваем пин со светодиодом в режим выхода
  pinMode(LED_PIN, OUTPUT);
}
 
void loop() {
  // Создаём цикл для перебора всех значений от 0 до 255
  // Выдаём на светодиод ШИМ-сигнал
  for (int brightness = 0; brightness <= 255; brightness++) {
    analogWrite(LED_PIN, brightness);
    // Ждём 10 мс
    delay(10);
  }
  // Создаём цикл для перебора всех значений от 255 до 0
  // Выдаём на светодиод ШИМ-сигнал
  for (int brightness = 255; brightness >= 0; brightness--) {
    analogWrite(LED_PIN, brightness);
    // Ждём 10 мс
    delay(10);
  }
}

№18. Охотники за приведениями 2.0

Ghostbusters2.0.ino
// Даём пинам понятные имена
constexpr int LED_PIN = 3;
constexpr int WIRE_PIN = A0;
 
void setup() {
  // Настраиваем пин со светодиодом в режим выхода
  pinMode(LED_PIN, OUTPUT);
 
  // Настраиваем пин с подключённым проводом в режим входа
  pinMode(WIRE_PIN, INPUT);
}
 
void loop() {
  // Создаём переменную для хранения сигнала с провода
  // Считываем значение уровня сигнала сигнала с провода
  // Функция analogRead считывает напряжение на пине от 0 до 5 В
  // и преобразует его к числу от 0 до 1023
  int wireState = analogRead(WIRE_PIN);
  // Создаём переменную для хранения состояние светодиода
  // Разрядность АЦП В Iskra Nano — 10 бит
  // т.е. «analogRead» возвращает числовой диапазон от 0 до 1023
  // Разрядность ШИМ В Iskra Nano — 8 бит
  // т.е. «analogWrite» ожидает диапазон от 0 до 255
  // Преобразуем значения из одного диапазона [0; 1023] в другой [0; 255],
  // самый простой способ разделить на четыре
  int brightness = wireState / 4;
  // Выдаём результат на светодиод
  analogWrite(LED_PIN, brightness);
}

№19. Диммер

Программный код №1

Dimmer-v1.ino
// Даём пинам понятные имена
constexpr int LED_PIN = 3;
constexpr int POT_PIN = A0;
 
void setup() {
  // Настраиваем пин со светодиодом в режим выхода
  pinMode(LED_PIN, OUTPUT);
  // Настраиваем пин с потенциометром в режим входа
  pinMode(POT_PIN, INPUT);
}
 
void loop() {
  // Считываем аналоговый сигнал с потенциометра
  int rotation = analogRead(POT_PIN);
  // Преобразуем значение из шкалы 10 бит в 8 бит,
  // самый простой способ разделить на четыре
  int brightness = rotation / 4;
  // Выдаём результат на светодиод
  analogWrite(LED_PIN, brightness);
}

Программный код №2

Dimmer-v2.ino
// Даём пинам понятные имена
constexpr int LED_PIN = 3;
constexpr int POT_PIN = A0;
 
void setup() {
  // Настраиваем пин со светодиодом в режим выхода
  pinMode(LED_PIN, OUTPUT);
  // Настраиваем пин с потенциометром в режим входа
  pinMode(POT_PIN, INPUT);
}
 
void loop() {
  // Считываем аналоговый сигнал с потенциометра
  int rotation = analogRead(POT_PIN);
  // Преобразуем значение из шкалы 10 бит в 8 бит,
  // используем функцию map
int brightness = map(rotation, 0, 1023, 0, 255);
  // Выдаём результат на светодиод
  analogWrite(LED_PIN, brightness);
}

№20. Цветовая рулетка

Программный код №1

ColorGame-v1.ino
// Даём понятные имена пинам к которым подключены выводы RGB-светодиода
constexpr int LED_RED_PIN = 9;
constexpr int LED_GREEN_PIN = 10;
constexpr int LED_BLUE_PIN = 11;
 
// Даём понятное имя пину с кнопкой
constexpr int BUTTON_PIN = A0;
 
// Создаём три переменные для хранения яркости каждого цвета RGB-светодиода
int red, green, blue;
 
void setup() {
  // Настраиваем пины RGB-светодиода в режим выхода
  pinMode(LED_RED_PIN, OUTPUT);
  pinMode(LED_GREEN_PIN, OUTPUT);
  pinMode(LED_BLUE_PIN, OUTPUT);
  // Настраиваем пин с кнопкой в режим входа с подтяжкой к питанию
  pinMode(BUTTON_PIN, INPUT_PULLUP);
}
 
void loop() {
  // Считываем нажатие с кнопки
  bool buttonState = !digitalRead(BUTTON_PIN);
  if (buttonState) {
    // Генерируем случайное число от 1 до 7
    int number = random(1, 8);
    // В зависимости от сгенерированного числа,
    // подаём разные сигнала на ножки RGB-светодиода
    if (number == 1) {
      red = HIGH; green = LOW; blue = LOW;
    } else if (number == 2) {
      red = LOW; green = HIGH; blue = LOW;
    } else if (number == 3) {
      red = LOW; green = LOW; blue = HIGH;
    } else if (number == 4) {
      red = HIGH; green = HIGH; blue = LOW;
    } else if (number == 5) {
      red = LOW; green = HIGH; blue = HIGH;
    } else if (number == 6) {
      red = HIGH; green = LOW; blue = HIGH;
    }
    // Выводим полученные значения на ножки RGB-светодиода
    digitalWrite(LED_RED_PIN, red);
    digitalWrite(LED_GREEN_PIN, green);
    digitalWrite(LED_BLUE_PIN, blue);
    // Ждём одну секунду
    delay(1000);
    // Подаём на все ножки RGB-светодиода логический ноль
    digitalWrite(LED_RED_PIN, LOW);
    digitalWrite(LED_GREEN_PIN, LOW);
    digitalWrite(LED_BLUE_PIN, LOW);
  }
}

Программный код №2

ColorGame-v2.ino
// Даём понятные имена пинам к которым подключены выводы RGB-светодиода
constexpr int LED_RED_PIN = 9;
constexpr int LED_GREEN_PIN = 10;
constexpr int LED_BLUE_PIN = 11;
 
// Даём понятное имя пину с кнопкой
constexpr int BUTTON_PIN = A0;
 
// Создаём три переменные для хранения яркости каждого цвета RGB-светодиода
int red, green, blue;
 
void setup() {
  // Настраиваем пины RGB-светодиода в режим выхода
  pinMode(LED_RED_PIN, OUTPUT);
  pinMode(LED_GREEN_PIN, OUTPUT);
  pinMode(LED_BLUE_PIN, OUTPUT);
  // Настраиваем пин с кнопкой в режим входа с подтяжкой к питанию
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  // Передаём случайное число с пина A5
  // для последующей генерации случайных чисел
  randomSeed(analogRead(A5));
}
 
void loop() {
  // Считываем нажатие с кнопки
  bool buttonState = !digitalRead(BUTTON_PIN);
  if (buttonState) {
    // Генерируем случайное число от 1 до 7
    int number = random(1, 8);
    // В зависимости от сгенерированного числа,
    // подаём разные сигнала на ножки RGB-светодиода
    if (number == 1) {
      red = HIGH; green = LOW; blue = LOW;
    } else if (number == 2) {
      red = LOW; green = HIGH; blue = LOW;
    } else if (number == 3) {
      red = LOW; green = LOW; blue = HIGH;
    } else if (number == 4) {
      red = HIGH; green = HIGH; blue = LOW;
    } else if (number == 5) {
      red = LOW; green = HIGH; blue = HIGH;
    } else if (number == 6) {
      red = HIGH; green = LOW; blue = HIGH;
    }
    // Выводим полученные значения на ножки RGB-светодиода
    digitalWrite(LED_RED_PIN, red);
    digitalWrite(LED_GREEN_PIN, green);
    digitalWrite(LED_BLUE_PIN, blue);
    // Ждём одну секунду
    delay(1000);
    // Подаём на все ножки RGB-светодиода логический ноль
    digitalWrite(LED_RED_PIN, LOW);
    digitalWrite(LED_GREEN_PIN, LOW);
    digitalWrite(LED_BLUE_PIN, LOW);
  }
}

№21. Лампа настроения

MoodLamp.ino
// Даём понятные имена пинам к которым подключены выводы RGB-светодиода
constexpr int LED_RED_PIN = 9;
constexpr int LED_GREEN_PIN = 10;
constexpr int LED_BLUE_PIN = 11;
 
// Даём понятное имя пину с потенциометром 
constexpr int POT_PIN = A0;
 
// Создаём три переменные для хранения яркости каждого цвета RGB-светодиода
int red, green, blue;
 
void setup() {
  // Настраиваем пины RGB-светодиода в режим выхода
  pinMode(LED_RED_PIN, OUTPUT);
  pinMode(LED_GREEN_PIN, OUTPUT);
  pinMode(LED_BLUE_PIN, OUTPUT);
  // Настраиваем пин с потенциометром в режим входа
  pinMode(POT_PIN, INPUT);
}
 
void loop() {
  // Считываем аналоговый сигнал с потенциометра
  int rotation = analogRead(A0);
 
  // В зависимости от поворота ручки потенциометра,
  // генерируем разными методами цвета Red, Green и Blue
  if (rotation <= 170) {
    red = 255;
    green = map(rotation, 0, 170, 0, 255);
    blue = 0;
  }
 
  else if (rotation <= 340) {
    red = map(rotation, 170, 340, 255, 0);
    green = 255;
    blue = 0;
  }
 
  else if (rotation <= 512) {
    red = 0;
    green = 255;
    blue = map(rotation, 340, 512, 0, 255);
  }
 
  else if (rotation <= 682) {
    red = 0;
    green = map(rotation, 512, 682, 255, 0);
    blue = 255;
  }
 
  else if (rotation <= 852) {
    red = map(rotation, 682, 852, 0, 255);
    green = 0;
    blue = 255;
  }
 
  else {
    red = 255;
    green = 0;
    blue = map(rotation, 852, 1023, 255, 0);    
  }
 
  // Выводим полученные значения на ножки RGB-светодиода
  analogWrite(LED_RED_PIN, red);
  analogWrite(LED_GREEN_PIN, green);
  analogWrite(LED_BLUE_PIN, blue);
}

№22. О частота, ты звук!

Beep.ino
// Даём пинам понятные имена
constexpr int BUZZER_PIN = 3;
constexpr int BUTTON_PIN = 4;
 
void setup() {
  // Настраиваем пин с пищалкой в режим выхода
  pinMode(BUZZER_PIN, OUTPUT);
  // Настраиваем пин с кнопкой в режим входа с подтяжкой к питанию
  pinMode(BUTTON_PIN, INPUT_PULLUP);
}
 
void loop() {
  // Считываем нажатие с кнопки
  bool buttonState = !digitalRead(BUTTON_PIN);
  // Применяем полученное значение к пищалке
  digitalWrite(BUZZER_PIN, buttonState);
}

№23. На старт, внимание, бзз!

Frequency.ino
// Даём пину с пищалкой понятное имя
constexpr int BUZZER_PIN = 3;
 
void setup() {
  // Настраиваем пин с пищалкой в режим выхода
  pinMode(BUZZER_PIN, OUTPUT);
  // Выдаём на пищалку ШИМ-сигнал с коэффициентом заполнения 50%
  analogWrite(BUZZER_PIN, 127);
}
 
void loop() {
}

№24. Синтезатор

FrequencyRange.ino
// Даём понятное имя пину с пищалкой
constexpr int BUZZER_PIN = 3;
 
// Даём понятное имя пину с потенциометром 
constexpr int POT_PIN = A0;
 
void setup() {
  // Настраиваем пин с пищалкой в режим выхода
  pinMode(BUZZER_PIN, OUTPUT);
  // Настраиваем пин с потенциометром в режим входа
  pinMode(POT_PIN, INPUT);
}
 
void loop() {
  // Считываем аналоговый сигнал с потенциометра
  int rotation = analogRead(POT_PIN);
  // Преобразуем значение с потенциометра в частотный диапазон для пищалки
  int frequency = map(rotation, 0, 1023, 20, 20000);
  // Заставляем пин с пищалкой звучать на высчитанной частоте
  tone(BUZZER_PIN, frequency);
}

№25. Я вам спою

Melody.ino
// Даём понятное имя пину с пищалкой
constexpr int BUZZER_PIN = 7;
// Даём понятное имя пину с кнопкой
constexpr int BUTTON_PIN = 4;
 
// Создаём звуковой шрифт из нот со своими частотами
constexpr int NOTE_B3 = 247;
constexpr int NOTE_C4 = 262;
constexpr int NOTE_D4 = 294;
constexpr int NOTE_E4 = 330;
constexpr int NOTE_F4 = 349;
constexpr int NOTE_G4 = 392;
constexpr int NOTE_A4 = 440;
constexpr int NOTE_B4 = 494;
constexpr int NOTE_C5 = 523;
 
void setup() {
  // Настраиваем пин с пищалкой в режим выхода
  pinMode(BUZZER_PIN, OUTPUT);
  // Настраиваем пин с кнопкой в режим входа с подтяжкой к питанию
  pinMode(BUTTON_PIN, INPUT_PULLUP);
}
 
void loop() {
  // Считываем нажатие с кнопки
  bool buttonState = !digitalRead(BUTTON_PIN);
  // По нажатию кнопки проигрываем мелодию из последовательности нот
  if (buttonState) {
    tone(BUZZER_PIN, NOTE_E4, 100);
    delay(150);
    tone(BUZZER_PIN, NOTE_E4, 100);
    delay(300);
    tone(BUZZER_PIN, NOTE_E4, 100);
    delay(300);
    tone(BUZZER_PIN, NOTE_C4, 100);
    delay(100);
    tone(BUZZER_PIN, NOTE_E4, 100);
    delay(300);
    tone(BUZZER_PIN, NOTE_G4, 100);
    delay(550);
    tone(BUZZER_PIN, NOTE_C5, 100);
    delay(575);
  }
}

№26. Кнопочные ковбои

ButtonCowboys.ino
// Даём пинам понятные имена
constexpr int BUZZER_PIN = 4;
constexpr int LED_ONE_PIN = 3;
constexpr int LED_TWO_PIN = 5;
constexpr int BUTTON_ONE_PIN = 2;
constexpr int BUTTON_TWO_PIN = 6;
 
void setup() {
  // Настраиваем пин с пищалкой в режим выхода
  pinMode(BUZZER_PIN, OUTPUT);
  // Настраиваем пины со светодиодами в режим выхода
  pinMode(LED_ONE_PIN, OUTPUT);
  pinMode(LED_TWO_PIN, OUTPUT);
  // Настраиваем пины с кнопкой в режим входа с подтяжкой к питанию
  pinMode(BUTTON_ONE_PIN, INPUT_PULLUP);
  pinMode(BUTTON_TWO_PIN, INPUT_PULLUP);
  // Передаём случайное число с пина A0
  // для последующей генерации случайных чисел
  randomSeed(analogRead(A0));
}
 
void loop() {
  // Выжидаем случайное время от 2 до 10 секунд
  delay(random(2000, 10000));
  // Даём сигнал «пли!» 
  // Пищим 250 миллисекунд с частотой 3 килогерца 
  tone(BUZZER_PIN, 3000, 250);
  // Запускаем бесконечный цикл while
  while (true) {
    // Считываем состояния кнопок двух игроков
    bool buttonPlayerOne = !digitalRead(BUTTON_ONE_PIN);
    bool buttonPlayerTwo = !digitalRead(BUTTON_TWO_PIN);
    // Проверяем, нажата ли одна из кнопок.
    if (buttonPlayerOne || buttonPlayerTwo) {
      if (buttonPlayerOne) {
        // Если на кнопку нажал первый игрок
        // Зажигаем первый светодиод на 1000 мс
        // Выводим звуковой сигнал 4000 Гц / 1000 мс
        digitalWrite(LED_ONE_PIN, HIGH);
        tone(BUZZER_PIN, 4000, 1000);
        delay(1000);
        digitalWrite(LED_ONE_PIN, LOW);
        // Если на кнопку нажал второй игрок
        // Зажигаем первый светодиод на 1000 мс
        // Выводим звуковой сигнал 4000 Гц / 1000 мс
      } else if (buttonPlayerTwo) {
        digitalWrite(LED_TWO_PIN, HIGH);
        tone(BUZZER_PIN, 4000, 1000);
        delay(1000);
        digitalWrite(LED_TWO_PIN, LOW);
      }
      // Выходим из бесконечного цикла
      break;
    }
  }
}

№27. Терминал

Terminal.ino
void setup() {
  // Открываем соединения с последовательным портом (терминалом)
  Serial.begin(9600);
  // Выводим приветственную строку в терминал
  Serial.println("Hello, world");
}
 
void loop() {
  // Выводим каждую секунду новую строку
  Serial.println("This is a new line");
  delay(1000);
}

№28. Время жизни

Lifetime.ino
void setup() {
  // Открываем соединения с последовательным портом (терминалом)
  Serial.begin(9600);
  // Выводим приветственную строку в терминал
  Serial.println("Hello, world");
}
 
void loop() {
  // Создаём переменную time
  // и присваиваем ей количество секунд с начала работы программы
  int time = millis() / 1000;
  // Выводим полученные данные
  Serial.print("Time life: ");
  Serial.print(time);
  Serial.println(" seconds");
  delay(1000);
}

№29. Бегущий огонь

RunningFire.ino
// Создаём массив с контактами светодиодов
constexpr int PIN_LEDS[] = {2, 3, 4, 5, 6, 7, 8, 9};
// Количество светодиодов в массиве
constexpr int COUNT_LEDS = 8;
 
void setup() {
  // Настраиваем пины со светодиодами в режим выхода
  for (int i = 0; i < COUNT_LEDS ; i++) {
    pinMode(PIN_LEDS[i], OUTPUT);
  }
}
 
void loop() {
  // Мигаем каждым светодиодом в цикле по очереди
  for (int i = 0; i < COUNT_LEDS; i++) {
    digitalWrite(PIN_LEDS[i], HIGH);
    delay(100);
    digitalWrite(PIN_LEDS[i], LOW);
    delay(100);
  }
}

№30. Многопоточность

MultipleBlink.ino
// Даём пинам понятные имена
constexpr int LED_RED_PIN = 2;
constexpr int LED_GREEN_PIN = 3;
 
// Переменные для счётчиков красного и зелёного светодиода
long millisLastRed = 0;
long millisLastGreen = 0;
 
// Переменные для хранения состояний светодиодов
bool ledRedState = false;
bool ledGreenState = false;
 
void setup() {
  // Настраиваем пины со светодиодами в режим выхода
  pinMode(LED_RED_PIN, OUTPUT);
  pinMode(LED_GREEN_PIN, OUTPUT);
}
 
void loop() {
  // Засекаем текущее время
  long millisNow = millis();
  if (millisNow - millisLastRed > 100) {
    // Если интервал красного LED превысил 100 мс
    // Инвертируем его состояние
    millisLastRed = millisNow;
    ledRedState = !ledRedState;
    digitalWrite(LED_RED_PIN, ledRedState);
  }
  if (millisNow - millisLastGreen > 1000) {
    // Если интервал зелёного LED превысил 1000 мс
    // Инвертируем его состояние
    millisLastGreen = millisNow;
    ledGreenState = !ledGreenState;
    digitalWrite(LED_GREEN_PIN, ledGreenState);
  }
}

№31. Выкручивание напряжения

Программный код №1

VoltageRegulator-v1.ino
// Даём понятное имя пину с потенциометром 
constexpr int POT_PIN = A0;
 
void setup() {
  // Открываем соединения с последовательным портом
  Serial.begin(9600);
  // Настраиваем пин с потенциометром в режим входа
  pinMode(POT_PIN, INPUT);
}
 
void loop() {
  // Считываем аналоговый сигнал с потенциометра
  int sensorADC = analogRead(POT_PIN);
  // Преобразуем отсчёты АЦП в напряжение
  float sensorVoltage = (sensorADC * 5) / 1024;
  // Выводим в терминал показания с потенциометра
  // в отсчётах АЦП и в напряжении
  Serial.print(sensorADC);
  Serial.print(" ADC");
  Serial.print("\t\t");
  Serial.print(sensorVoltage);
  Serial.println(" Volts");
  delay(1000);
}

Программный код №2

VoltageRegulator-v2.ino
// Даём понятное имя пину с потенциометром 
constexpr int POT_PIN = A0;
 
void setup() {
  // Открываем соединения с последовательным портом
  Serial.begin(9600);
  // Настраиваем пин с потенциометром в режим входа
  pinMode(POT_PIN, INPUT);
}
 
void loop() {
  // Считываем аналоговый сигнал с потенциометра
  int sensorADC = analogRead(POT_PIN);
  // Преобразуем отсчёты АЦП в напряжение
  float sensorVoltage = (sensorADC * 5.0) / 1024.0;
  // Выводим в терминал показания с потенциометра
  // в отсчётах АЦП и в напряжении
  Serial.print(sensorADC);
  Serial.print(" ADC");
  Serial.print("\t\t");
  Serial.print(sensorVoltage);
  Serial.println(" Volts");
  delay(1000);
}

№32. Термометр

Thermometer.ino
// Даём понятное имя пину с датчиком температуры TMP36
constexpr int TMP36_PIN = A0;
 
void setup() {
  // Открываем соединения с последовательным портом
  Serial.begin(9600);
}
 
void loop() {
  // Считываем аналоговый сигнал с TMP36
  int tmp36ADC = analogRead(TMP36_PIN);
  // Преобразуем отсчёты АЦП в напряжение
  float tmp36Voltage = (tmp36ADC * 5.0) / 1024.0;
  // Преобразуем напряжение с датчика в значение температуры
  float tmp36Temperature = (tmp36Voltage - 0.5) * 100;
  // Выводим результаты измерений в терминал
  Serial.print(tmp36ADC);
  Serial.print(" ADC");
  Serial.print("\t\t");
  Serial.print(tmp36Voltage);
  Serial.print(" Volts");
  Serial.print("\t");
  Serial.print(tmp36Temperature);
  Serial.println(" Degrees C");
  delay(1000);
}

№33. Функциональный термометр

ThermometerFunc.ino
// Даём понятное имя пину с датчиком температуры TMP36
constexpr int TMP36_PIN = A0;
 
float readTemperature() {
  // Считываем аналоговый сигнал с TMP36
  int tmp36ADC = analogRead(TMP36_PIN);
  // Преобразуем отсчёты АЦП в напряжение
  float tmp36Voltage = (tmp36ADC * 5.0) / 1024.0;
  // Преобразуем напряжение с датчика в значение температуры
  float tmp36Temperature = (tmp36Voltage - 0.5) * 100.0;
  // Возвращаем результат
  return tmp36Temperature;
}
 
void setup() {
  // Открываем соединения с последовательным портом
  Serial.begin(9600);
}
 
void loop() {
  // Запрашиваем температуру у датчика TMP36
  float tmp36Temp = readTemperature();
  // Выводим результат измерений в терминал
  Serial.print(tmp36Temp);
  Serial.println(" Degrees C");
  delay(1000);
}

№34. Библиотечный термометр

ThermometerLib.ino
// Библиотека для работы с датчиком температуры TMP36
#include <TroykaThermometer.h>
 
// Даём понятное имя пину с датчиком температуры TMP36
constexpr int TMP36_PIN = A0;
 
// Создаём объект для работы с датчиком температуры TMP36
// В параметре передаём пин, к которому подключен сенсор
TroykaThermometer tmp36(TMP36_PIN);
 
void setup() {
  // Открываем соединения с последовательным портом
  Serial.begin(9600);
}
 
void loop() {
  // Считываем данные с датчика TMP36
  tmp36.read();
  // Запрашиваем температуру у датчика TMP36
  float tmp36Temp = tmp36.getTemperatureC();
  // Выводим результат измерений в терминал
  Serial.print(tmp36Temp);
  Serial.println(" Degrees C");
  delay(1000);
}

Библиотека TroykaThermometer

№35. Заводим дисплей

DisplayBegin.ino
// Библиотека для работы с текстовым дисплеем
#include <LiquidCrystal.h>
 
// Назначаем контакты для подключения дисплея
// Используется 4-битный параллельный интерфейс
constexpr int RS_PIN = 2;
constexpr int EN_PIN = 3;
constexpr int DB4_PIN = 4;
constexpr int DB5_PIN = 5;
constexpr int DB6_PIN = 6;
constexpr int DB7_PIN = 7;
 
// Создаём объект для работы с дисплеем
// В параметрах передаём используемые пины
LiquidCrystal lcd(RS_PIN, EN_PIN, DB4_PIN, DB5_PIN, DB6_PIN, DB7_PIN);
 
void setup() {
  // Инициализируем дисплей на 2 строки по 16 символов
  lcd.begin(16, 2);
  // Устанавливаем курсор на первом знакоместе в первой строке
  lcd.setCursor(0, 0);
  // Печатем первую строку
  lcd.print("Hello, world!");
  // Устанавливаем курсор на первом знакоместе во второй строке
  lcd.setCursor(0, 1);
  // Печатем вторую строку
  lcd.print("www.amperka.ru");
}
 
void loop() {
}

Библиотека LiquidCrystal

№36. Бегущая строка

Ticker.ino
// Библиотека для работы с текстовым дисплеем
#include <LiquidCrystal.h>
 
// Назначаем контакты для подключения дисплея
// Используется 4-битный параллельный интерфейс
constexpr int RS_PIN = 2;
constexpr int EN_PIN = 3;
constexpr int DB4_PIN = 4;
constexpr int DB5_PIN = 5;
constexpr int DB6_PIN = 6;
constexpr int DB7_PIN = 7;
 
// Создаём объект для работы с дисплеем
// В параметрах передаём используемые пины
LiquidCrystal lcd(RS_PIN, EN_PIN, DB4_PIN, DB5_PIN, DB6_PIN, DB7_PIN);
 
void setup() {
  // Инициализируем дисплей на 2 строки по 16 символов
  lcd.begin(16, 2);
  // Открываем соединения с последовательным портом
  Serial.begin(9600);
}
 
void loop() {
  // Создаём объект динамической строки
  String data;
  if (Serial.available()) {
    // Если пришли данные с последовательного порта
    // Считываем их в строку 
    data = Serial.readString();
    // Выводим данные из строки на дисплей в виде бегущей строки 
    lcdPrintScroll(data);
  }
}
 
// Функция для вывода эффекта бегущей строки
void lcdPrintScroll(String str) {
  // Очищаем дисплей
  lcd.clear();
  // Устанавливаем курсор на шестнадцатом знакоместе во второй строке
  lcd.setCursor(16, 1);
  // Печатаем строку на дисплей
  lcd.print(str);
  // Сдвигаем видимую область дисплея влево на шестнадцать символов
  for(int i = 0; i < str.length() + 16; i++) {
    lcd.scrollDisplayLeft();
    delay(200);
  }
}

Библиотека LiquidCrystal

№37. Бегущая строка без правил

TickerUnlimited.ino
// Библиотека для работы с текстовым дисплеем
#include <LiquidCrystal.h>
 
// Назначаем контакты для подключения дисплея
// Используется 4-битный параллельный интерфейс
constexpr int RS_PIN = 2;
constexpr int EN_PIN = 3;
constexpr int DB4_PIN = 4;
constexpr int DB5_PIN = 5;
constexpr int DB6_PIN = 6;
constexpr int DB7_PIN = 7;
 
// Создаём объект для работы с дисплеем
// В параметрах передаём используемые пины
LiquidCrystal lcd(RS_PIN, EN_PIN, DB4_PIN, DB5_PIN, DB6_PIN, DB7_PIN);
 
void setup() {
  // Инициализируем дисплей на 2 строки по 16 символов
  lcd.begin(16, 2);
  // Открываем соединение с последовательным портом
  Serial.begin(9600);
}
 
void loop() {
  // Создаём объект динамической строки
  String data;
  if (Serial.available()) {
    // Если пришли данные с последовательного порта
    // Считываем их в строку 
    data = Serial.readString();
    // Выводим данные из строки на дисплей в виде бегущей строки 
    lcdPrintScroll(data);
  }
}
 
// Функция для вывода эффекта бегущей строки
void lcdPrintScroll(String str) {
  // Создаём строку и заполняем её 16-ю пробелами
  String strSpace;
  for (int i = 0; i <= 15; i++) {
    strSpace.concat(" ");
  }
  // Создаём строку и заполняем её:
  String str16;
  str = strSpace + str + strSpace;
  for (int i = 0; i < str.length(); i++) {
    str16 = str.substring(i, i + 16);
    lcd.setCursor(0, 1);
    lcd.print(str16);
    delay(200);
  }
}

Библиотека LiquidCrystal

№38. I²C-сканер

ScannerI2C.ino
// Библиотека для работы с I²C-устройствами
#include <Wire.h>
 
void setup() {
  // Инициализируем шину I²C
  Wire.begin();
  // Открываем соединение с последовательным портом
  Serial.begin(9600);
}
 
void loop() {
  // Выводим сообщение о начале работы сканера
  Serial.println("I2C Scanner begin...");
  int address;
  int countDevices = 0;
  // Сканируем адреса шины I²C от 0 до 127
  for (address = 1; address < 127; address++ ) {
    Wire.beginTransmission(address);
    if (Wire.endTransmission() == 0) {
      // Если найдено устройство I²C, выводим его адрес в терминал
      Serial.println("I2C device found at address:");
      Serial.print("DEC:");
      Serial.println(address);
      Serial.print("HEX:0x");
      Serial.println(address, HEX);
      countDevices++;
    }
  }
 
  // Если устройства I²C не обнаружены, выводим сообщение в терминал
  if (countDevices == 0) {
    Serial.println("No I2C devices found!");
  }
 
  // Выводим сообщение о завершении работы сканера
  Serial.println("I2C Scanner end!\n");
  delay(5000);
}

Библиотека Wire

№39. Заводим дисплей по I²C

DisplayBeginI2C.ino
// Библиотека для работы с текстовым дисплеем по шине I²C
#include <LiquidCrystal_I2C.h>
 
// Создаём объект для работы с дисплеем
// В параметрах передаём I²C-адрес дисплея, кол-во строк и символов 
LiquidCrystal_I2C lcd(0x38, 16, 2);
 
void setup() {
  // Инициализируем дисплей
  lcd.init();
  // Включаем подсветку
  lcd.backlight();
  // Устанавливаем курсор на первом знакоместе в первой строке
  lcd.setCursor(0, 0);
  // Печатаем первую строку
  lcd.print("Hello, world");
  // Устанавливаем курсор на первом знакоместе во второй строке
  lcd.setCursor(0, 1);
  // Печатаем вторую строку
  lcd.print("www.amperka.ru");
}
 
void loop() {
}

Библиотека LiquidCrystal_I2C

№40. Системы счисления

NumberSystem.ino
// Библиотека для работы с текстовым дисплеем по шине I²C
#include <LiquidCrystal_I2C.h>
 
// Создаём объект для работы с дисплеем
// В параметрах передаём I²C-адрес дисплея, кол-во строк и символов 
LiquidCrystal_I2C lcd(0x38, 16, 2);
 
// Даём понятное имя пину с потенциометром 
constexpr int POT_PIN = A0;
 
// Переменная для хранения переводимого числа
int value = 0;
 
void setup() {
  // Инициализируем дисплей
  lcd.init();
  // Включаем подсветку
  lcd.backlight();
  // Печатаем данные на дисплей
  printDataLCD(value);
}
 
void loop() {
  // Считываем сигнал с потенциометра
  int valueNow = readPot(POT_PIN);
  // Если текущее показание потенциометра отличается от предыдущего
  if (value != valueNow) {
    // Обновляем показатель
    value = valueNow;
    // Печатаем данные на дисплей
    printDataLCD(value);
  }
}
 
// Функция для считывания аналогового сигнала с потенциометра
// с преобразованием в диапазон значений от 0 до 16
int readPot(int pin) {
  int sensorADC = 0;
  int sensorValue = 0;
  // Фильтруем данные
  а затем делим результат 
  for (int i = 0; i < 32; i++) {
    sensorADC += analogRead(pin);
    delay(10);
  }
  sensorADC = sensorADC / 32;
  // Преобразуем выходной сигнал с потенциометра в диапазон значений от 0 до 16
  sensorValue = map(sensorADC, 0, 1023, 0, 15);
  // Возвращаем результат
  return sensorValue;
}
 
// Функция для вывода числа на дисплей в различных системах счисления
void printDataLCD(int numberNow) {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("DEC:");
  lcd.print(numberNow, DEC);
  lcd.setCursor(8, 0);
  lcd.print("BIN:");
  lcd.print(numberNow, BIN);
  lcd.setCursor(0, 1);
  lcd.print("OCT:");
  lcd.print(numberNow, OCT);
  lcd.setCursor(8, 1);
  lcd.print("HEX:");
  lcd.print(numberNow, HEX);
}

Библиотека LiquidCrystal_I2C

№41. Термометр на ЖК

ThermometerLCD.ino
// Библиотека для работы с датчиком температуры TMP36
#include <TroykaThermometer.h>
// Библиотека для работы с текстовым дисплеем по шине I²C
#include <LiquidCrystal_I2C.h>
 
// Даём понятное имя пину с датчиком температуры TMP36
constexpr int TMP36_PIN = A0;
 
// Создаём объект для работы с датчиком температуры TMP36
// В параметре передаём пин, к которому подключен сенсор
TroykaThermometer tmp36(TMP36_PIN);
 
// Создаём объект для работы с дисплеем
// В параметрах передаём I²C-адрес дисплея, кол-во строк и символов 
LiquidCrystal_I2C lcd(0x38, 16, 2);
 
void setup() {
  // Инициализируем дисплей
  lcd.init();
  // Включаем подсветку
  lcd.backlight();
  // Устанавливаем курсор на первом знакоместе в первой строке
  lcd.setCursor(0, 0);
  // Выводим строку на дисплей
  lcd.print("<Thermometer>");
}
 
void loop() {
  // Считываем данные с датчика TMP36
  tmp36.read();
  // Устанавливаем курсор на первом знакоместе во второй строке
  lcd.setCursor(0, 1);
  // Выводим данные температуры на дисплей
  lcd.print("T = ");
  lcd.print(tmp36.getTemperatureC());
  lcd.print(" C");
  delay(1000);
}

№42. Настольные часы

ClockLCD.ino
// Библиотека для работы с текстовым дисплеем по шине I²C
#include <LiquidCrystal_I2C.h>
// Библиотека для работы с кнопкой
#include <TroykaButton.h>
 
// Создаём объект для работы с дисплеем
// В параметрах передаём I²C-адрес дисплея, кол-во строк и символов 
LiquidCrystal_I2C lcd(0x38, 16, 2);
 
// Создаём объекты кнопок для управления
TroykaButton buttonMinute(2);
TroykaButton buttonHour(3);
 
int hours, minutes, seconds; 
 
long millisLastTime = 0;
 
void setup() {
  // Инициализируем кнопки
  buttonMinute.begin();
  buttonHour.begin();
  // Инициализируем дисплей
  lcd.init();
  // Включаем подсветку
  lcd.backlight();
  // Устанавливаем курсор на первом знакоместе в первой строке
  lcd.setCursor(0, 0);
  // Выводим строку на дисплей
  lcd.print("<Amperka Clock>");
  setTime(12, 45, 30);
}
 
void loop() {
  // Обновляем время посекундно
  updateTime();
  // Обновляем время через кнопки
  updateTimeButtons();
  // Преобразуем время
  wrapTime();
  // Выводим на дисплее время в формате «чч:мм:сс»
  printTimeLCD();
}
 
// Функция для установки времени
void setTime(int h, int m, int s) {
  hours = h;
  minutes = m;
  seconds = s;
}
 
// Функция для отсчёта времени посекундно
void updateTime() {
  long millisNowTime = millis();
  if (millisNowTime - millisLastTime > 1000) {
    millisLastTime = millisNowTime;
    seconds++;
  }
}
 
// Функция для обновления времени через кнопки
void updateTimeButtons() {
  buttonHour.read();
  buttonMinute.read();
  if (buttonHour.isClick()) {
    hours++;
  }
  if (buttonMinute.isClick()) {
    minutes++;
  }
}
 
// Функция для форматирования времени
// При достижении лимита в 60 секунд, 60 минут и 24 часа отсчёт начинается заново
void wrapTime() {
  if (seconds >= 60) {
    minutes++;
    seconds -= 60;
  }
  if (minutes >= 60) {
    hours++;
    minutes -= 60;
  }
  if (hours >= 24) {
    hours = 0;
  }
}
 
// Функция для вывода на дисплее времени в формате «чч:мм:сс»
void printTimeLCD() {
  lcd.setCursor(0, 1);
  lcd.print("TIME: ");
  printTwoDigits(hours);
  lcd.print(":");
  printTwoDigits(minutes);
  lcd.print(":");
  printTwoDigits(seconds); 
}
 
// Функция для вывода на дисплей двух цифр:
// на первом знакоместе 0, если цифра меньше 10
void printTwoDigits(int digit) {
  if (digit < 10) {
    lcd.print("0");
  }
  lcd.print(digit);
}

№43. Часы с термометром

ClockThermometerLCD.ino
// Библиотека для работы с текстовым дисплеем по шине I²C
#include <LiquidCrystal_I2C.h>
// Библиотека для работы с кнопкой
#include <TroykaButton.h>
// Библиотека для работы с датчиком температуры TMP36
#include <TroykaThermometer.h>
 
// Создаём объект для работы с дисплеем
// В параметрах передаём I²C-адрес дисплея, кол-во строк и символов 
LiquidCrystal_I2C lcd(0x38, 16, 2);
 
// Создаём объекты кнопок для управления
TroykaButton buttonMinute(2);
TroykaButton buttonHour(3);
 
// Создаём объект для работы с датчиком температуры TMP36
// В параметре передаём пин, к которому подключен сенсор
TroykaThermometer tmp36(A0);
 
// Переменные для хранения часов, минут и секунд
int hours, minutes, seconds;
// Переменные для хранения температуры
float temparature;
 
long millisLastTime = 0;
long millisLastTemp = 0;
 
void setup() {
  // Инициализируем кнопки
  buttonMinute.begin();
  buttonHour.begin();
  // Инициализируем дисплей
  lcd.init();
  // Включаем подсветку
  lcd.backlight();
  // Устанавливаем курсор на первом знакоместе в первой строке
  lcd.setCursor(0, 0);
  // Устанавливаем тестовое время 12:45:30
  setTime(12, 45, 30);
}
 
void loop() {
  // Обновляем время посекундно
  updateTime();
  // Обновляем время через кнопки
  updateTimeButtons();
  // Обновляем температуру
  updateTemperature();
  // Преобразуем время
  wrapTime();
  // Выводим на дисплее время в формате «чч:мм:сс»
  printTimeLCD();
}
 
// Функция для установки времени
void setTime(int h, int m, int s) {
  hours = h;
  minutes = m;
  seconds = s;
}
 
// Функция для отсчёта времени
void updateTime() {
  long millisNowTime = millis();
  if (millisNowTime - millisLastTime > 1000) {
    millisLastTime = millisNowTime;
    seconds++;
  }
}
 
// Функция для обновления температуры
void updateTemperature() {
  long millisNowTemp = millis();
  if (millisNowTemp - millisLastTemp > 500) {
    millisLastTemp = millisNowTemp;
    tmp36.read();
    temparature = tmp36.getTemperatureC();
  }
}
 
// Функция для обновления времени через кнопки
void updateTimeButtons() {
  buttonHour.read();
  buttonMinute.read();
  if (buttonHour.isClick()) {
    hours++;
  }
  if (buttonMinute.isClick()) {
    minutes++;
  }
}
 
// Функция для форматирования времени
// При достижении лимита в 60 секунд, 60 минут и 24 часа отсчёт начинается заново
void wrapTime() {
  if (seconds >= 60) {
    minutes++;
    seconds -= 60;
  }
  if (minutes >= 60) {
    hours++;
    minutes -= 60;
  }
  if (hours >= 24) {
    hours = 0;
  }
}
 
// Функция для вывода на дисплее времени в формате «чч:мм:сс» и температуры
void printDataLCD() {
  lcd.setCursor(0, 0);
  lcd.print("TIME: ");
  printTwoDigits(hours);
  lcd.print(":");
  printTwoDigits(minutes);
  lcd.print(":");
  printTwoDigits(seconds);
  lcd.setCursor(0, 1);
  lcd.print("TEMP: ");
  lcd.print(temparature);
  lcd.print(" C");
}
 
// Функция для вывода на дисплей двух цифр:
// на первом знакоместе 0, если цифра меньше 10
void printTwoDigits(int digit) {
  if (digit < 10) {
    lcd.print("0");
  }
  lcd.print(digit);
}

№44. Карманный девайс

ClockThermometerLCD.ino
// Библиотека для работы с текстовым дисплеем по шине I²C
#include <LiquidCrystal_I2C.h>
// Библиотека для работы с кнопкой
#include <TroykaButton.h>
// Библиотека для работы с датчиком температуры TMP36
#include <TroykaThermometer.h>
 
// Создаём объект для работы с дисплеем
// В параметрах передаём I²C-адрес дисплея, кол-во строк и символов 
LiquidCrystal_I2C lcd(0x38, 16, 2);
 
// Создаём объекты кнопок для управления
TroykaButton buttonMinute(2);
TroykaButton buttonHour(3);
 
// Создаём объект для работы с датчиком температуры TMP36
// В параметре передаём пин, к которому подключен сенсор
TroykaThermometer tmp36(A0);
 
// Переменные для хранения часов, минут и секунд
int hours, minutes, seconds;
// Переменные для хранения температуры
float temparature;
 
long millisLastTime = 0;
long millisLastTemp = 0;
 
void setup() {
  // Инициализируем кнопки
  buttonMinute.begin();
  buttonHour.begin();
  // Инициализируем дисплей
  lcd.init();
  // Включаем подсветку
  lcd.backlight();
  // Устанавливаем курсор на первом знакоместе в первой строке
  lcd.setCursor(0, 0);
  // Устанавливаем тестовое время 12:45:30
  setTime(12, 45, 30);
}
 
void loop() {
  // Обновляем время посекундно
  updateTime();
  // Обновляем время через кнопки
  updateTimeButtons();
  // Обновляем температуру
  updateTemperature();
  // Преобразуем время
  wrapTime();
  // Выводим на дисплее время в формате «чч:мм:сс»
  printTimeLCD();
}
 
// Функция для установки времени
void setTime(int h, int m, int s) {
  hours = h;
  minutes = m;
  seconds = s;
}
 
// Функция для отсчёта времени
void updateTime() {
  long millisNowTime = millis();
  if (millisNowTime - millisLastTime > 1000) {
    millisLastTime = millisNowTime;
    seconds++;
  }
}
 
// Функция для обновления температуры
void updateTemperature() {
  long millisNowTemp = millis();
  if (millisNowTemp - millisLastTemp > 500) {
    millisLastTemp = millisNowTemp;
    tmp36.read();
    temparature = tmp36.getTemperatureC();
  }
}
 
// Функция для обновления времени через кнопки
void updateTimeButtons() {
  buttonHour.read();
  buttonMinute.read();
  if (buttonHour.isClick()) {
    hours++;
  }
  if (buttonMinute.isClick()) {
    minutes++;
  }
}
 
// Функция для форматирования времени
// При достижении лимита в 60 секунд, 60 минут и 24 часа отсчёт начинается заново
void wrapTime() {
  if (seconds >= 60) {
    minutes++;
    seconds -= 60;
  }
  if (minutes >= 60) {
    hours++;
    minutes -= 60;
  }
  if (hours >= 24) {
    hours = 0;
  }
}
 
// Функция для вывода на дисплее времени в формате «чч:мм:сс» и температуры
void printDataLCD() {
  lcd.setCursor(0, 0);
  lcd.print("TIME: ");
  printTwoDigits(hours);
  lcd.print(":");
  printTwoDigits(minutes);
  lcd.print(":");
  printTwoDigits(seconds);
  lcd.setCursor(0, 1);
  lcd.print("TEMP: ");
  lcd.print(temparature);
  lcd.print(" C");
}
 
// Функция для вывода на дисплей двух цифр:
// на первом знакоместе 0, если цифра меньше 10
void printTwoDigits(int digit) {
  if (digit < 10) {
    lcd.print("0");
  }
  lcd.print(digit);
}

№45. Биты и байты

BitsBytes.ino
// Библиотека для работы с кнопкой
#include <TroykaButton.h>
// Библиотека для работы с текстовым дисплеем по шине I²C
#include <LiquidCrystal_I2C.h>
 
// Создаём объект для работы с дисплеем
// В параметрах передаём I²C-адрес дисплея, кол-во строк и символов 
LiquidCrystal_I2C lcd(0x38, 16, 2);
 
// Создаём переменную для хранения тестового числа
byte value = 127;
 
// Максимальныое знакоместо на дисплее
constexpr int COL_MAX = 11;
 
// Индекс положения курсора
int digitIndex = 0;
 
// Создаём объекты кнопок для управления
TroykaButton buttonLeft(3);
TroykaButton buttonRight(2);
TroykaButton buttonSet(A3);
 
void setup() {
  // Инициализируем кнопки
  buttonLeft.begin();
  buttonRight.begin();
  buttonSet.begin();
  // Инициализируем дисплей
  lcd.init();
  // Включаем подсветку
  lcd.backlight();
  // Отображаем курсор
  lcd.cursor();
  // Включаем мигание курсора
  lcd.blink();
  // Печатаем данные на дисплей
  printDataLCD();
  lcd.setCursor(COL_MAX - digitIndex, 0);
}
 
void loop() {
  // Считываем состояние кнопок
  buttonLeft.read();
  buttonRight.read();
  buttonSet.read();
  // Если нажата кнопка «вправо», смещаем курсор на одно знакоместо «вправо»
  if (buttonRight.isClick() && digitIndex > 0) {
    digitIndex--;
    lcd.setCursor(COL_MAX - digitIndex, 0);
  }
  // Если нажата кнопка «влево», смещаем курсор на одно знакоместо «влево»
  if (buttonLeft.isClick() && digitIndex < 7) {
    digitIndex++;
    lcd.setCursor(COL_MAX - digitIndex, 0);
  }
  // Если нажата кнопка «Установка»
  // «переворачиваем» значение выбранного курсором бита
  if (buttonSet.isClick()) {
    bitInvert(digitIndex);
    printDataLCD();
    lcd.setCursor(COL_MAX - digitIndex, 0);
  }
}
 
// Функция для вывода числа на дисплей в системах счисления BIN и DEC
void printDataLCD() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("BIN:");
  printByteLCD();
  lcd.setCursor(0, 1);
  lcd.print("DEC:");
  lcd.print(value);
}
 
// Функция для вывода числа по байтам
void printByteLCD() {
  for (int i = 7; i >= 0; i--) {
    lcd.print(bitRead(value, i));
  }
}
 
// Функция для инвертирования значение бита
void bitInvert(int bitNumber) {
  bool bitValue = bitRead(value, bitNumber);
  bitValue = !bitValue;
  bitWrite(value, bitNumber, bitValue);
}

№46. Тайны знакогенератора

СharacterList.ino
// Библиотека для работы с текстовым дисплеем по шине I²C
#include <LiquidCrystal_I2C.h>
 
// Создаём объект для работы с дисплеем
// В параметрах передаём I²C-адрес дисплея, кол-во строк и символов 
LiquidCrystal_I2C lcd(0x38, 16, 2);
 
void setup() {
  // Инициализируем дисплей
  lcd.init();
  // Включаем подсветку
  lcd.backlight();
}
 
void loop() {
  // Устанавливаем курсор на третьем знакоместе в первой строке
  lcd.setCursor(2, 0);
  lcd.print("ASCII Table");
  // Выводим в цикле все символы знакогенератора с кодом от 0 до 255
  for (int i = 0 ; i <= 255 ; i++)  {
    lcd.setCursor(0, 1);
    if (i < 16) {
      lcd.print("0");
    }
    lcd.print(i, HEX);
    lcd.print(" = ");
    lcd.write(i);
    delay(500);
  }
  lcd.clear();
}

№47. Я календарь переключу…

СharacterSwitchPage.ino
// Библиотека для работы с текстовым дисплеем по шине I²C
#include <LiquidCrystal_I2C.h>
 
// Создаём объект для работы с дисплеем
// В параметрах передаём I²C-адрес дисплея, кол-во строк и символов 
LiquidCrystal_I2C lcd(0x38, 16, 2);
 
// Константа с адресом нулевой страницы знакогенератора
constexpr int LCD_PAGE_0 = 0b101000;
// Константа с адресом первой страницы знакогенератора
constexpr int LCD_PAGE_1 = 0b101010;
 
void setup() {
  // Инициализируем дисплей
  lcd.init();
  // Включаем подсветку
  lcd.backlight();
  // Устанавливаем курсор на четвёртом знакоместе в первой строке
  lcd.setCursor(3, 0);
  // Выводим строку на дисплей
  lcd.print("Switch Page");
  // Устанавливаем курсор на шестом знакоместе во второй строке
  lcd.setCursor(5, 1);
  // Выводим пять символов из таблицы знакогенератора
  lcd.print("\x9f\x9e\x9d\x9c\x9b");
}
 
void loop() {
  // Переключаем две страницы знакогенератора с интервалом каждую секунду
  lcd.command(LCD_PAGE_0);
  delay(1000);
  lcd.command(LCD_PAGE_1);
  delay(1000);
}

№48. Индикатор заряда

ChargeIndicator.ino
// Библиотека для работы с текстовым дисплеем по шине I²C
#include <LiquidCrystal_I2C.h>
 
// Даём понятное имя пину, который подключен
// к батарейному отсеку через резистивный делитель
constexpr int VOLTAGE_PIN = A3;
 
// Создаём объект для работы с дисплеем
// В параметрах передаём I²C-адрес дисплея, кол-во строк и символов 
LiquidCrystal_I2C lcd(0x38, 16, 2);
 
// Создаём константы резисторного делителя:
// номиналы резисторов и коэффициент делителя
constexpr float R1 = 10;
constexpr float R2 = 1;
constexpr float k = R2/(R2+R1);
 
// Создаём константы с адресами иконок заряда батареи из таблицы знакогенератора
constexpr int LCD_ICON_BATTERY_1 = 0x9B;
constexpr int LCD_ICON_BATTERY_2 = 0x9C;
constexpr int LCD_ICON_BATTERY_3 = 0x9D;
constexpr int LCD_ICON_BATTERY_4 = 0x9E;
constexpr int LCD_ICON_BATTERY_5 = 0x9F;
 
void setup() {
  // Инициализируем дисплей
  lcd.init();
  // Включаем подсветку
  lcd.backlight();
  // Устанавливаем курсор на первом знакоместе в первой строке
  lcd.setCursor(0, 0);
  // Выводим строку на дисплей
  lcd.print("Charge indicator");
}
 
void loop() {
  // Измеряем напряжение на батарейном отсеке
  float batVoltage = readVoltage();
  // Устанавливаем курсор на первом знакоместе в второй строке
  lcd.setCursor(0, 1);
  // Выводим напряжение на батарейном отсеке
  lcd.print(batVoltage);
  lcd.print(" V / ");
  // Выводим иконку, которая соответствует остаточному уровню заряда батареи
  if (batVoltage >= 9) {
    lcd.write(LCD_ICON_BATTERY_1);
  } else if (batVoltage >= 8) {
    lcd.write(LCD_ICON_BATTERY_2);
  } else if (batVoltage >= 7) {
    lcd.write(LCD_ICON_BATTERY_3);
  } else if (batVoltage >= 6) {
    lcd.write(LCD_ICON_BATTERY_4);
  } else {
    lcd.write(LCD_ICON_BATTERY_5);
  }
  delay(1000);
}
 
// Функция для считывания напряжения c батарейного отсека
float readVoltage() {
  // Считываем аналоговый сигнал с резисторного делителя
  int batADC = analogRead(VOLTAGE_PIN);
  // Преобразуем отсчёты АЦП в напряжение с учётом коэффициента делителя
  float batVoltage = ((batADC * 5.0) / 1024.0) / k;
  return batVoltage;
}

№49. Люксметр

Программный код №1

Luxmeter-v1.ino
// Библиотека для работы с текстовым дисплеем по шине I²C
#include <LiquidCrystal_I2C.h>
// Библиотека для работы с фоторезистором
#include <TroykaLight.h>
 
// Даём понятное имя пину с фоторезистором
constexpr int LDR_PIN = A3;
 
// Создаём объект для работы с дисплеем
// В параметрах передаём I²C-адрес дисплея, кол-во строк и символов 
LiquidCrystal_I2C lcd(0x38, 16, 2);
 
// Создаём объект для работы с фоторезистором
TroykaLight ldr(LDR_PIN);
 
void setup() {
  // Инициализируем дисплей
  lcd.init();
  // Включаем подсветку
  lcd.backlight();
  // Устанавливаем курсор на первом знакоместе в первой строке
  lcd.setCursor(0, 0);
  // Выводим строку на дисплей
  lcd.print("<Luxmeter>");
}
 
void loop() {
  // Считываем данные с фоторезистора
  ldr.read();
  // Запрашиваем освещенность у фоторезистора
  long ldrLight = ldr.getLightLux();
  // Устанавливаем курсор на первом знакоместе во второй строке
  lcd.setCursor(0, 1);
  // Выводим показания освещенности на дисплей
  lcd.print("L = ");
  lcd.print(ldrLight);
  lcd.print(" Lx");
  delay(1000);
}

Программный код №2

Luxmeter-v2.ino
// Библиотека для работы с текстовым дисплеем по шине I²C
#include <LiquidCrystal_I2C.h>
// Библиотека для работы с фоторезистором
#include <TroykaLight.h>
 
// Даём понятное имя пину с фоторезистором
constexpr int LDR_PIN = A3;
 
// Создаём объект для работы с дисплеем
// В параметрах передаём I²C-адрес дисплея, кол-во строк и символов 
LiquidCrystal_I2C lcd(0x38, 16, 2);
 
// Создаём объект для работы с фоторезистором
TroykaLight ldr(LDR_PIN);
 
void setup() {
  // Инициализируем дисплей
  lcd.init();
  // Включаем подсветку
  lcd.backlight();
  // Устанавливаем курсор на первом знакоместе в первой строке
  lcd.setCursor(0, 0);
  // Выводим строку на дисплей
  lcd.print("<Luxmeter>");
}
 
void loop() {
  // Считываем данные с фоторезистора
  ldr.read();
  // Запрашиваем освещенность у фоторезистора
  long ldrLight = ldr.getLightLux();
  // Создаём динамическую строку класса String, которая содержит шесть символов:
  // показание освещённости с ведущими нулями
  String ldrLightStr = longToStr(ldrLight, 6);
  // Устанавливаем курсор на первом знакоместе во второй строке
  lcd.setCursor(0, 1);
  // Выводим показания освещенности на дисплей
  lcd.print("L = ");
  lcd.print(ldrLightStr);
  lcd.print(" Lx");
  delay(1000);
}
 
// Функция для преобразования переменной типа long в переменную String,
// оставляя при этом заданное количество знаков.
String longToStr(long value, int countDigits) {
  String strValue;
  int lenValue = String(value).length();
  for (int i = countDigits; i > lenValue; i--) {
    strValue += "0";
  }
  strValue += String(value);
  return strValue;
}

№50. Терменвокс

Theremin.ino
// Даём понятное имя пину с фоторезистором
constexpr int LDR_PIN = A3;
// Даём понятное имя пину с пищалкой
constexpr int BUZZER_PIN = 7;
 
void setup() {
// Настраиваем пин с фоторезистором в режим входа
  pinMode(LDR_PIN, INPUT);
  // Настраиваем пин с пищалкой в режим выхода
  pinMode(BUZZER_PIN, OUTPUT);
}
 
void loop() {
  // Считываем аналоговый сигнал с фоторезистора
  int ldrLight = analogRead(LDR_PIN);
  // Преобразуем значение с фоторезистора в частотный диапазон для пищалки
  int frequency = map(ldrLight, 0, 1023, 500, 5000);
  // Заставляем пин с пищалкой звучать на высчитанной частоте
  tone(BUZZER_PIN, frequency);
}

№51. Умная подсветка

SmartBacklight.ino
// Библиотека для работы с фоторезистором
#include <TroykaLight.h>
// Библиотека для работы с текстовым дисплеем по шине I²C
#include <LiquidCrystal_I2C.h>
 
// Даём понятное имя пину с потенциометром
constexpr int POT_PIN = A2;
// Даём понятное имя пину с фоторезистором
constexpr int LDR_PIN = A3;
 
// Создаём объект для работы с фоторезистором
TroykaLight ldr(LDR_PIN);
 
// Создаём объект для работы с дисплеем
// В параметрах передаём I²C-адрес дисплея, кол-во строк и символов 
LiquidCrystal_I2C lcd(0x38, 16, 2);
 
void setup() {
  // Инициализируем дисплей
  lcd.init();
  // Включаем подсветку
  lcd.backlight();
}
 
void loop() {
  // Считываем аналоговый сигнал с потенциометра
  int potADC = analogRead(POT_PIN);
  // Преобразуем значение с потенциометра в диапазон освещённости
  // для порог срабатывания подсветки
  long limitLight = map(potADC, 0, 1023, 0, 100000);
  // Считываем данные с фоторезистора
  ldr.read();
  // Запрашиваем освещенность у фоторезистора
  long ldrLight = ldr.getLightLux();
  // Выводим на дисплей
  // порог освещенности срабатывания подсветки и текущую освещенность  
  printDataLCD(limitLight, ldrLight);
  if (ldrLight < limitLight) {
    // Если текущее показание фоторезистора ниже порога, включаем подсветку
    lcd.backlight();
  } else if (ldrLight > limitLight) {
    // Если текущее показание фоторезистора выше порога, выключаем подсветку
    lcd.noBacklight();
  }
  delay(1000);
}
 
// Функция для вывода на дисплей
// порога освещенности срабатывания подсветки и текущую освещенность 
void printDataLCD(long limitLight, long ldrLight) {
  String limitLightStr = longToStr(limitLight, 6);
  String ldrLightStr = longToStr(ldrLight, 6);
  lcd.setCursor(0, 0);
  lcd.print("P = ");
  lcd.print(limitLightStr);
  lcd.print(" Lx");
  lcd.setCursor(0, 1);
  lcd.print("L = ");
  lcd.print(ldrLightStr);
  lcd.print(" Lx");
}
 
// Функция для преобразования переменной типа long в переменную String,
// оставляя при этом заданное количество знаков.
String longToStr(long value, int countDigits) {
  String strValue;
  int lenValue = String(value).length();
  for (int i = countDigits; i > lenValue; i--) {
    strValue += "0";
  }
  strValue += String(value);
  return strValue;
}

№52. Умная подсветка без глюков

SmartBacklightHyst.ino
// Библиотека для работы с фоторезистором
#include <TroykaLight.h>
// Библиотека для работы с текстовым дисплеем по шине I²C
#include <LiquidCrystal_I2C.h>
 
// Даём понятное имя пину с потенциометром
constexpr int POT_PIN = A2;
// Даём понятное имя пину с фоторезистором
constexpr int LDR_PIN = A3;
 
// Создаём объект для работы с фоторезистором
TroykaLight ldr(LDR_PIN);
 
// Создаём объект для работы с дисплеем
// В параметрах передаём I²C-адрес дисплея, кол-во строк и символов 
LiquidCrystal_I2C lcd(0x38, 16, 2);
 
void setup() {
  // Инициализируем дисплей
  lcd.init();
  // Включаем подсветку
  lcd.backlight();
}
 
void loop() {
  // Считываем аналоговый сигнал с потенциометра
  int potADC = analogRead(POT_PIN);
  // Преобразуем значение с потенциометра в диапазон освещённости
  // для порог срабатывания подсветки
  long limitLight = map(potADC, 0, 1023, 0, 100000);
  // Создаём переменную для хранения гистерезиса
  int hyster = limitLight * 0.1 + 50;
  // Считываем данные с фоторезистора
  ldr.read();
  // Запрашиваем освещенность у фоторезистора
  long ldrLight = ldr.getLightLux();
  // Выводим на дисплей
  // порог освещенности срабатывания подсветки и текущую освещенность  
  printDataLCD(limitLight, ldrLight);
  if (ldrLight < limitLight - hyster) {
    // Если текущее показание фоторезистора ниже нижней границы гистерезиса,
    // включаем подсветку
    lcd.backlight();
  } else if (ldrLight > limitLight + hyster) {
    // Если текущее показание фоторезистора выше верхней границы гистерезиса,
    // выключаем подсветку
    lcd.noBacklight();
  }
  delay(1000);
}
 
// Функция для вывода на дисплей
// порога освещенности срабатывания подсветки и текущую освещенность 
void printDataLCD(long limitLight, long ldrLight) {
  String limitLightStr = longToStr(limitLight, 6);
  String ldrLightStr = longToStr(ldrLight, 6);
  lcd.setCursor(0, 0);
  lcd.print("P = ");
  lcd.print(limitLightStr);
  lcd.print(" Lx");
  lcd.setCursor(0, 1);
  lcd.print("L = ");
  lcd.print(ldrLightStr);
  lcd.print(" Lx");
}
 
// Функция для преобразования переменной типа long в переменную String,
// оставляя при этом заданное количество знаков.
String longToStr(long value, int countDigits) {
  String strValue;
  int lenValue = String(value).length();
  for (int i = countDigits; i > lenValue; i--) {
    strValue += "0";
  }
  strValue += String(value);
  return strValue;
}

№53. Оптопрерыватель

SecuritySystem.ino
// Библиотека для работы с фоторезистором
#include <TroykaLight.h>
// Библиотека для работы с текстовым дисплеем по шине I²C
#include <LiquidCrystal_I2C.h>
 
// Даём понятное имя пину с потенциометром
constexpr int POT_PIN = A2;
// Даём понятное имя пину с фоторезистором
constexpr int LDR_PIN = A3;
 
// Создаём объект для работы с фоторезистором
TroykaLight ldr(LDR_PIN);
 
// Создаём объект для работы с дисплеем
// В параметрах передаём I²C-адрес дисплея, кол-во строк и символов 
LiquidCrystal_I2C lcd(0x38, 16, 2);
 
long ldrLight;
long lightNormal;
int hyster;
 
// Переменная для хранения кол-ва проходящих нарушителей
int passCount = 0;
 
void setup() {
  // Настраиваем пин с пищалкой в режим выхода
  pinMode(BUZZER_PIN, OUTPUT);
  // Инициализируем дисплей
  lcd.init();
  // Включаем подсветку
  lcd.backlight();
  // Устанавливаем курсор на первом знакоместе в первой строке
  lcd.setCursor(0, 0);
  // Выводим строку на дисплей
  lcd.print("Security system");
  // Устанавливаем курсор на первом знакоместе во второй строке
  lcd.setCursor(0, 1);
  // Выводим строку на дисплей
  lcd.print("Pass count: ");
  // Печатаем кол-во проходящих нарушителей
  lcd.print(passCount);
  // Считываем данные с фоторезистора
  ldr.read();
  // Запрашиваем освещенность у фоторезистора
  ldrLight = ldr.getLightLux();
  // Корректируем значение штатного освещения и гистерезиса
  updateLightNormal(ldrLight);
}
 
void loop() {
  // Считываем данные с фоторезистора
  ldr.read();
  // Запрашиваем освещенность у фоторезистора
  ldrLight = ldr.getLightLux();
  if (ldrLight < lightNormal - hyster) {
    // Если уровень освещённости падает ниже нормального с учётом гистерезиса
    lcd.setCursor(12, 1);
    // Обновляем кол-во нарушителей
    lcd.print(++passCount);
    // Пищим зуммеров
    playMelodyAlarm();
  } else {
    // Корректируем значение штатного освещения и гистерезиса
    updateLightNormal(ldrLight);
  }
  delay(100);
}
 
// Функция для корректировки значений штатного освещения и гистерезиса
void updateLightNormal(long ldrLight) {
  lightNormal = ldrLight;
  hyster = lightNormal * 0.1 + 50;
}
 
// Функция для воспроизведения сигнала тревоги
void playMelodyAlarm() {
  for (int i = 0; i <= 2; i++) {
    tone(BUZZER_PIN, 200, 300);
    delay(200);
    tone(BUZZER_PIN, 120, 300);
    delay(200);
  }
}

№54. Заметки мейкера

MakerNotes.ino
// Библиотека для работы с текстовым дисплеем по шине I²C
#include <LiquidCrystal_I2C.h>
 
// Создаём объект для работы с дисплеем
// В параметрах передаём I²C-адрес дисплея, кол-во строк и символов 
LiquidCrystal_I2C lcd(0x38, 16, 2);
 
// Константа для хранения паузы в миллисекундах
constexpr int PAUSE = 5000;
 
// Создаём три строки в виде массива символов
char str1[] = "1. Read the manual carefully.";
char str2[] = "2. Wire all breadboard contents.";
char str3[] = "3. Flash the Iskra Nano board.";
 
void setup() {
  // Инициализируем дисплей
  lcd.init();
  // Включаем подсветку
  lcd.backlight();
}
 
void loop() {
  // Поочерёдно выводим строки на дисплей
  // с автопереносом символов на следующую строку
  printLCDAutoLine(str1);
  delay(PAUSE);
  printLCDAutoLine(str2);
  delay(PAUSE);
  printLCDAutoLine(str3);
  delay(PAUSE);
}
 
// Функция для вывода строки на дисплей
// с автопереносом символов на следующую строку
void printLCDAutoLine(char *str) {
  lcd.clear();
  lcd.setCursor(0, 0);
  for (int i = 0; i < 16 && str[i] != '\0'; i++) {
    lcd.print(str[i]);
  }
  lcd.setCursor(0, 1);
  for (int i = 16; i < 32 && str[i] != '\0'; i++) {
    lcd.print(str[i]);
  }
}

Библиотека LiquidCrystal_I2C

№55. Кастомные символы

CustomSymbols.ino
// Библиотека для работы с текстовым дисплеем по шине I²C
#include <LiquidCrystal_I2C.h>
 
// Создаём объект для работы с дисплеем
// В параметрах передаём I²C-адрес дисплея, кол-во строк и символов 
LiquidCrystal_I2C lcd(0x38, 16, 2);
 
// Создаём символ динозаврика в двоичной системе BIN
byte dino[8] = {
  0b00000, 0b00111, 0b00111, 0b10110,
  0b11111, 0b01010, 0b01010, 0b00000
};
 
// Создаём символ кактуса в двоичной системе BIN
byte cactus[8] = {
  0b00100, 0b00101, 0b10101, 0b10101,
  0b10111, 0b11100, 0b00100, 0b00000
};
 
// Создаём символ сердца в двоичной системе BIN
byte heart[8] = {
  0b00000, 0b01010, 0b11111, 0b11111,
  0b11111, 0b01110, 0b00100, 0b00000
};
 
// Константы для хранения адресов символов из таблицы знакогенератора
constexpr byte LCD_ICON_DINO = 0x00;
constexpr byte LCD_ICON_CACTUS = 0x01;
constexpr byte LCD_ICON_HEART = 0x02;
 
void setup() {
  // Инициализируем дисплей
  lcd.init();
  // Включаем подсветку
  lcd.backlight();
  // Добавляем собственный символы в ячейки знакогенератора
  lcd.createChar(LCD_ICON_DINO, dino);
  lcd.createChar(LCD_ICON_CACTUS, cactus);
  lcd.createChar(LCD_ICON_HEART, heart);
  // Выводим на дисплей строки и созданные символы
  lcd.setCursor(0, 0);
  lcd.print("I ");
  lcd.write(LCD_ICON_HEART);
  lcd.print(" Arduino! ");
  lcd.setCursor(0, 1);
  lcd.write(LCD_ICON_DINO);
  lcd.setCursor(5, 1);
  lcd.write(LCD_ICON_CACTUS);
  lcd.setCursor(8, 1);
  lcd.write(LCD_ICON_CACTUS);
  lcd.setCursor(13, 1);
  lcd.print("\x02");
}
 
void loop() {
}

Библиотека LiquidCrystal_I2C

№56. Бегущий динозаврик

RunningDino.ino
// Библиотека для работы с текстовым дисплеем по шине I²C
#include <LiquidCrystal_I2C.h>
// Библиотека для работы с кнопкой
#include <TroykaButton.h>
 
// Создаём объект для работы с дисплеем
// В параметрах передаём I²C-адрес дисплея, кол-во строк и символов 
LiquidCrystal_I2C lcd(0x38, 16, 2);
 
// Даём понятное имя пину с пищалкой
constexpr int BUZZER_PIN = 7;
 
// Создаём звуковой шрифт из нот со своими частотами
constexpr int NOTE_B3 = 247;
constexpr int NOTE_C4 = 262;
constexpr int NOTE_D4 = 294;
constexpr int NOTE_E4 = 330;
constexpr int NOTE_F4 = 349;
constexpr int NOTE_G4 = 392;
constexpr int NOTE_A4 = 440;
constexpr int NOTE_B4 = 494;
constexpr int NOTE_C5 = 523;
 
// Создаём символ динозаврика в двоичной системе BIN
byte dino[8] = {
  0b00000, 0b00111, 0b00111, 0b10110,
  0b11111, 0b01010, 0b01010, 0b00000
};
 
// Создаём символ кактуса в двоичной системе BIN
byte cactus[8] = {
  0b00100, 0b00101, 0b10101, 0b10101,
  0b10111, 0b11100, 0b00100, 0b00000
};
 
// Назначаем константы с адресами кастомных символов в таблице знакогенератора
constexpr byte LCD_ICON_DINO = 0x00;
constexpr byte LCD_ICON_CACTUS = 0x01;
 
// Назначаем константы с адресами штатных символов в таблице знакогенератора
constexpr byte LCD_ICON_BLOCK = 0xFF;
constexpr byte LCD_ICON_EMPTY = 0x20;
 
// Переменные для подсчёта набранных очков и хранения рекордов
int score = 0;
int bestScore = 0;
 
// Переменная для блокировки очков
bool stateFreezeScore = false;
 
// Массив игровой области
// Нижняя строка дисплея
char gameArea[16];
// Ячейка дисплея
// динозаврик в прыжке или нет
char gameDinoJump;
 
long millisLastTime = 0;
long millisLastJump = 0;
 
// Время зависания динозавра в воздухе
int jumpInterval = 50;
// Скорость прокрутки дисплея
int speedScroller = 300;
 
// Создаём перечисления состояний игры
enum {
  GAME_BEGIN,
  GAME_PLAY,
  GAME_OVER
} stateGame;
 
// Создаём объект для работы с кнопкой
TroykaButton button(5);
 
void setup() {
  // Инициализируем дисплей
  lcd.init();
  // Включаем подсветку
  lcd.backlight();
  // Добавляем собственные символы в ячейки знакогенератора
  lcd.createChar(LCD_ICON_DINO, dino);
  lcd.createChar(LCD_ICON_CACTUS, cactus);
  // Инициализируем кнопку
  button.begin();
  // Задаём зерно генерации случайных чисел
  randomSeed(analogRead(A0));
  // Устанавливаем режим «Начало игры»
  stateGame = GAME_BEGIN;
}
 
void loop() {
  // Если установлен режим «Начало игры»
  if (stateGame == GAME_BEGIN) {
    // Обнуляем текущий результат
    score = 0;
    // Очищаем массив игровой зоны
    for (int i = 0; i <= 15; i++) {
      gameArea[i] = LCD_ICON_EMPTY;
    }
    // Выводим начальную заставку на дисплей
    printGameBegin();
    // Выводим приветственную музыку
    melodyGameBegin();
    // Ожидаем нажатие кнопки для старта
    while (!button.isClick()) {
      button.read();
    }
    // Устанавливаем режим «Процесс игры»
    stateGame = GAME_PLAY;
    // Очищаем дисплей
    lcd.clear();
    delay(1000);
  }
 
  // Если установлен режим «Процесс игры»
  if (stateGame == GAME_PLAY) {
    // и функция playGame выдаёт ложное значение,
    // переходим в режим «Конец игры».
    if (playGame() == false) {
      stateGame = GAME_OVER;
    }
  }
 
  // Если установлен режим «Конец игры»
  if (stateGame == GAME_OVER) {
    // Выводим конечную заставку на дисплей
    printGameOver();
    // Выводим завершающую музыку
    melodyGameOver();
    // Ждём нажатия кнопки для повторной игры
    while (!button.isClick()) {
      button.read();
    }
    // Заново переходим в режим «Начало игры»
    stateGame = GAME_BEGIN;
    delay(1000);
  }
}
 
// Функция вывода начальной заставки на экран
void printGameBegin() {
  // Очищаем дисплей
  lcd.clear();
  lcd.setCursor(4, 0);
  lcd.print("Dino Game");
  lcd.setCursor(3, 1);
  lcd.print("Press Start");
  // Выводим два барьера по бокам
  lcd.setCursor(0, 1);
  lcd.write(LCD_ICON_BLOCK);
  lcd.setCursor(15, 1);
  lcd.write(LCD_ICON_BLOCK);
  // Выводим динозаврика
  lcd.setCursor(1, 1);
  lcd.write(LCD_ICON_DINO);
}
 
// Функция вывода конечной заставки на экран
void printGameOver() {
  // Выводим сообщение «Игра окончена»
  lcd.setCursor(4, 1);
  lcd.print("Game Over");
  delay(1000);
  lcd.clear();
  // Выводим набранный счёт
  lcd.setCursor(4, 0);
  lcd.print("Score: ");
  lcd.print(score);
  // Если счёт превзошёл предыдущий рекорд, обновляем значение рекорда
  if (score > bestScore) {
    bestScore = score;
  }
  // Выводим лучший результат игры за все попытки
  lcd.setCursor(5, 1);
  lcd.print("Best: ");
  lcd.print(bestScore);
}
 
// Функция обработки игрового процесса
bool playGame() {
  button.read();
  // Запоминаем текущее время
  unsigned long millisNowTime = millis();
  // Если прошёл заданный интервал
  if (millisNowTime - millisLastTime >= speedScroller) {
    millisLastTime = millisNowTime;
    // Генерируем случайно число от 0 до 10
    int randomNumber = random(0, 10);
    // Если выпало число 0
    if (randomNumber == 0) {
      // Выводим препятствие
      gameArea[15] = LCD_ICON_CACTUS;
    } else {
      // В остальных случаях выводим пустоту
      gameArea[15] = LCD_ICON_EMPTY;
    }
    // Сдвигаем игровую область дисплея на шаг влево
    for (int i = 0; i <= 15; i++) {
      gameArea[i] = gameArea[i + 1];
    }
    // Если блокировка очков выключена, увеличиваем очки
    if (!stateFreezeScore) {
      score++;
    }
  }
  // Выводим барьеры
  gameArea[0] = LCD_ICON_BLOCK;
  gameArea[15] = LCD_ICON_BLOCK;
 
  // Если нажата кнопка
  if (button.isPressed()) {
    tone(BUZZER_PIN, 1000, 100);
    // Включаем динозаврика в прыжке
    gameDinoJump = LCD_ICON_DINO;
    // Включаем заморозку очков
    stateFreezeScore = true;
    // Запоминаем текущее время прыжка
    millisLastJump = millis();
  }
 
  // Если время прыжка истекло и внизу нет препятствия
  if (millis() - millisLastJump >= jumpInterval) {
    if (gameArea[1] == LCD_ICON_EMPTY || 
        gameArea[1] == LCD_ICON_DINO) {
      // Возвращаем динозаврика на землю
      gameArea[1] = LCD_ICON_DINO;
      gameDinoJump = LCD_ICON_EMPTY;
      // Выключаем заморозку очков
      stateFreezeScore = false;
    } else {
      return false;
    }
  }
  // Обновляем содержимое на дисплее
  updateDataLCD();
  return true;
}
 
// Функция вывода данных на дисплей
void updateDataLCD() {
  // Печатаем игровое поле
  for (int i = 0; i <= 15; i++) {
    lcd.setCursor(i, 1);
    lcd.write(gameArea[i]);
  }
  // Выводим  ячейку для прыжка и текущее количество очков
  lcd.setCursor(1, 0);
  lcd.write(gameDinoJump);
  lcd.setCursor(4, 0);
  lcd.print("Score: ");
  lcd.print(score);
}
 
// Функция вступительной мелодии
void melodyGameBegin() {
    tone(BUZZER_PIN, NOTE_E4, 100);
    delay(150);
    tone(BUZZER_PIN, NOTE_E4,100);
    delay(300);
    tone(BUZZER_PIN, NOTE_E4,100);
    delay(300);
    tone(BUZZER_PIN, NOTE_C4,100);
    delay(100);
    tone(BUZZER_PIN, NOTE_E4,100);
    delay(300);
    tone(BUZZER_PIN, NOTE_G4, 100);
    delay(550);
    tone(BUZZER_PIN, NOTE_C5, 100);
    delay(575);
}
 
// Функция завершающей мелодии
void melodyGameOver() {
    for (int i = 0; i <= 2; i++) {
    tone(BUZZER_PIN, NOTE_G4, 300);
    delay(200);
    tone(BUZZER_PIN, NOTE_C4, 300);
    delay(200);
  }
}

№57. Светодиодная лента своими руками

№58. Сдаём вождение по току

BlinkBJT.ino
// Даём понятное имя пину с транзистором,
// который управляет светодиодной лентой
constexpr int PIN_TRANSISTOR_BJT = 2;
 
void setup() {
   // Настраиваем пин с транзистором в режим выхода
  pinMode(PIN_TRANSISTOR_BJT, OUTPUT);
}
 
void loop() {
  // Зажигаем ленту
  digitalWrite(PIN_TRANSISTOR_BJT, HIGH);
  // Ждём 200 мс
  delay(200);
  // Гасим ленту
  digitalWrite(PIN_TRANSISTOR_BJT, LOW);
  // Ждём 800 мс
  delay(800);
}

№59. Волшебное прикосновение

№60. Сдаём вождение по напряжению

BlinkMOSFET.ino
// Даём понятное имя пину с транзистором,
// который управляет светодиодной лентой
constexpr int PIN_MOSFET = 2;
 
void setup() {
  // Настраиваем пин с транзистором в режим выхода
  pinMode(PIN_MOSFET, OUTPUT);
}
 
void loop() {
  // Зажигаем ленту
  digitalWrite(PIN_MOSFET, HIGH);
  // Ждём 200 мс
  delay(200);
  // Гасим ленту
  digitalWrite(PIN_MOSFET, LOW);
  // Ждём 800 мс
  delay(800);
}

№61. Саймон говорит

SimonSays.ino
// Библиотека для работы с текстовым дисплеем по шине I²C
#include <LiquidCrystal_I2C.h>
// Создаём объект для работы с дисплеем
// В параметрах передаём I²C-адрес дисплея, кол-во строк и символов 
LiquidCrystal_I2C lcd(0x38, 16, 2);
 
// Создаём звуковой шрифт из нот со своими частотами
constexpr int NOTE_C3 = 131;
constexpr int NOTE_D3 = 147;
constexpr int NOTE_E3 = 165;
constexpr int NOTE_F3 = 175;
constexpr int NOTE_G3 = 196;
constexpr int NOTE_A3 = 220;
constexpr int NOTE_B3 = 247;
constexpr int NOTE_C4 = 262;
constexpr int NOTE_D4 = 294;
constexpr int NOTE_E4 = 330;
constexpr int NOTE_F4 = 349;
constexpr int NOTE_G4 = 392;
constexpr int NOTE_A4 = 440;
constexpr int NOTE_B4 = 494;
constexpr int NOTE_C5 = 523;
 
// Даём понятное имя пину с пищалкой
constexpr int BUZZER_PIN = 2;
 
// Даём понятные имена пинам со светодиодами
constexpr int LED_1_PIN = 3;
constexpr int LED_2_PIN = 4;
constexpr int LED_3_PIN = 5;
constexpr int LED_4_PIN = 6;
 
// Даём понятные имена пинам с кнопками
constexpr int BUTTON_1_PIN = A3;
constexpr int BUTTON_2_PIN = A2;
constexpr int BUTTON_3_PIN = A1;
constexpr int BUTTON_4_PIN = A0;
 
// Массив нот для мелодии «Начало игры»
constexpr int NOTE_GAME_BEGIN[] = {
  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
};
 
// Массив длительности нот для мелодии «Начало игры
constexpr int NOTE_DURATION_GAME_BEGIN[] = {
  8, 4, 8, 4, 8, 4, 8, 8, 8, 8, 4, 8, 2
};
 
// Количество нот в мелодии «Начало игры
constexpr int NOTE_COUNT_GAME_BEGIN = 13;
 
// Массив нот для мелодии «Выигрыш»
constexpr int NOTE_GAME_WIN[] = {
  NOTE_C4, NOTE_C4, NOTE_G4, NOTE_C5, NOTE_G4, NOTE_C5
};
 
// Массив длительности нот для мелодии «Выигрыш»
constexpr int NOTE_DURATION_GAME_WIN[] = {
  8, 8, 8, 4, 8, 4
};
// Количество нот в мелодии «Выигрыш»
constexpr int NOTE_COUNT_GAME_WIN = 6;
 
// Массив пинов со светодиодами
constexpr int LED_NUMBER[] = {
  LED_1_PIN, LED_2_PIN, LED_3_PIN, LED_4_PIN
};
 
// Массив нот для светодиодов
constexpr int LED_NOTE[] = { NOTE_F3, NOTE_G3, NOTE_A3, NOTE_B3 };
 
// Массив пинов с кнопками
constexpr int BUTTON_NUMBER[] = {
  BUTTON_1_PIN, BUTTON_2_PIN, BUTTON_3_PIN, BUTTON_4_PIN
};
 
// Количество светодиодов и кнопок в массиве
constexpr int LED_BUTTON_COUNTS = 4;
 
// Переменные для текущего и лучшего счёта
int score = 0;
int scoreBest = 0;
// Константа максимального счёта
constexpr int SCORE_MAX = 128;
 
// Массив последовательности случайно сгенерированных комбинаций
int randomArray[SCORE_MAX];
 
// Массив последовательности нажатия кнопок
int inputArray[SCORE_MAX];
 
// Создаём перечисления состояний игры
enum {
  GAME_BEGIN,
  GAME_PLAY,
  GAME_OVER
} stateGame;
 
void setup() {
  // Инициализируем дисплей
  lcd.init();
  // Включаем подсветку
  lcd.backlight();
  // Назначаем пины светодиодов в режим выхода
  // А пины кнопок в режим входа с подтяжкой к питанию
  for (int i = 0; i < LED_BUTTON_COUNTS; i++) {
    pinMode(LED_NUMBER[i], OUTPUT);
    pinMode(BUTTON_NUMBER[i], INPUT_PULLUP);
  }
  // Задаём зерно генерации случайных чисел
  randomSeed(analogRead(A6));
  // Устанавливаем режим «Начало игры»
  stateGame = GAME_BEGIN;
}
 
void loop() {
  // Если установлен режим «Начало игры»
  if (stateGame == GAME_BEGIN) {
    // Обнуляем текущий результат
    score = 0;
    // Выводим приветственную заставку на дисплей
    printLCDGameBegin();
    // Выводим приветственную музыку
    playMelodyGameBegin();
    // Даём пользователю время на просмотр заставки
    delay(1000);
    // Очищаем дисплей
    lcd.clear();
    // Устанавливаем режим «Процесс игры»
    stateGame = GAME_PLAY;
  }
 
  // Если установлен режим «Процесс игры»
  if (stateGame == GAME_PLAY) {
    // и функция playGame выдаёт ложное значение,
    // переходим в режим «Конец игры».
    if (playGame() == false) {
      stateGame = GAME_OVER;
    }
  }
 
  // Если установлен режим «Конец игры»
  if (stateGame == GAME_OVER) {
    // Выводим конечную заставку на дисплей
    printLCDGameOver();
    // Выводим завершающую музыку
    playMelodyGameOver();
    stateGame = GAME_BEGIN;
    delay(1000);
  }
}
 
// Функция обработки игрового процесса
bool playGame() {
  lcd.setCursor(2, 0);
  lcd.print("<Simon Says>");
  lcd.setCursor(0, 1);
  lcd.print("Score:");
  while (true) {
    // Отображаем на дисплее текущее количество очков
    lcd.setCursor(6, 1);
    lcd.print(score);
    // Зажигаем все светодиоды
    ledsSetAll(HIGH);
    // Включаем победную мелодию
    playMelodyGameWin();
    // Гасим все светодиоды
    ledsSetAll(LOW);
    // Ждём
    delay(1000);
    // Демонстрируем случайную последовательность светодиодов
    generateRandomArray();
    // Игра продолжается и счёт увеличивается,
    // пока функция readInputArray истинна.
    if (readInputArray()) {
      score++;
    } else {
      return false;
    }
  }
}
 
// Функция вывода начальной заставки на экран
void printLCDGameBegin() {
  // Очищаем дисплей
  lcd.clear();
  // Выводим текст начальной заставки
  lcd.setCursor(2, 0);
  lcd.print("<Simon Says>");
  lcd.setCursor(4, 1);
  lcd.print("DIY Game");
}
 
// Функция вывода конечной заставки на экран
void printLCDGameOver() {
  lcd.clear();
  lcd.setCursor(0, 1);
  lcd.print("Score:");
  lcd.print(score);
  // Если счёт превзошёл предыдущий рекорд, обновляем значение рекорда
  if (score > scoreBest) {
    scoreBest = score;
  }
  // Выводим лучший результат игры за все попытки
  lcd.setCursor(0, 0);
  lcd.print("Best: ");
  lcd.print(scoreBest);
}
 
// Функция вывода мелодии «Начало игры
void playMelodyGameBegin() {
  // Выполняем перебор нот
  for (int i = 0; i < NOTE_COUNT_GAME_BEGIN; i++) {
    int led = random(0, LED_BUTTON_COUNTS);
    // Зажигаем случайно выбранный светодиод
    digitalWrite(LED_NUMBER[led], HIGH);
    // Играем ноту с определённой длительностью
    tone(BUZZER_PIN, NOTE_GAME_BEGIN[i], 1000 / NOTE_DURATION_GAME_BEGIN[i]);
    // Делаем задержку для выделения нот в мелодии:
    // Длительность текущей ноты плюс 30% 
    delay((1000 / NOTE_DURATION_GAME_BEGIN[i]) * 1.3);
    // Гасим случайно выбранный светодиод
    digitalWrite(LED_NUMBER[led], LOW);
  }
}
 
// Функция вывода мелодии «Выигрыш»
void playMelodyGameWin() {
  // Выполняем перебор нот
  for (int i = 0; i < NOTE_COUNT_GAME_WIN; i++) {
    // Играем ноту с определённой длительностью
    tone(BUZZER_PIN, NOTE_GAME_WIN[i], 1000 / NOTE_DURATION_GAME_WIN[i]);
    // Делаем задержку для выделения нот в мелодии:
    // Длительность текущей ноты плюс 30% 
    delay((1000 / NOTE_DURATION_GAME_WIN[i]) * 1.3);
  }
}
 
// Функция вывода мелодии «Проигрыш»
void playMelodyGameOver() {
  for (int i = 0; i <= 2; i++) {
    // Зажигаем все светодиоды
    ledsSetAll(HIGH);
    // Издаём звуковой сигнал
    tone(BUZZER_PIN, NOTE_G3, 300);
    delay(200);
    // Гасим все светодиоды
    ledsSetAll(LOW);
    // Издаём звуковой сигнал
    tone(BUZZER_PIN, NOTE_C3, 300);
    delay(200);
  }
}
 
// Функция вывода произвольной комбинации светодиодов
void generateRandomArray() {
  // Генерируем случайное число в диапазоне от 0 до 3
  randomArray[score] = random(0, LED_BUTTON_COUNTS);
  // Зажигаем по очереди всю последовательность светодиодов
  for (int scoreNow = 0; scoreNow <= score; scoreNow++) {
    ledBeep(randomArray[scoreNow]);
  }
}
 
// Функция считывания угадываемой комбинации кнопок
bool readInputArray() {
  int scoreNow = 0;
  // Пока количество нажатий на кнопки не превысило количество очков
  while (scoreNow <= score) {
    // Проверяем каждую кнопку
    for (int i = 0; i < LED_BUTTON_COUNTS; i++) {
      if (!digitalRead(BUTTON_NUMBER[i])) {
        // Подаём светодиодный звуковой сигнал нажатой кнопки
        ledBeep(i);
        // Присваиваем массиву кода кнопок, текущий номер кнопки
        inputArray[scoreNow] = i;
        delay(250);
        // Если текущая нажатая клавиша не совпадает со случайно сгенерированной
        if (inputArray[scoreNow] != randomArray[scoreNow]) {
          // Функция ложна
          return false;
        }
        scoreNow++;
      }
    }
  }
  return true;
}
 
// Функция включение или отключение всех светодиодов
void ledsSetAll(bool state) {
  for (int i = 0; i < LED_BUTTON_COUNTS; i++) {
    digitalWrite(LED_NUMBER[i], state);
  }
}
 
// Функция светодиодного звукового сигнала
void ledBeep(int led) {
  // Зажигаем выбранный светодиод
  digitalWrite(LED_NUMBER[led], HIGH);
  // Подаём звуковой сигнал зажжённого светодиода
  tone(BUZZER_PIN, LED_NOTE[led], 100);
  delay(400);
  // Гасим выбранный светодиод
  digitalWrite(LED_NUMBER[led], LOW);
  delay(100);
}

Библиотека LiquidCrystal_I2C

№62. Саймон говорит с сохранением

SimonSaysEEPROM.ino
// Библиотека для доступа к энергонезависимой памяти EEPROM
#include <EEPROM.h>
// Библиотека для работы с текстовым дисплеем по шине I²C
#include <LiquidCrystal_I2C.h>
// Создаём объект для работы с дисплеем
// В параметрах передаём I²C-адрес дисплея, кол-во строк и символов 
LiquidCrystal_I2C lcd(0x38, 16, 2);
 
// Константа для хранения адреса ячейки в памяти EEPROM
// для сохранения и считывания данных игры
constexpr int EEPROM_ADDRESS = 200;
 
// Создаём звуковой шрифт из нот со своими частотами
constexpr int NOTE_C3 = 131;
constexpr int NOTE_D3 = 147;
constexpr int NOTE_E3 = 165;
constexpr int NOTE_F3 = 175;
constexpr int NOTE_G3 = 196;
constexpr int NOTE_A3 = 220;
constexpr int NOTE_B3 = 247;
constexpr int NOTE_C4 = 262;
constexpr int NOTE_D4 = 294;
constexpr int NOTE_E4 = 330;
constexpr int NOTE_F4 = 349;
constexpr int NOTE_G4 = 392;
constexpr int NOTE_A4 = 440;
constexpr int NOTE_B4 = 494;
constexpr int NOTE_C5 = 523;
 
// Даём понятное имя пину с пищалкой
constexpr int BUZZER_PIN = 2;
 
// Даём понятные имена пинам со светодиодами
constexpr int LED_1_PIN = 3;
constexpr int LED_2_PIN = 4;
constexpr int LED_3_PIN = 5;
constexpr int LED_4_PIN = 6;
 
// Даём понятные имена пинам с кнопками
constexpr int BUTTON_1_PIN = A3;
constexpr int BUTTON_2_PIN = A2;
constexpr int BUTTON_3_PIN = A1;
constexpr int BUTTON_4_PIN = A0;
 
// Массив нот для мелодии «Начало игры»
constexpr int NOTE_GAME_BEGIN[] = {
  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
};
 
// Массив длительности нот для мелодии «Начало игры
constexpr int NOTE_DURATION_GAME_BEGIN[] = {
  8, 4, 8, 4, 8, 4, 8, 8, 8, 8, 4, 8, 2
};
 
// Количество нот в мелодии «Начало игры
constexpr int NOTE_COUNT_GAME_BEGIN = 13;
 
// Массив нот для мелодии «Выигрыш»
constexpr int NOTE_GAME_WIN[] = {
  NOTE_C4, NOTE_C4, NOTE_G4, NOTE_C5, NOTE_G4, NOTE_C5
};
 
// Массив длительности нот для мелодии «Выигрыш»
constexpr int NOTE_DURATION_GAME_WIN[] = {
  8, 8, 8, 4, 8, 4
};
// Количество нот в мелодии «Выигрыш»
constexpr int NOTE_COUNT_GAME_WIN = 6;
 
// Массив пинов со светодиодами
constexpr int LED_NUMBER[] = {
  LED_1_PIN, LED_2_PIN, LED_3_PIN, LED_4_PIN
};
 
// Массив нот для светодиодов
constexpr int LED_NOTE[] = { NOTE_F3, NOTE_G3, NOTE_A3, NOTE_B3 };
 
// Массив пинов с кнопками
constexpr int BUTTON_NUMBER[] = {
  BUTTON_1_PIN, BUTTON_2_PIN, BUTTON_3_PIN, BUTTON_4_PIN
};
 
// Количество светодиодов и кнопок в массиве
constexpr int LED_BUTTON_COUNTS = 4;
 
// Переменные для текущего и лучшего счёта
int score = 0;
int scoreBest = 0;
// Константа максимального счёта
constexpr int SCORE_MAX = 128;
 
// Массив последовательности случайно сгенерированных комбинаций
int randomArray[SCORE_MAX];
 
// Массив последовательности нажатия кнопок
int inputArray[SCORE_MAX];
 
// Создаём перечисления состояний игры
enum {
  GAME_BEGIN,
  GAME_PLAY,
  GAME_OVER
} stateGame;
 
void setup() {
  // Инициализируем дисплей
  lcd.init();
  // Включаем подсветку
  lcd.backlight();
  // Назначаем пины светодиодов в режим выхода
  // А пины кнопок в режим входа с подтяжкой к питанию
  for (int i = 0; i < LED_BUTTON_COUNTS; i++) {
    pinMode(LED_NUMBER[i], OUTPUT);
    pinMode(BUTTON_NUMBER[i], INPUT_PULLUP);
  }
  // Задаём зерно генерации случайных чисел
  randomSeed(analogRead(A6));
  // Считываем лучший результат игры из EEPROM
  EEPROM.get(EEPROM_ADDRESS, scoreBest);
  // Устанавливаем режим «Начало игры»
  stateGame = GAME_BEGIN;
}
 
void loop() {
  // Если установлен режим «Начало игры»
  if (stateGame == GAME_BEGIN) {
    // Обнуляем текущий результат
    score = 0;
    // Выводим приветственную заставку на дисплей
    printLCDGameBegin();
    // Выводим приветственную музыку
    playMelodyGameBegin();
    // Даём пользователю время на просмотр заставки
    delay(1000);
    // Очищаем дисплей
    lcd.clear();
    // Устанавливаем режим «Процесс игры»
    stateGame = GAME_PLAY;
  }
 
  // Если установлен режим «Процесс игры»
  if (stateGame == GAME_PLAY) {
    // и функция playGame выдаёт ложное значение,
    // переходим в режим «Конец игры».
    if (playGame() == false) {
      stateGame = GAME_OVER;
    }
  }
 
  // Если установлен режим «Конец игры»
  if (stateGame == GAME_OVER) {
    // Выводим конечную заставку на дисплей
    printLCDGameOver();
    // Выводим завершающую музыку
    playMelodyGameOver();
    stateGame = GAME_BEGIN;
    delay(1000);
  }
}
 
// Функция обработки игрового процесса
bool playGame() {
  lcd.setCursor(2, 0);
  lcd.print("<Simon Says>");
  lcd.setCursor(0, 1);
  lcd.print("Score:");
  while (true) {
    // Отображаем на дисплее текущее количество очков
    lcd.setCursor(6, 1);
    lcd.print(score);
    // Зажигаем все светодиоды
    ledsSetAll(HIGH);
    // Включаем победную мелодию
    playMelodyGameWin();
    // Гасим все светодиоды
    ledsSetAll(LOW);
    // Ждём
    delay(1000);
    // Демонстрируем случайную последовательность светодиодов
    generateRandomArray();
    // Игра продолжается и счёт увеличивается,
    // пока функция readInputArray истинна.
    if (readInputArray()) {
      score++;
    } else {
      return false;
    }
  }
}
 
// Функция вывода начальной заставки на экран
void printLCDGameBegin() {
  // Очищаем дисплей
  lcd.clear();
  // Выводим текст начальной заставки
  lcd.setCursor(2, 0);
  lcd.print("<Simon Says>");
  lcd.setCursor(4, 1);
  lcd.print("DIY Game");
}
 
// Функция вывода конечной заставки на экран
void printLCDGameOver() {
  lcd.clear();
  lcd.setCursor(0, 1);
  lcd.print("Score:");
  lcd.print(score);
  // Если счёт превзошёл предыдущий рекорд, обновляем значение рекорда
  if (score > scoreBest) {
    scoreBest = score;
    // Сохраняем лучший результат игры в EEPROM
    EEPROM.put(EEPROM_ADDRESS, scoreBest);
  }
  // Выводим лучший результат игры за все попытки
  lcd.setCursor(0, 0);
  lcd.print("Best: ");
  lcd.print(scoreBest);
}
 
// Функция вывода мелодии «Начало игры
void playMelodyGameBegin() {
  // Выполняем перебор нот
  for (int i = 0; i < NOTE_COUNT_GAME_BEGIN; i++) {
    int led = random(0, LED_BUTTON_COUNTS);
    // Зажигаем случайно выбранный светодиод
    digitalWrite(LED_NUMBER[led], HIGH);
    // Играем ноту с определённой длительностью
    tone(BUZZER_PIN, NOTE_GAME_BEGIN[i], 1000 / NOTE_DURATION_GAME_BEGIN[i]);
    // Делаем задержку для выделения нот в мелодии:
    // Длительность текущей ноты плюс 30% 
    delay((1000 / NOTE_DURATION_GAME_BEGIN[i]) * 1.3);
    // Гасим случайно выбранный светодиод
    digitalWrite(LED_NUMBER[led], LOW);
  }
}
 
// Функция вывода мелодии «Выигрыш»
void playMelodyGameWin() {
  // Выполняем перебор нот
  for (int i = 0; i < NOTE_COUNT_GAME_WIN; i++) {
    // Играем ноту с определённой длительностью
    tone(BUZZER_PIN, NOTE_GAME_WIN[i], 1000 / NOTE_DURATION_GAME_WIN[i]);
    // Делаем задержку для выделения нот в мелодии:
    // Длительность текущей ноты плюс 30% 
    delay((1000 / NOTE_DURATION_GAME_WIN[i]) * 1.3);
  }
}
 
// Функция вывода мелодии «Проигрыш»
void playMelodyGameOver() {
  for (int i = 0; i <= 2; i++) {
    // Зажигаем все светодиоды
    ledsSetAll(HIGH);
    // Издаём звуковой сигнал
    tone(BUZZER_PIN, NOTE_G3, 300);
    delay(200);
    // Гасим все светодиоды
    ledsSetAll(LOW);
    // Издаём звуковой сигнал
    tone(BUZZER_PIN, NOTE_C3, 300);
    delay(200);
  }
}
 
// Функция вывода произвольной комбинации светодиодов
void generateRandomArray() {
  // Генерируем случайное число в диапазоне от 0 до 3
  randomArray[score] = random(0, LED_BUTTON_COUNTS);
  // Зажигаем по очереди всю последовательность светодиодов
  for (int scoreNow = 0; scoreNow <= score; scoreNow++) {
    ledBeep(randomArray[scoreNow]);
  }
}
 
// Функция считывания угадываемой комбинации кнопок
bool readInputArray() {
  int scoreNow = 0;
  // Пока количество нажатий на кнопки не превысило количество очков
  while (scoreNow <= score) {
    // Проверяем каждую кнопку
    for (int i = 0; i < LED_BUTTON_COUNTS; i++) {
      if (!digitalRead(BUTTON_NUMBER[i])) {
        // Подаём светодиодный звуковой сигнал нажатой кнопки
        ledBeep(i);
        // Присваиваем массиву кода кнопок, текущий номер кнопки
        inputArray[scoreNow] = i;
        delay(250);
        // Если текущая нажатая клавиша не совпадает со случайно сгенерированной
        if (inputArray[scoreNow] != randomArray[scoreNow]) {
          // Функция ложна
          return false;
        }
        scoreNow++;
      }
    }
  }
  return true;
}
 
// Функция включение или отключение всех светодиодов
void ledsSetAll(bool state) {
  for (int i = 0; i < LED_BUTTON_COUNTS; i++) {
    digitalWrite(LED_NUMBER[i], state);
  }
}
 
// Функция светодиодного звукового сигнала
void ledBeep(int led) {
  // Зажигаем выбранный светодиод
  digitalWrite(LED_NUMBER[led], HIGH);
  // Подаём звуковой сигнал зажжённого светодиода
  tone(BUZZER_PIN, LED_NOTE[led], 100);
  delay(400);
  // Гасим выбранный светодиод
  digitalWrite(LED_NUMBER[led], LOW);
  delay(100);
}

№63. Бегущая тень

SoftwareSPI.ino
// Назначаем контакты для связи с выходным сдвиговым регистром
constexpr int LATCH_PIN = 10;
constexpr int DATA_PIN = 11;
constexpr int CLOCK_PIN = 13;
 
// Переменная для хранения значений восьми светодиодов в битовом виде
byte leds = 0b00000000;
 
void setup() {
  // Настраиваем пины для связи с выходным сдвиговым регистром в режим выхода
  pinMode(LATCH_PIN, OUTPUT);
  pinMode(DATA_PIN, OUTPUT);  
  pinMode(CLOCK_PIN, OUTPUT);
}
 
void loop() {
  // Последовательно зажигаем светодиоды через сдвиговый регистр
  for (int i = 0; i < 8; i++) {
    bitWrite(leds, i, HIGH);
    sendData();
    delay(100);
  }
 
  // Последовательно гасим светодиоды через сдвиговый регистр
  for (int i = 0; i < 8; i++) {
    bitWrite(leds, i, LOW);
    sendData();
    delay(100);
  }
}
 
// Функция для переди байта в сдвиговый регистр 
// через программный SPI начиная с младшего бита
void sendData() {
   digitalWrite(LATCH_PIN, LOW);
   shiftOut(DATA_PIN, CLOCK_PIN, LSBFIRST, leds);
   digitalWrite(LATCH_PIN, HIGH);
}

№64. Бегущая тень по SPI

HardwareSPI.ino
// Библиотека для работы с SPI-устройствами
#include <SPI.h>
 
// Назначаем контакт для защёлки сдвигового регистра
constexpr int LATCH_PIN = 10;
 
// Переменная для хранения значений восьми светодиодов в битовом виде
byte leds = 0b00000000;
 
void setup() {
  // Инициализируем шину SPI
  SPI.begin();
  // Инициируем передачу данных по SPI младшим битом вперёд
  SPI.setBitOrder(LSBFIRST);
  // Назначаем контакт защёлки сдвигового регистра в режим выхода
  pinMode(LATCH_PIN, OUTPUT);
}
 
void loop() {
  // Последовательно зажигаем светодиоды через сдвиговый регистр
  for (int i = 0; i < 8; i++) {
    bitWrite(leds, i, HIGH);
    sendData();
    delay(100);
  }
 
  // Последовательно гасим светодиоды через сдвиговый регистр
  for (int i = 0; i < 8; i++) {
    bitWrite(leds, i, LOW);
    sendData();
    delay(100);
  }
}
 
// Функция для переди байта в сдвиговый регистр 
// через аппаратный SPI начиная с младшего бита
void sendData() {
   digitalWrite(LATCH_PIN, LOW);
   SPI.transfer(leds);
   digitalWrite(LATCH_PIN, HIGH);
}

Библиотека SPI

Полезные скетчи

Очистка EEPROM

EEPROMClear.ino
// Библиотека для работы с EEPROM-памятью
#include <EEPROM.h>
 
// Даём понятное имя встроенном светодиоду
constexpr int LED_PIN = 13;
 
void setup() {
  // Выставляем светодиод в редим выхода
  pinMode(LED_PIN, OUTPUT);
  // Шасим светодиод
  digitalWrite(LED_PIN, LOW);
  // прогогняем по очереди все ячейки EEPROM-памяти
  for (int i = 0; i < EEPROM.length(); i++) {
    // Заполняем каждую ячейку значением 0xFF
    EEPROM.write(i, 0xFF);
  }
  // После очистки памяти, зажигаем светодиод
  digitalWrite(LED_PIN, HIGH);
}
 
void loop() {
}

Чтение EEPROM

EEPROMRead.ino
// Библиотека для работы с EEPROM-памятью
#include <EEPROM.h>
 
void setup() {
  // Открываем Serial-порт
  Serial.begin(9600);
  // Прогогняем по очереди все ячейки EEPROM-памяти
  for (int i = 0; i < EEPROM.length(); i++) {
    // Считываем каждую ячейку памяти
    Serial.print(i);
    Serial.print("\t");
    Serial.println(EEPROM.read(i));
  }
}
 
void loop() {
}

Ресурсы

Софт

Библиотеки

Datasheet