10 класс
Конструирование программируемых устройств
Занятие 14. Цифровые актюаторы. Пассивный зуммер и функция tone()

14. Цифровые актюаторы. Управление устройствами через digitalWrite() и tone()

📅 13.01 ⏱ 2 × 40 минут
🕐 Занятие состоит из двух уроков по 40 минут с коротким перерывом

📋 План занятия

  1. 00:00-00:40: Урок 1: Теория цифровых актюаторов. Классификация устройств. Работа с пассивным зуммером и tone()
  2. 00:40-00:45: Короткий перерыв
  3. 00:45-01:25: Урок 2: Практика: 3 простые задачи на урок. Дополнительная задача с вибромодулем
  4. 01:25-01:30: Итоги. Объяснение домашнего задания

🎯 Основной материал

1. Теоретическая часть

1.1 Классификация устройств для Arduino

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

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

1.2 Принцип управления цифровыми актюаторами

Цифровой актюатор — устройство с двумя четкими состояниями: ВКЛЮЧЕНО и ВЫКЛЮЧЕНО.

Управление осуществляется через цифровой выход Arduino:

digitalWrite(номер_пина, HIGH); // Включить устройство
digitalWrite(номер_пина, LOW); // Выключить устройство

1.3 Типы зуммеров и методы управления

Важное отличие: В этом занятии мы работаем с ПАССИВНЫМ зуммером, который управляется через функцию tone()!
Характеристика Пассивный зуммер Активный зуммер
Управление Функция tone() digitalWrite()
Частота звука Любая (31 Гц - 65535 Гц) Фиксированная (обычно 2-4 кГц)
Создание мелодий Возможно Невозможно
Внешний вид Нет маркировки сверху Есть маркировка "+"

1.4 Функция tone() - управление звуком

Функция tone(pin, frequency, duration) генерирует прямоугольный сигнал на указанном пине.

// Базовый синтаксис:
tone(пин, частота); // звук без остановки
tone(пин, частота, длительность); // звук заданное время (мс)
noTone(пин); // остановить звук

1.5 Таблица частот для нот

Октава/Нота C (До) D (Ре) E (Ми) F (Фа) G (Соль) A (Ля) B (Си)
4 октава 262 Гц 294 Гц 330 Гц 349 Гц 392 Гц 440 Гц 494 Гц
5 октава 523 Гц 587 Гц 659 Гц 698 Гц 784 Гц 880 Гц 988 Гц

1.6 Необходимые компоненты

Компонент Количество Пин подключения
Arduino Uno/Nano 1 -
Пассивный зуммер 1 D9 (для tone)
Светодиод 1 D8
Резистор 220 Ом 1 Для светодиода
Вибромодуль (доп.) 1 D10
Соединительные провода 10-15 Папа-папа
Макетная плата 1 400 или 800 точек

2. Практическая часть

Структура практики: Выполните 3 простые задачи (каждая оценивается в 1 балл) ИЛИ одну сложную задачу (оценивается в 3 балла). Все задачи равны по баллам.

Задача 1: Базовый звук со светодиодом 1 балл

Цель: При каждом звуковом сигнале светодиод загорается.

void setup() {
  pinMode(8, OUTPUT); // Светодиод
  pinMode(9, OUTPUT); // Пассивный зуммер
}

void loop() {
  digitalWrite(8, HIGH);
  tone(9, 440, 500); // Ля 4 октавы (440 Гц)
  delay(500);
  digitalWrite(8, LOW);
  delay(1000);
}

Задача 2: Восходящая гамма 1 балл

Цель: Воспроизвести 5 нот подряд с разными частотами.

int notes[] = {262, 294, 330, 349, 392}; // До, Ре, Ми, Фа, Соль

void setup() {
  pinMode(9, OUTPUT);
}

void loop() {
  for(int i = 0; i < 5; i++) {
    tone(9, notes[i], 300);
    delay(350); // 300 мс звук + 50 мс пауза
  }
  delay(1000);
}

Задача 3: Сигнал SOS 1 балл

Цель: Реализовать сигнал бедствия (··· --- ···) на зуммере.

void setup() {
  pinMode(9, OUTPUT);
}

void loop() {
  // Три коротких (S)
  for(int i = 0; i < 3; i++) {
    tone(9, 660, 200);
    delay(250);
  }
  delay(100);
  
  // Три длинных (O)
  for(int i = 0; i < 3; i++) {
    tone(9, 660, 600);
    delay(650);
  }
  delay(100);
  
  // Три коротких (S)
  for(int i = 0; i < 3; i++) {
    tone(9, 660, 200);
    delay(250);
  }
  
  delay(3000);
}

Сложная задача: Светомузыка с вибромодулем 3 балла

Цель: Создать синхронизированную систему из трех устройств.

Схема подключения:
• Светодиод: D8
• Пассивный зуммер: D9
• Вибромодуль: D10
int melody[] = {262, 330, 392, 523, 392, 330, 262};
int noteDurations[] = {400, 300, 300, 500, 300, 300, 400};

void setup() {
  pinMode(8, OUTPUT); // Светодиод
  pinMode(9, OUTPUT); // Зуммер
  pinMode(10, OUTPUT); // Вибромодуль
}

void loop() {
  for(int i = 0; i < 7; i++) {
    // Зажигаем светодиод
    digitalWrite(8, HIGH);
    
    // Включаем вибрацию для низких нот
    if(melody[i] < 350) {
      digitalWrite(10, HIGH);
    }
    
    // Воспроизводим ноту
    tone(9, melody[i], noteDurations[i]);
    delay(noteDurations[i] + 50);
    
    // Выключаем все
    digitalWrite(8, LOW);
    digitalWrite(10, LOW);
    delay(100);
  }
  delay(2000);
}
Правило выбора: Можно выполнить 3 простые задачи (1+1+1 = 3 балла) ИЛИ одну сложную задачу (3 балла). Выбор за вами!

3. Готовые мелодии для пассивного зуммера

3.1 Мелодия "Happy Birthday"

int happyNotes[] = {
  262, 262, 294, 262, 349, 330,
  262, 262, 294, 262, 392, 349,
  262, 262, 523, 440, 349, 330, 294,
  466, 466, 440, 349, 392, 349
};

int happyDurations[] = {
  500, 500, 1000, 1000, 1000, 2000,
  500, 500, 1000, 1000, 1000, 2000,
  500, 500, 1000, 1000, 1000, 1000, 2000,
  500, 500, 1000, 1000, 1000, 2000
};

void setup() {
  pinMode(9, OUTPUT);
}

void loop() {
  for(int i = 0; i < 24; i++) {
    tone(9, happyNotes[i], happyDurations[i]);
    delay(happyDurations[i] * 1.1); // Добавляем небольшую паузу
  }
  delay(5000);
}

3.2 Сирена "Полиция"

void setup() {
  pinMode(8, OUTPUT); // Светодиод
  pinMode(9, OUTPUT); // Зуммер
}

void loop() {
  // Восходящий тон (мигание светодиода)
  for(int freq = 300; freq <= 1200; freq += 10) {
    digitalWrite(8, HIGH);
    tone(9, freq, 20);
    delay(20);
  }
  
  // Нисходящий тон
  for(int freq = 1200; freq >= 300; freq -= 10) {
    digitalWrite(8, LOW);
    tone(9, freq, 20);
    delay(20);
  }
}

3.3 Простые сигналы (для кнопок)

void beep(int freq, int duration) {
  tone(9, freq, duration);
  delay(duration + 50);
}

void setup() {
  pinMode(9, OUTPUT);
}

void loop() {
  beep(1000, 100); // Короткий высокий звук
  delay(500);
  beep(500, 200); // Более длинный низкий звук
  delay(1000);
  beep(1500, 50); // Очень короткий высокий
  delay(2000);
}

📝 Домашнее задание

ДЗ #3 📅 Срок сдачи: 20.01 🏆 Макс. балл: 5 баллов

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

Цель задания: Научиться создавать сложные звуковые последовательности, работать с функциями tone() и noTone(), интегрировать звук с визуальными и тактильными эффектами.

Базовая часть (5 баллов, любое из трех на выбор):

  1. Музыкальная шкала: Создайте программу, которая проигрывает мажорную гамму (8 нот) с разной длительностью: первые 4 ноты по 200 мс, последние 4 ноты по 400 мс.
  2. Мелодия с ритмом: Напишите мелодию из 10 нот, где каждая нота сопровождается миганием светодиода (вкл во время звучания ноты).
  3. Контроль пауз: Создайте программу с использованием noTone(), где между нотами есть четкие паузы (noTone на 100 мс).

🔍 Дополнительно

❓ Контрольные вопросы

Какая функция Arduino используется для управления цифровыми актюаторами?

Для управления цифровыми актюаторами используется функция digitalWrite(pin, state), где pin - номер цифрового вывода, state - HIGH (5В) или LOW (0В).

Почему для светодиода нужен резистор, а для модуля вибромотора — нет?

Светодиод имеет низкое сопротивление и может сгореть при подключении напрямую к 5В. Резистор ограничивает ток. Модуль вибромотора уже содержит транзистор и ограничивающие резисторы на плате модуля.

Что произойдет, если не вызвать pinMode() в функции setup()?

Пин будет находиться в неопределённом состоянии (вход с высоким импедансом). Устройство может работать некорректно или не работать вообще. Всегда настраивайте пины с помощью pinMode() перед использованием.

Чем отличается активный зуммер от пассивного?

Активный зуммер имеет встроенный генератор и издаёт звук фиксированной частоты при подаче питания. Пассивный зуммер требует внешнего генератора сигнала (функция tone()) и может воспроизводить разные частоты.

Для чего нужна функция noTone()?

Функция noTone() останавливает генерацию звука на указанном пине. Без неё звук может продолжаться до следующего вызова tone() или перезагрузки.

🔗 Полезные материалы

🎵 Еще больше мелодий для практики

// Мелодия "Jingle Bells" (фрагмент)
int jingleNotes[] = {
  330, 330, 330, 262, 392, 392, 392, 349
};

// Мелодия "Star Wars" (вступление)
int starWarsNotes[] = {
  392, 392, 392, 311, 466, 392, 311, 466, 392
};

Совет: Экспериментируйте с длительностями нот и паузами между ними. Попробуйте создать свою собственную мелодию, изменяя частоты в небольших пределах (±20 Гц).

Занятие 15. Сила тока и способы коммутации. MOSFET и реле

15. Сила тока и способы коммутации нагрузки. MOSFET и реле

📅 20.01 ⏱ 2 × 40 минут
Важно! На этом занятии мы научимся безопасно управлять мощными нагрузками, которые нельзя подключать напрямую к Arduino

📋 План занятия

  1. 00:00-00:20: Теория. Кнопки и их подключение. Ограничения Arduino.
  2. 00:20-00:40: Теория. MOSFET и реле - принципы работы. Демонстрация.
  3. 00:40-00:45: Короткий перерыв
  4. 00:45-01:25: Практика: выбор между 1 сложной задачей (3 балла) ИЛИ 3 простыми задачами (по 1 баллу)
  5. 01:25-01:30: Итоги. Объяснение домашнего задания

🎯 Основной материал

1.1 Кнопки и их подключение к Arduino

Что такое кнопка?

Кнопка — это простейший цифровой датчик, который имеет два состояния:

  • Нажата (Pressed) — контакты замкнуты
  • Отпущена (Released) — контакты разомкнуты

Основные типы кнопок:

Тип кнопки Описание Особенности
Тактовая кнопка Механическая кнопка с тактильным щелчком Нужен подтягивающий резистор
Сенсорная (тач) кнопка Реагирует на прикосновение без механического нажатия Уже содержит электронику, выдает цифровой сигнал
Кнопка с фиксацией Остается в нажатом состоянии до следующего нажатия Как выключатель

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

Arduino 5V → [Резистор 10kΩ] → Pin 2
Pin 2 → [Кнопка] → GND

// При нажатии: Pin 2 → GND (LOW)
// При отпускании: Pin 2 → 5V через резистор (HIGH)

Использование внутреннего подтягивающего резистора:

// Простейший пример с кнопкой и светодиодом
const int buttonPin = 2; // Кнопка подключена к пину 2
const int ledPin = 13; // Встроенный светодиод на пине 13

void setup() {
  pinMode(buttonPin, INPUT_PULLUP); // Включаем встроенный подтягивающий резистор
  pinMode(ledPin, OUTPUT);
}

void loop() {
  int buttonState = digitalRead(buttonPin);
  
  if (buttonState == LOW) { // Кнопка нажата (INPUT_PULLUP инвертирует!)
    digitalWrite(ledPin, HIGH); // Включаем светодиод
  }
  else {
    digitalWrite(ledPin, LOW); // Выключаем светодиод
  }
}
Важно: INPUT_PULLUP инвертирует логику!
• Кнопка нажата → digitalRead() возвращает LOW (0)
• Кнопка отпущена → digitalRead() возвращает HIGH (1)
Сенсорные кнопки обычно уже имеют подтяжку и выдают прямой сигнал.

Проблема дребезга кнопок и её решение:

// Антидребезг с использованием millis()
const int buttonPin = 2;
const int ledPin = 13;
int lastButtonState = HIGH; // Изначально кнопка отпущена
int buttonState; // Текущее состояние кнопки
unsigned long lastDebounceTime = 0; // Время последнего изменения
const long debounceDelay = 50; // Время антидребезга (мс)

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);
}

void loop() {
  int reading = digitalRead(buttonPin);
  unsigned long currentMillis = millis();

  // Если состояние изменилось (из-за дребезга или реального нажатия)
  if (reading != lastButtonState) {
    lastDebounceTime = currentMillis; // Сбрасываем таймер
  }

  // Если прошло достаточно времени после последнего изменения
  if ((currentMillis - lastDebounceTime) > debounceDelay) {
    // Если состояние кнопки изменилось
    if (reading != buttonState) {
      buttonState = reading;

      // Только теперь реагируем на изменение
      if (buttonState == LOW) {
        digitalWrite(ledPin, !digitalRead(ledPin)); // Инвертируем светодиод
      }
    }
  }

  lastButtonState = reading; // Сохраняем текущее состояние для следующего цикла
}

1.2 Почему нельзя подключать нагрузку напрямую к Arduino?

Ограничения Arduino Uno:
• Максимальный ток через один пин: 20 мА
• Суммарный ток со всех выводов: 200 мА
• Типичная нагрузка (мотор, помпа, светодиодная лента): 200 мА - 2 А

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

1.3 Два типа ключей: Полупроводниковый vs Электромеханический

Характеристика MOSFET (IRF520) Электромеханическое реле (5V)
Принцип работы Электронный ключ. Током между стоком (D) и истоком (S) управляет напряжение на затворе (G). Механический ключ. Ток через катушку создаёт магнитное поле, которое замыкает контакты.
Скорость переключения Очень высокая (микросекунды). Подходит для ШИМ. Низкая (10-100 мс). Не для ШИМ.
Гальваническая развязка НЕТ. Управляющая и силовая цепи имеют общую землю (GND). ЕСТЬ полная. Катушка и контакты электрически не связаны.
Тип нагрузки В основном постоянный ток (DC). Постоянный (DC) и переменный (AC 220V) ток.
Ток управления ~0 мА (только заряд ёмкости затвора). Значительный (50-100 мА). Требует отдельного питания для нескольких реле.
Главное правило подключения ОБЯЗАТЕЛЬНА общая земля (GND) между Arduino, модулем MOSFET и блоком питания нагрузки. Общая земля НЕ нужна, но питание катушки должно быть совместимо с 5V от Arduino.

1.4 Ключевые выводы по выбору:

  • MOSFET: Для DC-нагрузок, ШИМ-управления, частого переключения.
  • Реле: Для AC-нагрузок (сеть 220В), когда нужна полная гальваническая развязка.

1.5 Демонстрация: «Почему реле не может работать быстро?»

// Демо-код для быстрого переключения реле
void setup() {
  pinMode(10, OUTPUT);
}

void loop() {
  // Пытаемся переключать реле на частоте ~10 Гц (100 мс период)
  digitalWrite(10, HIGH);
  delay(50); // Включено 50 мс
  digitalWrite(10, LOW);
  delay(50); // Выключено 50 мс
}
Что произойдёт и почему:
• Вы услышите дребезг, нечёткие щелчки или полное отсутствие реакции.
• Реле не успевает физически сработать — катушке нужно время на создание магнитного поля, а якорю — время на возврат пружиной.
Вывод: Реле — механическое устройство. Его скорость ограничена физикой движения деталей.

1.6 Необходимые компоненты

Компонент Количество Пин подключения
Arduino Uno/Nano 1 -
Модуль MOSFET (IRF520) 1 D9
Модуль реле (одноканальный, 5V) 1 D10
Сенсорная тач-кнопка 1 D2
Пассивный зуммер 1 D3 (для ДЗ)
Лабораторный БП (15В, 2А) 1 Отдельное питание нагрузки
Нагрузка: 12V светодиодная лента/5V мотор/помпа 1 Через MOSFET или реле
Мультиметр 1 Для измерений
Соединительные провода 15-20 Папа-папа, папа-мама

2. Практическая часть

Структура практики: Выполните ОДНУ сложную задачу (оценивается в 3 балла) ИЛИ ТРИ простые задачи (каждая оценивается в 1 балл). Все задачи равны по баллам.

СЛОЖНАЯ ЗАДАЧА: «Сенсорный контроллер с двумя типами ключей» 3 балла

Цель: Создать систему, где тач-кнопка управляет нагрузкой сначала через реле, а при долгом удержании — через MOSFET с ШИМ.

Схема подключения:
• Реле (пин 9) коммутирует общий плюс 5V моторчика
• MOSFET (пин 10) коммутирует общий минус того же моторчика
• Сенсорная кнопка: пин 2
ВАЖНО: Общая земля (GND) Arduino, всех модулей и БП
// === НАСТРОЙКА ПИНОВ ===
const int TOUCH_PIN = 2; // Пин тач-кнопки
const int RELAY_PIN = 9; // Пин управления реле
const int MOSFET_PIN = 10; // Пин управления MOSFET (ШИМ)

// === НАСТРОЙКИ ТАЙМЕРОВ ===
const unsigned long HOLD_THRESHOLD = 1000; // 1 сек - время удержания для переключения на MOSFET
const unsigned long RELAY_ON_TIME = 3000; // 3 сек - время работы реле
const unsigned long PWM_RAMP_TIME = 2500; // 2.5 сек - время плавного пуска/останова

// === ПЕРЕМЕННЫЕ ДЛЯ ТАЙМЕРОВ ===
unsigned long touchStartTime = 0; // Время начала касания
unsigned long currentMillis = 0; // Текущее время
unsigned long lastTouchTime = 0; // Время последнего касания
unsigned long lastDebounceTime = 0; // Для антидребезга
const unsigned long DEBOUNCE_DELAY = 50; // 50 мс - время антидребезга

// === ПЕРЕМЕННЫЕ СОСТОЯНИЯ ===
bool touchState = false;
bool lastTouchState = false;
bool relayActive = false;
bool mosfetActive = false;

void setup() {
  pinMode(TOUCH_PIN, INPUT);
  pinMode(RELAY_PIN, OUTPUT);
  pinMode(MOSFET_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW);
  digitalWrite(MOSFET_PIN, LOW);
  Serial.begin(9600);
}

void loop() {
  currentMillis = millis();
  bool currentTouch = readDebouncedTouch();

  // === ВАШ КОД ЗДЕСЬ ===
  // 1. Обработка начала касания (сохранить touchStartTime)
  // 2. Обработка отпускания (выключить всё, сбросить состояния)
  // 3. Проверка удержания > HOLD_THRESHOLD (переключить на MOSFET)
  // 4. Проверка короткого касания (включить реле на RELAY_ON_TIME)
  // 5. Автовыключение реле через RELAY_ON_TIME
  // 6. Для MOSFET: плавный пуск/останов с analogWrite()

  lastTouchState = currentTouch;
}

bool readDebouncedTouch() {
  bool reading = digitalRead(TOUCH_PIN);
  if (reading != lastTouchState) {
    lastDebounceTime = currentMillis;
  }
  if ((currentMillis - lastDebounceTime) > DEBOUNCE_DELAY) {
    return reading;
  }
  return lastTouchState;
}

void activateRelay() {
  // Включить реле, выключить MOSFET
  digitalWrite(RELAY_PIN, HIGH);
  digitalWrite(MOSFET_PIN, LOW);
  relayActive = true;
  mosfetActive = false;
}

void activateMosfet() {
  // Включить MOSFET, выключить реле
  digitalWrite(RELAY_PIN, LOW);
  relayActive = false;
  mosfetActive = true;
  // Плавный пуск будет в main loop
}
Критерии выполнения:
1. Чёткое разделение работы реле (простое вкл/выкл) и MOSFET (плавное управление)
2. Отсутствие конфликта: реле и MOSFET не включены одновременно
3. Использование готовых таймеров для обработки времени

ПРОСТЫЕ ЗАДАЧИ (выполнить все три, каждая по 1 баллу):

Задача 1: «Тач-включение через реле» 1 балл

Схема: Тач-кнопка → Arduino → Реле → 12V светодиодная лента (от ЛБП).

Задача: При каждом касании кнопки состояние ленты инвертируется (вкл/выкл). Добавьте защиту от случайного срабатывания: изменение состояния только если между касаниями прошло более 0.5 секунд.

// === НАСТРОЙКА ПИНОВ ===
const int TOUCH_PIN = 2; // Пин тач-кнопки
const int RELAY_PIN = 9; // Пин управления реле

// === НАСТРОЙКИ ТАЙМЕРОВ ===
const unsigned long DEBOUNCE_DELAY = 500; // 500 мс - защита от случайного срабатывания

// === ПЕРЕМЕННЫЕ ДЛЯ ТАЙМЕРОВ ===
unsigned long lastTouchTime = 0; // Время последнего касания
unsigned long currentMillis = 0; // Текущее время
unsigned long lastDebounceTime = 0; // Для антидребезга
const unsigned long TOUCH_DEBOUNCE = 50; // 50 мс - время антидребезга кнопки

// === ПЕРЕМЕННЫЕ СОСТОЯНИЯ ===
bool relayState = false; // Текущее состояние реле (false - выкл)
bool lastTouchReading = false; // Предыдущее состояние кнопки
bool touchState = false; // Дебаунснутое состояние кнопки

void setup() {
  pinMode(TOUCH_PIN, INPUT);
  pinMode(RELAY_PIN, OUTPUT);
  digitalWrite(RELAY_PIN, LOW);
}

void loop() {
  currentMillis = millis();
  touchState = readDebouncedTouch();

  // === ВАШ КОД ЗДЕСЬ ===
  // 1. Проверить, было ли новое нажатие (touchState == HIGH)
  // 2. Проверить, прошло ли DEBOUNCE_DELAY с lastTouchTime
  // 3. Если оба условия true - инвертировать relayState
  // 4. Обновить lastTouchTime
  // 5. Установить реле в relayState (digitalWrite)

  lastTouchReading = touchState;
}

bool readDebouncedTouch() {
  bool reading = digitalRead(TOUCH_PIN);
  if (reading != lastTouchReading) {
    lastDebounceTime = currentMillis;
  }
  if ((currentMillis - lastDebounceTime) > TOUCH_DEBOUNCE) {
    return reading;
  }
  return lastTouchReading;
}

Задача 2: «Плавное управление через MOSFET» 1 балл

Схема: Тач-кнопка → Arduino → MOSFET → 5V мини-помпа.

Задача: Реализуйте два режима работы помпы, переключаемые двойным касанием (интервал между касаниями < 300 мс):
1. Режим 1: Короткое касание → помпа включается напрямую на 2 секунды.
2. Режим 2: Короткое касание → помпа плавно включается (ШИМ 0→255 за 2 сек) и так же плавно выключается.

// === НАСТРОЙКА ПИНОВ ===
const int TOUCH_PIN = 2; // Пин тач-кнопки
const int MOSFET_PIN = 10; // Пин управления MOSFET (ШИМ)

// === НАСТРОЙКИ ТАЙМЕРОВ ===
const unsigned long DOUBLE_CLICK_TIME = 300; // 300 мс - максимальное время между кликами
const unsigned long DIRECT_ON_TIME = 2000; // 2 сек - время работы в режиме 1
const unsigned long RAMP_TIME = 2000; // 2 сек - время плавного изменения в режиме 2

// === ПЕРЕМЕННЫЕ ДЛЯ ТАЙМЕРОВ ===
unsigned long lastClickTime = 0; // Время последнего клика
unsigned long currentMillis = 0; // Текущее время
unsigned long modeStartTime = 0; // Время начала работы режима
unsigned long lastDebounceTime = 0; // Для антидребезга
const unsigned long TOUCH_DEBOUNCE = 50; // 50 мс - время антидребезга

// === ПЕРЕМЕННЫЕ СОСТОЯНИЯ ===
int clickCount = 0; // Счетчик кликов
bool mode = false; // false - режим 1, true - режим 2
bool pumpActive = false; // Насос активен?
bool lastTouchReading = false; // Предыдущее состояние кнопки
bool touchState = false; // Дебаунснутое состояние кнопки

void setup() {
  pinMode(TOUCH_PIN, INPUT);
  pinMode(MOSFET_PIN, OUTPUT);
  digitalWrite(MOSFET_PIN, LOW);
}

void loop() {
  currentMillis = millis();
  touchState = readDebouncedTouch();

  // === ВАШ КОД ЗДЕСЬ ===
  // 1. Обработка нажатия кнопки (детектирование кликов)
  // 2. Проверка двойного клика (время между кликами < DOUBLE_CLICK_TIME)
  // 3. Если двойной клик - переключить mode
  // 4. Если одиночный клик:
  // - mode == false: включить насос напрямую на DIRECT_ON_TIME
  // - mode == true: плавный пуск/останов с analogWrite() за RAMP_TIME
  // 5. Контроль времени работы для режима 1

  // Таймаут для сброса счетчика кликов
  if ((currentMillis - lastClickTime) > DOUBLE_CLICK_TIME && clickCount > 0) {
    clickCount = 0;
  }

  lastTouchReading = touchState;
}

bool readDebouncedTouch() {
  bool reading = digitalRead(TOUCH_PIN);
  if (reading != lastTouchReading) {
    lastDebounceTime = currentMillis;
  }
  if ((currentMillis - lastDebounceTime) > TOUCH_DEBOUNCE) {
    return reading;
  }
  return lastTouchReading;
}

Задача 3: «Сравнительный тест: слушаем реле» 1 балл

Схема: Arduino → Реле (без нагрузки!).

Задача: Напишите программу, которая заставляет реле щёлкать с разной частотой:
1. Сначала 10 раз с интервалом 1000 мс (1 раз в секунду)
2. Затем 10 раз с интервалом 200 мс
3. Затем 10 раз с интервалом 50 мс

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

void testSequence(int delayTime, int cycles, String testName) {
  Serial.print("Testing: ");
  Serial.print(testName);
  Serial.print(", delay: ");
  Serial.print(delayTime);
  Serial.println(" ms");

  for (int i = 0; i < cycles; i++) {
    digitalWrite(9, HIGH);
    delay(delayTime / 2);
    digitalWrite(9, LOW);
    delay(delayTime / 2);
  }
  delay(1000);
}

void loop() {
  testSequence(1000, 10, "Slow (1 Hz)");
  testSequence(200, 10, "Medium (5 Hz)");
  testSequence(50, 10, "Fast (20 Hz)");

  Serial.println("=== Cycle complete ===");
  delay(5000);
}
Что наблюдать и записать в тетрадь:
1. На какой частоте реле перестаёт успевать физически щёлкать?
2. Что происходит со светодиодом-индикатором на модуле реле?
3. Вывод: Почему реле нельзя использовать для ШИМ-управления мотором (например, на 1 кГц)?
Правило выбора: Можно выполнить 1 сложную задачу (3 балла) ИЛИ 3 простые задачи (1+1+1 = 3 балла). Выбор за вами!

3. Схемы подключения для практики

3.1 Подключение MOSFET-модуля

Для управления нагрузкой 12V:
1. Arduino GND → GND модуля MOSFET
2. Arduino Pin 9 → SIG/IN модуля MOSFET
3. ЛБП 12V (+) → (+) нагрузки (лента/мотор)
4. ЛБП 12V (-) → (-) модуля MOSFET (или GND)
5. (-) нагрузки → OUT модуля MOSFET

Ключевое правило: ОБЩАЯ ЗЕМЛЯ (GND) между Arduino, модулем MOSFET и ЛБП!

3.2 Подключение реле-модуля

Для управления нагрузкой 12V:
1. Arduino 5V → VCC реле (питание катушки)
2. Arduino GND → GND реле
3. Arduino Pin 10 → IN реле (управление)
4. ЛБП 12V (+) → COM реле (Common)
5. ЛБП 12V (-) → (-) нагрузки
6. NO реле (Normally Open) → (+) нагрузки

Особенность: Катушка реле потребляет ~70 мА! При управлении несколькими реле нужен отдельный источник питания.

📝 Домашнее задание

ДЗ #4 📅 Срок сдачи: 27.01 🏆 Макс. балл: 3 балла

Добавление звуковой индикации к системе управления

Цель задания: Научиться интегрировать звуковую индикацию (зуммер) в систему управления нагрузкой через MOSFET или реле.

Общее для всех:

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

Для пути А (усовершенствование сложной задачи):

  1. Добавьте звуковую индикацию для каждого состояния:
    • Короткий звук (440 Гц, 100 мс): при касании кнопки
    • Два коротких звука (660 Гц, 100 мс): при включении реле
    • Восходящий тон (200→1000 Гц за 500 мс): при переходе в режим MOSFET
    • Нисходящий тон (1000→200 Гц за 500 мс): при выключении MOSFET
  2. Добавьте режим безопасности с звуковой сигнализацией. Если нагрузка работала суммарно более 15 секунд:
    • Издать три предупреждающих сигнала (800 Гц, 200 мс с паузой 100 мс)
    • Через 5 секунд выключить нагрузку
    • Воспроизвести сигнал "отключение" (нисходящий глиссандо)
  3. Реализуйте звуковую обратную связь при ошибках (например, если пытаются включить MOSFET при активном реле).

Для пути Б (усовершенствование простых задач):

Выберите ОДНУ из трёх задач для усовершенствования:

  1. К заданию 1 (Тач-включение через реле):
    • Добавьте разные звуки для включения и выключения
    • При включении: воспроизвести мажорный аккорд (до-ми-соль, 262-330-392 Гц по 150 мс)
    • При выключении: воспроизвести минорный аккорд (до-ми♭-соль, 262-311-392 Гц по 150 мс)
    • Добавьте звуковой таймер: если нагрузка включена более 10 секунд, каждые 2 секунды издавать короткий сигнал (1000 Гц, 50 мс)
  2. К заданию 2 (Плавное управление через MOSFET):
    • Добавьте звуковое сопровождение плавного пуска/останова
    • При плавном пуске: тон плавно повышается от 200 Гц до 800 Гц вместе с ШИМ
    • При плавном останове: тон плавно понижается от 800 Гц до 200 Гц
    • Добавьте звуковую индикацию режимов:
      • При переключении в режим 1: два коротких сигнала (500 Гц)
      • При переключении в режим 2: три коротких сигнала (700 Гц)
  3. К заданию 3 (Сравнительный тест реле):
    • Добавьте звуковую идентификацию тестов
    • Перед каждым тестом воспроизводить идентифицирующий звук:
      • Медленный тест: низкий тон (200 Гц, 500 мс)
      • Средний тест: средний тон (500 Гц, 300 мс)
      • Быстрый тест: высокий тон (1000 Гц, 100 мс)
    • Добавьте звуковую обратную связь при пропущенных щелчках реле (когда реле не успевает срабатывать)
    • В конце цикла тестов воспроизвести мелодию из 5 нот (до-ре-ми-фа-соль)

🗣 Критерии оценки ДЗ:

  • 1 балл: Работоспособность и корректная интеграция зуммера
  • 1 балл: Разнообразие и осмысленность звуковых сигналов
  • 1 балл: Синхронизация звука с работой нагрузки и обработка таймеров

Дополнительно: Все звуки должны быть реализованы через функцию tone() с правильными длительностями и паузами между сигналами.

Совет по реализации:
• Используйте функцию playTone(pin, frequency, duration) для упрощения кода
• Для сложных звуковых последовательностей создайте массивы частот и длительностей
• Помните: tone() работает асинхронно - можно воспроизводить звук параллельно с другими действиями

🔍 Дополнительно

❓ Контрольные вопросы

Почему пины Arduino могут сгореть при прямом подключении мотора?

Пины Arduino рассчитаны на максимальный ток 20 мА. Моторы потребляют от 200 мА до 2 А, что в 10-100 раз больше. При превышении тока происходит перегрев и разрушение полупроводниковой структуры вывода микроконтроллера.

Что такое "общая земля" и почему она важна для MOSFET?

Общая земля (GND) — это соединение всех "минусовых" цепей схемы. Для MOSFET она важна, потому что управляющее напряжение на затворе измеряется относительно истока. Без общей земли сигнал от Arduino не будет корректно восприниматься MOSFET.

Почему реле нельзя использовать для ШИМ-управления?

Реле — механическое устройство. Его контакты физически перемещаются, что занимает время (10-50 мс). Частота ШИМ для моторов обычно 1-20 кГц (период 0.05-1 мс). Реле просто не успевает следовать за такими быстрыми изменениями.

В каких случаях обязательно использовать реле вместо MOSFET?

1. При коммутации цепей переменного тока 220В.
2. Когда необходима полная гальваническая развязка (защита Arduino от высокого напряжения).
3. Для коммутации очень высоких напряжений (сотни вольт).

Какой ток потребляет затвор MOSFET при управлении?

Практически нулевой постоянный ток. MOSFET управляется напряжением, а не током. Кратковременно (наносекунды) потребляется небольшой ток для заряда/разряда ёмкости затвора, но в среднем это доли миллиампера.

💡 Готовые функции для работы со звуком

Базовая функция воспроизведения тона

// Простая функция для воспроизведения тона с паузой
void playTone(int pin, int frequency, int duration) {
  tone(pin, frequency, duration);
  delay(duration + 50); // Пауза после тона
}

// Функция для воспроизведения последовательности тонов
void playSequence(int pin, int frequencies[], int durations[], int length) {
  for (int i = 0; i < length; i++) {
    playTone(pin, frequencies[i], durations[i]);
  }
}

Готовые звуковые сигналы для ДЗ

// Массивы для различных звуковых сигналов
int successMelody[] = {523, 659, 784}; // До-Ми-Соль
int successDurations[] = {200, 200, 400};

int warningMelody[] = {784, 784, 784}; // Три высоких тона
int warningDurations[] = {100, 100, 100};

// Функция для восходящего/нисходящего глиссандо
void glissando(int pin, int startFreq, int endFreq, int duration) {
  int step = (endFreq > startFreq) ? 10 : -10;
  int steps = abs(endFreq - startFreq) / 10;
  int delayTime = duration / steps;

  for (int freq = startFreq; abs(freq - endFreq) > 10; freq += step) {
    tone(pin, freq, delayTime);
    delay(delayTime);
  }
  noTone(pin);
}

Совет для ДЗ: Создайте отдельные функции для каждого типа звукового сигнала (например, playSuccess(), playWarning(), playError()). Это сделает код чище и проще для отладки.

Занятие 16. Двигатели и направление — H-мост

16. Двигатели и направление — H-мост

📅 27.01 ⏱ 2 × 40 минут
🚗 Ключевая тема для проекта! На этом занятии мы научимся управлять направлением вращения моторов, что критически важно для робота Line Follower

📋 План занятия

  1. 00:00-00:20: Теория. Почему MOSFET не может менять направление. Принцип H-моста.
  2. 00:20-00:40: Знакомство с драйвером L298N/L9110S. Таблица истинности управления.
  3. 00:40-00:45: Короткий перерыв
  4. 00:45-01:25: Практика: управление TT-мотором через драйвер. Плавный разгон и торможение.
  5. 01:25-01:30: Итоги. Объяснение домашнего задания

🎯 Основной материал

1.1 Почему MOSFET не может менять направление вращения?

Ограничение одиночного MOSFET:
MOSFET может только включать/выключать ток, но не может менять его направление.
Мотору для реверса нужно поменять полярность напряжения — для этого нужна специальная схема.

Аналогия с мостом:

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

  • MOSFET — это просто паром, который может только включать/выключать движение
  • H-мост — это разводной мост с двумя путями: на северный берег и на южный

Решение: H-мост (Н-мост)

Структура H-моста (4 ключа):

[+] ---- [Q1] ---- A+ ---- [МОТОР] ---- B- ---- [Q4] ---- [-]
             |                                    |
[+] ---- [Q2] ---- A- ---- [МОТОР] ---- B+ ---- [Q3] ---- [-]

Q1-Q4 — транзисторные ключи (обычно MOSFET)

1.2 Интерактивная схема H-моста

Нажмите на состояния ниже, чтобы увидеть, как работает H-мост:

▶ ВПЕРЕД

Q1 и Q4 открыты

Ток: [+] → Q1 → Мотор(+) → Мотор(-) → Q4 → [-]

● Q1 ВКЛ
○ Q2 ВЫКЛ
○ Q3 ВЫКЛ
● Q4 ВКЛ

◀ НАЗАД

Q2 и Q3 открыты

Ток: [+] → Q2 → Мотор(-) → Мотор(+) → Q3 → [-]

○ Q1 ВЫКЛ
● Q2 ВКЛ
● Q3 ВКЛ
○ Q4 ВЫКЛ

⚠ ТОРМОЖЕНИЕ

Q1 и Q2 открыты (или Q3 и Q4)

Короткое замыкание обмоток мотора

● Q1 ВКЛ
● Q2 ВКЛ
○ Q3 ВЫКЛ
○ Q4 ВЫКЛ

∞ СВОБОДНЫЙ ХОД

Все ключи выключены

Мотор вращается по инерции

○ Q1 ВЫКЛ
○ Q2 ВЫКЛ
○ Q3 ВЫКЛ
○ Q4 ВЫКЛ
Выберите состояние выше, чтобы увидеть описание
На схеме показано, как работают 4 транзисторных ключа для управления направлением вращения мотора.

1.3 Драйверы двигателей L298N и L9110S

Вместо того чтобы собирать H-мост из отдельных транзисторов, мы используем готовые драйверы:

Драйвер Макс. ток Напряжение Особенности
L298N 2А на канал (4А пиковый) 5-35В 2 канала, радиатор, стабилизатор 5В
L9110S 0.8А (1.5А пиковый) 2.5-12В Маленький, дешевый, 2 канала
TB6612FNG 1.2А (3.2А пиковый) 2.5-13.5В Эффективный, мало тепла

Распиновка L298N (самый распространенный):

Питание

+12V (VM) Питание моторов (5-35В)
+5V (VCC) Питание логики (можно от Arduino)
GND Земля (должна быть общей!)

Канал A (Мотор 1)

ENA ШИМ для скорости (пин ~ на Arduino)
IN1 Направление 1 (цифровой пин)
IN2 Направление 2 (цифровой пин)
OUT1, OUT2 Выход на мотор

Канал B (Мотор 2)

ENB ШИМ для скорости
IN3, IN4 Направление
OUT3, OUT4 Выход на мотор
Важно: Для небольших моторов (TT-моторы, до 500 мА) можно использовать L298N без радиатора. Для более мощных моторов обязательно нужен радиатор или активное охлаждение!

1.4 Таблица истинности управления L298N

Управление направлением осуществляется комбинацией сигналов IN1 и IN2:

IN1 IN2 ENA Режим Действие
1 (HIGH) 0 (LOW) PWM (0-255) ВПЕРЕД Вращение по часовой стрелке
0 (LOW) 1 (HIGH) PWM (0-255) НАЗАД Вращение против часовой стрелки
1 (HIGH) 1 (HIGH) X ТОРМОЖЕНИЕ Быстрая остановка (короткое замыкание)
0 (LOW) 0 (LOW) X СВОБОДНЫЙ ХОД Вращение по инерции
X X 0 (LOW) ОТКЛЮЧЕНО Мотор выключен (независимо от IN1/IN2)
Опасная комбинация: Никогда не подавайте одновременно IN1=1 и IN2=1 при ENA=1 (PWM > 0)! Это создаст короткое замыкание внутри драйвера и может его повредить.

Универсальная функция управления мотором:

// Универсальная функция управления мотором через L298N
void setMotor(int enaPin, int in1Pin, int in2Pin, int speed, bool direction) {
  // speed: 0-255 (ШИМ значение)
  // direction: true - вперед, false - назад

  if (speed == 0) {
    // Остановка (свободный ход)
    digitalWrite(in1Pin, LOW);
    digitalWrite(in2Pin, LOW);
    analogWrite(enaPin, 0);
  } else if (direction) {
    // Вращение вперед
    digitalWrite(in1Pin, HIGH);
    digitalWrite(in2Pin, LOW);
    analogWrite(enaPin, speed);
  } else {
    // Вращение назад
    digitalWrite(in1Pin, LOW);
    digitalWrite(in2Pin, HIGH);
    analogWrite(enaPin, speed);
  }
}

// Пример использования:
void setup() {
  pinMode(9, OUTPUT); // ENA
  pinMode(8, OUTPUT); // IN1
  pinMode(7, OUTPUT); // IN2
}

void loop() {
  // Вперед на полной скорости
  setMotor(9, 8, 7, 255, true);
  delay(2000);
  
  // Назад на половине скорости
  setMotor(9, 8, 7, 128, false);
  delay(2000);
}

2. Практическая часть: Управление TT-мотором

Цель практики: Собрать схему управления TT-мотором через драйвер L298N/L9110S и написать программу плавного управления скоростью и направлением.

2.1 Необходимые компоненты

Компонент Количество Примечание
Arduino Uno/Nano 1 -
Драйвер L298N или L9110S 1 Для одного мотора достаточно одного канала
TT-мотор с редуктором 1 3-6В, с колесом
Батарейный отсек 4×AA 1 6В для моторов (ОТДЕЛЬНОЕ ПИТАНИЕ!)
Перемычки папа-папа 10-15 Для подключения
Макетная плата 1 Для удобства подключения

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

Подключение для одного мотора:

1. Arduino 5V → VCC на L298N (питание логики)
2. Arduino GND → GND на L298N (ОБЯЗАТЕЛЬНО!)
3. Батарейка 6V+ → +12V (VM) на L298N
4. Батарейка 6V- → GND на L298N
5. Arduino D9 (~) → ENA на L298N (ШИМ для скорости)
6. Arduino D8 → IN1 на L298N
7. Arduino D7 → IN2 на L298N
8. OUT1, OUT2 на L298N → Контакты мотора
ВНИМАНИЕ!
1. НЕ подключайте питание моторов (VM) к Arduino 5V!
2. ОБЯЗАТЕЛЬНА общая земля между Arduino, драйвером и батарейкой
3. TT-моторы могут потреблять до 500 мА при старте — Arduino не обеспечит такой ток!

2.3 Базовые задачи управления

// === КОНФИГУРАЦИЯ ПИНОВ ===
const int ENA_PIN = 9; // ШИМ-пин для скорости (должен быть ~)
const int IN1_PIN = 8; // Направление 1
const int IN2_PIN = 7; // Направление 2

void setup() {
  pinMode(ENA_PIN, OUTPUT);
  pinMode(IN1_PIN, OUTPUT);
  pinMode(IN2_PIN, OUTPUT);
  Serial.begin(9600);
  Serial.println("Motor Control Ready");
}

void loop() {
  Serial.println("Forward at full speed");
  motorForward(255);
  delay(2000);
  
  Serial.println("Backward at half speed");
  motorBackward(128);
  delay(2000);
  
  Serial.println("Stop (coast)");
  motorStop();
  delay(1000);
  
  Serial.println("Brake (fast stop)");
  motorBrake();
  delay(1000);
}

void motorForward(int speed) {
  digitalWrite(IN1_PIN, HIGH);
  digitalWrite(IN2_PIN, LOW);
  analogWrite(ENA_PIN, speed);
}

void motorBackward(int speed) {
  digitalWrite(IN1_PIN, LOW);
  digitalWrite(IN2_PIN, HIGH);
  analogWrite(ENA_PIN, speed);
}

void motorStop() {
  // Свободный ход (коастинг)
  digitalWrite(IN1_PIN, LOW);
  digitalWrite(IN2_PIN, LOW);
  analogWrite(ENA_PIN, 0);
}

void motorBrake() {
  // Торможение (короткое замыкание обмоток)
  digitalWrite(IN1_PIN, HIGH);
  digitalWrite(IN2_PIN, HIGH);
  analogWrite(ENA_PIN, 0);
}

2.4 Плавный разгон и торможение

void smoothAcceleration(bool forward, int accelerationTime) {
  // Устанавливаем направление
  if (forward) {
    digitalWrite(IN1_PIN, HIGH);
    digitalWrite(IN2_PIN, LOW);
  } else {
    digitalWrite(IN1_PIN, LOW);
    digitalWrite(IN2_PIN, HIGH);
  }
  
  // Плавный разгон от 0 до 255
  Serial.print("Accelerating ");
  Serial.println(forward ? "forward" : "backward");
  
  for (int speed = 0; speed <= 255; speed++) {
    analogWrite(ENA_PIN, speed);
    delay(accelerationTime / 255);
  }
  
  // Держим максимальную скорость 1 секунду
  delay(1000);
  
  // Плавное торможение от 255 до 0
  Serial.println("Decelerating");
  for (int speed = 255; speed >= 0; speed--) {
    analogWrite(ENA_PIN, speed);
    delay(accelerationTime / 255);
  }
  
  // Полная остановка
  motorStop();
  delay(500);
}

2.5 Задания для практики

Выполните все три задания:
1. Соберите схему с драйвером и одним мотором
2. Загрузите код плавного разгона/торможения
3. Модифицируйте программу по указаниям ниже

Задание 1: "Змейка скорости" 1 балл

Задача: Создайте программу, где мотор плавно разгоняется от 0 до 255, затем плавно тормозит до 0, и сразу (без остановки) начинает движение в обратном направлении с таким же профилем скорости.

void loop() {
  // === ВАШ КОД ЗДЕСЬ ===
  // 1. Плавный разгон вперед от 0 до 255 (3 секунды)
  // 2. Плавное торможение до 0 (3 секунды)
  // 3. БЕЗ ПАУЗЫ: плавный разгон назад от 0 до 255
  // 4. Плавное торможение назад до 0
  // 5. Пауза 1 секунда и повтор цикла
}

Задание 2: "Ступенчатая скорость" 1 балл

Задача: Реализуйте программу, где мотор работает 2 секунды на скорости 64, затем 2 секунды на скорости 128, затем 2 секунды на скорости 192, затем 2 секунды на скорости 255 — всё в одном направлении.

void loop() {
  // === ВАШ КОД ЗДЕСЬ ===
  // Используйте массив скоростей: int speeds[] = {64, 128, 192, 255};
  // Цикл for по массиву, для каждой скорости:
  // 1. Установить направление (вперед)
  // 2. Установить скорость из массива
  // 3. Ждать 2000 мс
  // 4. Перейти к следующей скорости
}

Задание 3: "Эксперимент с торможением" 1 балл

Задача: Сравните два вида остановки: свободный ход (коастинг) и торможение (брейкинг).

void loop() {
  // Тест 1: Свободный ход
  Serial.println("Test 1: Coasting stop");
  motorForward(255);
  delay(1000);
  motorStop(); // Свободный ход
  // Замерьте время полной остановки мотора
  delay(3000);
  
  // Тест 2: Торможение
  Serial.println("Test 2: Braking stop");
  motorForward(255);
  delay(1000);
  motorBrake(); // Торможение
  // Замерьте время полной остановки мотора
  delay(3000);
  
  // === ЗАПИШИТЕ В ТЕТРАДЬ ===
  // 1. Какая остановка быстрее?
  // 2. Слышен ли характерный звук при торможении?
  // 3. Какой способ лучше для точного позиционирования?
}

3. Важные технические замечания

3.1 Диоды обратной связи (Flyback Diodes)

Почему моторы "стреляют" в драйвер:
При резком отключении мотора в его обмотках возникает ЭДС самоиндукции (высокое напряжение обратной полярности).
Хорошая новость: В драйверах L298N и L9110S защитные диоды уже встроены на плате!

3.2 Нагрев драйвера

L298N сильно греется, особенно при больших токах или при использовании торможения:

  • Без радиатора: До 0.5А непрерывно
  • С радиатором: До 2А непрерывно
  • Решение: Установите радиатор, используйте активное охлаждение (вентилятор), избегайте длительного торможения

3.3 Переход на более эффективные драйверы

Для проектов роботов рекомендуются более современные драйверы:

Драйвер Преимущества Для каких проектов
TB6612FNG Низкий нагрев, высокая эффективность Line Follower, небольшие роботы
DRV8833 Маленький размер, низкое напряжение Мини-роботы, дроны
VNH5019 Мощный (до 30А), защита от перегрева Тяжелые роботы, автомобили

📝 Домашнее задание

ДЗ #5 📅 Срок сдачи: 03.02 🏆 Макс. балл: 5 баллов

Управление скоростью мотора через потенциометр

Цель задания: Научиться считывать аналоговые значения с потенциометра и использовать их для плавного управления скоростью и направлением вращения мотора.

Часть 1: Сборка схемы (2 балла)

  1. Соберите схему со следующими компонентами:
    • Драйвер L298N/L9110S с подключенным TT-мотором
    • Потенциометр 10 кОм, подключенный к аналоговому пину A0
    • Отдельный источник питания 6В для моторов (батарейный отсек)
  2. Схема подключения потенциометра:
    Потенциометр (10 кОм):
    • Левый вывод → GND Arduino
    • Средний вывод → A0 Arduino (сигнал)
    • Правый вывод → 5V Arduino

Часть 2: Программирование (3 балла)

Задача: Напишите программу, которая:

  1. Считывает значение с потенциометра (0-1023)
  2. Преобразует это значение в скорость мотора (-255 до +255) с помощью функции map()
  3. Управляет мотором:
    • Когда потенциометр в среднем положении → мотор остановлен
    • При повороте вправо → мотор вращается вперед со скоростью пропорциональной углу поворота
    • При повороте влево → мотор вращается назад со скоростью пропорциональной углу поворота
  4. Выводит в Serial Monitor:
    • Сырое значение с потенциометра (0-1023)
    • Преобразованная скорость (-255..+255)
    • Направление вращения (Вперед/Назад/Стоп)

Ключевая функция: map()

// Функция map() преобразует число из одного диапазона в другой
// Синтаксис: map(значение, отМинимум, отМаксимум, кМинимум, кМаксимум)

// Пример 1: Преобразование 0-1023 в 0-255
int potValue = analogRead(A0); // 0-1023
int speed = map(potValue, 0, 1023, 0, 255);
// speed теперь будет от 0 до 255

// Пример 2: Преобразование 0-1023 в -255 до +255
int potValue2 = analogRead(A0);
int speed2 = map(potValue2, 0, 1023, -255, 255);
// speed2 будет от -255 до +255
// Отрицательные значения - вращение назад
// Положительные значения - вращение вперед
Как работает map():
1. Берёт значение из исходного диапазона (0-1023)
2. Вычисляет его положение в этом диапазоне (например, 512 - это середина)
3. Переносит это положение в новый диапазон (-255..+255)
4. Возвращает соответствующее значение в новом диапазоне

Формула:
результат = (значение - отМинимум) × (кМаксимум - кМинимум) / (отМаксимум - отМинимум) + кМинимум

Каркас программы для ДЗ:

// === КОНФИГУРАЦИЯ ПИНОВ ===
const int POT_PIN = A0; // Потенциометр
const int ENA_PIN = 9; // ШИМ для скорости
const int IN1_PIN = 8; // Направление 1
const int IN2_PIN = 7; // Направление 2

void setup() {
  pinMode(ENA_PIN, OUTPUT);
  pinMode(IN1_PIN, OUTPUT);
  pinMode(IN2_PIN, OUTPUT);
  Serial.begin(9600);
  Serial.println("Potentiometer Motor Control");
}

void loop() {
  // 1. Считать значение с потенциометра (0-1023)
  int potValue = analogRead(POT_PIN);
  
  // 2. Преобразовать в скорость от -255 до +255 с помощью map()
  int speed = map(potValue, 0, 1023, -255, 255);
  
  // 3. Управление мотором в зависимости от скорости
  if (speed > 0) {
    // Вращение вперед
    digitalWrite(IN1_PIN, HIGH);
    digitalWrite(IN2_PIN, LOW);
    analogWrite(ENA_PIN, speed);
    Serial.print("Forward, speed: ");
  } else if (speed < 0) {
    // Вращение назад (скорость отрицательная)
    digitalWrite(IN1_PIN, LOW);
    digitalWrite(IN2_PIN, HIGH);
    analogWrite(ENA_PIN, -speed); // Берем модуль скорости
    Serial.print("Backward, speed: ");
  } else {
    // Остановка
    digitalWrite(IN1_PIN, LOW);
    digitalWrite(IN2_PIN, LOW);
    analogWrite(ENA_PIN, 0);
    Serial.print("Stopped, speed: ");
  }
  
  // 4. Вывод информации в Serial Monitor
  Serial.print(speed);
  Serial.print(" | Potentiometer: ");
  Serial.println(potValue);
  
  // 5. Небольшая задержка для стабильности
  delay(100);
}

Усложнение задания (дополнительные баллы):

Выполните ОДИН из вариантов для получения дополнительного балла:
Вариант А: "Мертвая зона" +1 балл

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

// Пример создания мертвой зоны
int potValue = analogRead(POT_PIN);
int speed = 0;

if (potValue < 450) {
  // Левый диапазон: назад
  speed = map(potValue, 0, 450, -255, 0);
} else if (potValue > 573) {
  // Правый диапазон: вперед
  speed = map(potValue, 573, 1023, 0, 255);
}
// Между 450 и 573 - мертвая зона, speed = 0
Вариант Б: "Плавный старт" +1 балл

Добавьте плавный старт мотора при резком повороте потенциометра. Используйте плавное изменение скорости вместо мгновенного.

// Пример плавного изменения скорости
int targetSpeed = map(potValue, 0, 1023, -255, 255);
static int currentSpeed = 0;

// Плавное изменение текущей скорости к целевой
if (currentSpeed < targetSpeed) {
  currentSpeed += 5; // Шаг разгона
  currentSpeed = min(currentSpeed, targetSpeed);
} else if (currentSpeed > targetSpeed) {
  currentSpeed -= 5; // Шаг торможения
  currentSpeed = max(currentSpeed, targetSpeed);
}
Вариант В: "Двойной потенциометр" +1 балл

Используйте два потенциометра: один для управления скоростью (0-255), второй для выбора направления (вперед/назад) через кнопку или тумблер.

// Пример с двумя потенциометрами
int speedPot = analogRead(A0); // Скорость (0-1023)
int dirPot = analogRead(A1); // Направление

// Преобразование скорости
int speed = map(speedPot, 0, 1023, 0, 255);

// Определение направления
bool forward = (dirPot > 512); // >50% - вперед

🗣 Критерии оценки ДЗ:

  • 2 балла: Корректная сборка схемы с потенциометром и драйвером мотора
  • 2 балла: Рабочая программа с использованием функции map() для преобразования значений
  • 1 балл: Четкий вывод информации в Serial Monitor и плавное управление мотором
  • Дополнительно +1 балл: Выполнение одного из вариантов усложнения

Что должно быть в отчете:

  1. Фото собранной схемы
  2. Полный код программы с комментариями
  3. Скриншот Serial Monitor с показаниями потенциометра и скорости
  4. Описание работы: как значения 0-1023 преобразуются в -255..+255
Советы по выполнению:
• Используйте функцию constrain() для ограничения скорости в диапазоне -255..255
• Добавьте небольшую задержку delay(10-50) для стабильности считывания
• Для отрицательных скоростей используйте функцию abs() для получения модуля
• Проверьте, что при среднем положении потенциометра мотор действительно остановлен
Распространенные ошибки:
1. Не подключена общая земля (GND) между Arduino, драйвером и потенциометром
2. Питание моторов подключено к 5V Arduino (должно быть отдельное питание!)
3. Забыли использовать analogWrite() для ШИМ-пина скорости
4. Неправильные диапазоны в функции map()

🔍 Дополнительно

❓ Контрольные вопросы

Почему для изменения направления мотора нужен H-мост, а не просто MOSFET?

Одиночный MOSFET может только включать/выключать ток в одном направлении. H-мост состоит из 4 ключей, которые могут менять полярность напряжения на моторе, создавая ток в обоих направлениях.

Что произойдет, если одновременно открыть верхний и нижний ключи на одной стороне H-моста?

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

В чем разница между свободным ходом (коастинг) и торможением (брейкинг)?

При свободном ходе все ключи выключены, мотор вращается по инерции. При торможении обмотки мотора замыкаются через ключи, создавая электрическое сопротивление вращению — мотор останавливается быстрее.

Зачем нужен отдельный источник питания для моторов?

Моторы потребляют большой ток (сотни мА - единицы А). Arduino не может обеспечить такой ток через свои пины. Также пульсации тока от моторов могут создавать помехи в работе микроконтроллера.

Почему L298N сильно греется, а TB6612FNG — нет?

L298N использует биполярную технологию с большим падением напряжения (≈2В). TB6612FNG использует MOSFET с очень низким сопротивлением в открытом состоянии (≈0.2Ω), поэтому выделяет меньше тепла.

💡 Готовые функции для проекта Line Follower

Библиотека управления двумя моторами

// === КОНФИГУРАЦИЯ ДЛЯ ДВУХ МОТОРОВ ===
const int ENA = 9; // Левый мотор: скорость
const int IN1 = 8; // Левый мотор: направление 1
const int IN2 = 7; // Левый мотор: направление 2
const int ENB = 10; // Правый мотор: скорость
const int IN3 = 5; // Правый мотор: направление 1
const int IN4 = 6; // Правый мотор: направление 2

void setupMotors() {
  pinMode(ENA, OUTPUT);
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(ENB, OUTPUT);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);
}

void setLeftMotor(int speed, bool forward) {
  if (forward) {
    digitalWrite(IN1, HIGH);
    digitalWrite(IN2, LOW);
  } else {
    digitalWrite(IN1, LOW);
    digitalWrite(IN2, HIGH);
  }
  analogWrite(ENA, abs(speed));
}

void setRightMotor(int speed, bool forward) {
  if (forward) {
    digitalWrite(IN3, HIGH);
    digitalWrite(IN4, LOW);
  } else {
    digitalWrite(IN3, LOW);
    digitalWrite(IN4, HIGH);
  }
  analogWrite(ENB, abs(speed));
}

void drive(int leftSpeed, int rightSpeed) {
  // Универсальная функция движения для робота
  setLeftMotor(leftSpeed, leftSpeed >= 0);
  setRightMotor(rightSpeed, rightSpeed >= 0);
}

void robotStop() {
  drive(0, 0);
}

Примеры маневров для робота

// Движение вперед
void moveForward(int speed) {
  drive(speed, speed);
}

// Поворот на месте
void rotate(int speed, bool clockwise) {
  if (clockwise) {
    drive(speed, -speed); // Правый назад, левый вперед
  } else {
    drive(-speed, speed); // Левый назад, правый вперед
  }
}

// Плавный поворот (один мотор медленнее)
void smoothTurn(int baseSpeed, int turnAmount) {
  // turnAmount: -100..+100, где отрицательное - влево
  int leftSpeed = constrain(baseSpeed - turnAmount, 0, 255);
  int rightSpeed = constrain(baseSpeed + turnAmount, 0, 255);
  drive(leftSpeed, rightSpeed);
}

Совет для подготовки к проекту: Начните создавать свою библиотеку функций управления моторами уже сейчас. Это значительно упростит разработку робота Line Follower в будущем.

Занятие 17. Умные моторы — сервоприводы

17. Умные моторы — сервоприводы

📅 03.02 ⏱ 2 × 40 минут
🛠 Точное позиционирование! На этом занятии мы научимся управлять сервоприводами для точного контроля угла поворота

📋 План занятия

  1. 00:00-00:20: Теория. Отличие сервопривода от коллекторного мотора. Принцип работы ШИМ 50 Гц.
  2. 00:20-00:40: Библиотека Servo.h. Подключение сервопривода SG90. Управление углом.
  3. 00:40-00:45: Короткий перерыв
  4. 00:45-01:25: Практика: подключение сервопривода, плавное движение, непрерывное вращение.
  5. 01:25-01:30: Итоги. Объяснение домашнего задания

🎯 Основной материал

1.1 Что такое сервопривод и чем он отличается от обычного мотора?

Ключевое отличие:
Коллекторный мотор — вращается непрерывно, управляется скоростью
Сервопривод — вращается на определенный угол (0-180°), управляется позицией

Строение сервопривода:

  • Мотор — обычный коллекторный мотор
  • Редуктор — система шестерен для увеличения крутящего момента
  • Потенциометр — датчик угла поворота
  • Контроллер — схема управления, сравнивает заданный угол с текущим

Принцип работы "замкнутого контура":

  1. Arduino отправляет команду "повернись на 90°"
  2. Контроллер сервы считывает текущий угол с потенциометра
  3. Сравнивает: "текущий угол" vs "заданный угол"
  4. Если не совпадает — включает мотор в нужную сторону
  5. Когда углы совпали — выключает мотор
Попробуйте! После установки сервы на угол (например, 90°) попытайтесь провернуть её рукой. Вы почувствуете сопротивление — это контроллер пытается удержать заданную позицию!

1.2 Сигнал управления сервоприводом (ШИМ 50 Гц)

Сервопривод управляется специальным ШИМ-сигналом с частотой 50 Гц (период 20 мс):

Период сигнала: 20 мс (50 Гц)

0° (0.5 мс) 90° (1.5 мс) 180° (2.0 мс)

• 0.5 мс импульс → 0° (минимальный угол)
• 1.5 мс импульс → 90° (среднее положение)
• 2.0 мс импульс → 180° (максимальный угол)
• Между 0.5-2.0 мс → пропорциональный угол 0-180°

Интерактивная анимация:

SERVO

Угол: 90°

1.3 Типы сервоприводов: SG90 vs Серводвигателя (непрерывное вращение)

Параметр SG90 (угловой) Серводвигатель (непрерывный)
Управление Угол поворота (0-180°) Скорость и направление
Сигнал Импульс 0.5-2.0 мс → угол Импульс 1.0-2.0 мс → скорость
Диапазон 0-180° (фиксированный) Непрерывное вращение 360°
Применение Роборуки, камеры, заслонки, поворотные механизмы Колеса роботов, конвейеры, перемещение объектов
Команда servo.write(angle) servo.write(90) - стоп, 0 - назад, 180 - вперед
Как преобразовать SG90 в непрерывное вращение (сломав при этом сервпривод!):
1. Снять ограничители с шестерен
2. Отключить или закоротить потенциометр
3. Теперь 1.0 мс → полный назад, 1.5 мс → стоп, 2.0 мс → полный вперед

1.4 Библиотека Servo.h для Arduino

Подключение библиотеки и базовое использование:

// Подключение библиотеки Servo
#include <Servo.h>

// Создание объекта сервопривода
Servo myServo;

void setup() {
  // Подключение сервы к пину 9
  myServo.attach(9);
}

void loop() {
  // Поворот на 0 градусов
  myServo.write(0);
  delay(1000);
  
  // Поворот на 90 градусов
  myServo.write(90);
  delay(1000);
  
  // Поворот на 180 градусов
  myServo.write(180);
  delay(1000);
}

Разница между write() и writeMicroseconds():

// write() - управление углом (библиотека сама преобразует в импульс)
myServo.write(90); // 90 градусов

// writeMicroseconds() - прямое управление длительностью импульса
myServo.writeMicroseconds(1500); // 1.5 мс = 90°

// Примеры соответствия:
// write(0) ↔ writeMicroseconds(500)
// write(90) ↔ writeMicroseconds(1500)
// write(180) ↔ writeMicroseconds(2500)
// Некоторые сервы имеют расширенный диапазон: 500-2500 мкс

2. Практическая часть: Управление сервоприводом

Необходимые компоненты:
• Arduino Uno
• Сервопривод SG90 (3 провода)
• Потенциометр 10 кОм (для ДЗ)
• Макетная плата и провода
Внешний источник питания 5V (рекомендуется!)

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

Подключение сервопривода SG90:

Сервопривод SG90 (3 провода):
Коричневый → GND Arduino
Красный → 5V Arduino (или внешний 5V)
Оранжевый/Желтый → Pin 9 Arduino (сигнал)
Внимание! Потребление тока:
Сервоприводы потребляют большой ток при движении (до 500-1000 мА).
• Один сервопривод можно питать от 5V пина Arduino
• Два и более сервопривода — ОБЯЗАТЕЛЬНО внешнее питание!
• При заедании/нагрузке ток может возрасти до 1.5А

2.2 Пример 1: Плавное движение по точкам

#include <Servo.h>

Servo myServo;
int servoPin = 9;

void setup() {
  myServo.attach(servoPin);
  Serial.begin(9600);
}

void loop() {
  // Медленное движение от 0 до 180 градусов
  Serial.println("Moving from 0 to 180 degrees");
  for (int angle = 0; angle <= 180; angle++) {
    myServo.write(angle);
    delay(20); // 20 мс на шаг = 3.6 секунды на полный оборот
  }
  
  // Медленное движение обратно
  Serial.println("Moving from 180 to 0 degrees");
  for (int angle = 180; angle >= 0; angle--) {
    myServo.write(angle);
    delay(20);
  }
  
  // Движение по заданным точкам
  int positions[] = {0, 45, 90, 135, 180};
  for (int i = 0; i < 5; i++) {
    Serial.print("Moving to: ");
    Serial.println(positions[i]);
    myServo.write(positions[i]);
    delay(1000);
  }
}

2.3 Пример 2: "Аналоговые часы" - плавное перемещение

void smoothMove(Servo &servo, int startAngle, int endAngle, int duration) {
  // Плавное перемещение из startAngle в endAngle за duration миллисекунд
  int steps = abs(endAngle - startAngle);
  int delayTime = duration / steps;
  int stepDir = (endAngle > startAngle) ? 1 : -1;

  for (int i = 0; i < steps; i++) {
    int currentAngle = startAngle + (i * stepDir);
    servo.write(currentAngle);
    delay(delayTime);
  }
  servo.write(endAngle); // Финальная позиция
}

void loop() {
  // Плавное движение как стрелки часов
  smoothMove(myServo, 0, 180, 3000); // 0→180 за 3 секунды
  delay(1000);
  smoothMove(myServo, 180, 0, 3000); // 180→0 за 3 секунды
  delay(1000);
}

2.4 Пример 3: Непрерывное вращение (если есть MG90S)

void loop() {
  // Для сервы непрерывного вращения:
  // 0 = полный назад, 90 = стоп, 180 = полный вперед
  
  Serial.println("Full speed forward");
  myServo.write(180);
  delay(2000);
  
  Serial.println("Stop");
  myServo.write(90);
  delay(1000);
  
  Serial.println("Full speed backward");
  myServo.write(0);
  delay(2000);
  
  Serial.println("Variable speed forward");
  for (int speed = 90; speed <= 180; speed += 10) {
    myServo.write(speed);
    delay(500);
  }
}

3. Важные технические замечания

3.1 Питание сервоприводов

Проблема "моргания" Arduino при движении сервы:
Когда сервопривод начинает движение, он потребляет большой ток (до 1А). Это вызывает просадку напряжения на Arduino, что может привести к:
1. Перезагрузке Arduino
2. Морганию светодиодов
3. Сбросу программы

Решение: Используйте отдельный блок питания 5V для сервоприводов!

3.2 Подключение нескольких сервоприводов

Правильное подключение 3 сервоприводов:

Внешний БП 5V 2A:
• +5V → Красные провода всех сервоприводов
• GND → Коричневые провода всех сервоприводов + GND Arduino

Arduino:
• Pin 9 → Сигнал сервы 1 (оранжевый)
• Pin 10 → Сигнал сервы 2
• Pin 11 → Сигнал сервы 3
• 5V → НЕ ПОДКЛЮЧАТЬ к сервам!

3.3 Ограничения библиотеки Servo.h

  • Библиотека использует таймеры Arduino
  • На Uno можно подключить до 12 сервоприводов
  • Использование серв отключает ШИМ на пинах 9 и 10
  • Для точного управления используйте writeMicroseconds()

📝 Домашнее задание

ДЗ #6 📅 Срок сдачи: 10.02 🏆 Макс. балл: 5 баллов

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

Цель задания: Научиться преобразовывать аналоговые значения с потенциометра в угол поворота сервопривода с использованием функции map().

Часть 1: Сборка схемы (2 балла)

  1. Соберите схему со следующими компонентами:
    • Сервопривод SG90 (3 провода)
    • Потенциометр 10 кОм
    • Arduino Uno
  2. Схема подключения:
    Сервопривод:
    • Коричневый → GND
    • Красный → 5V (рекомендуется внешний источник)
    • Оранжевый → Pin 9

    Потенциометр:
    • Левый вывод → GND
    • Средний вывод → A0
    • Правый вывод → 5V

Часть 2: Программирование (3 балла)

Задача: Напишите программу, которая:

  1. Считывает значение с потенциометра (0-1023)
  2. Преобразует его в угол сервопривода (0-180°) с помощью функции map()
  3. Устанавливает сервопривод в соответствующий угол
  4. Выводит в Serial Monitor:
    • Значение с потенциометра (0-1023)
    • Преобразованный угол (0-180°)
    • Длительность импульса в микросекундах (500-2500 мкс)

Пример реализации:

#include <Servo.h>

Servo myServo;
const int servoPin = 9;
const int potPin = A0;

void setup() {
  myServo.attach(servoPin);
  Serial.begin(9600);
  Serial.println("Servo Potentiometer Control");
}

void loop() {
  // 1. Считываем значение с потенциометра
  int potValue = analogRead(potPin);
  
  // 2. Преобразуем 0-1023 в 0-180 с помощью map()
  int angle = map(potValue, 0, 1023, 0, 180);
  
  // 3. Преобразуем угол в микросекунды (опционально)
  int pulseWidth = map(angle, 0, 180, 500, 2500);
  
  // 4. Устанавливаем сервопривод
  myServo.write(angle);
  // Или: myServo.writeMicroseconds(pulseWidth);
  
  // 5. Выводим информацию
  Serial.print("Pot: ");
  Serial.print(potValue);
  Serial.print(" | Angle: ");
  Serial.print(angle);
  Serial.print("° | Pulse: ");
  Serial.print(pulseWidth);
  Serial.println(" μs");
  
  // 6. Небольшая задержка
  delay(50);
}

Дополнительное задание (дополнительный балл):

Добавьте "плавное движение" сервопривода:
Вместо резкого изменения угла, реализуйте плавное перемещение от текущего положения к новому.
void loop() {
  static int currentAngle = 90; // Текущий угол
  int potValue = analogRead(potPin);
  int targetAngle = map(potValue, 0, 1023, 0, 180);
  
  // Плавное движение к targetAngle
  if (currentAngle < targetAngle) {
    currentAngle++;
    myServo.write(currentAngle);
  } else if (currentAngle > targetAngle) {
    currentAngle--;
    myServo.write(currentAngle);
  }
  
  delay(15); // Скорость движения
}

🗣 Критерии оценки ДЗ:

  • 2 балла: Корректная сборка схемы с сервоприводом и потенциометром
  • 2 балла: Рабочая программа с использованием map() для преобразования значений
  • 1 балл: Четкий вывод информации в Serial Monitor и плавное управление сервой

🔍 Дополнительно

❓ Контрольные вопросы

Чем сервопривод отличается от обычного мотора?

Сервопривод имеет обратную связь (потенциометр) и контроллер, что позволяет точно позиционировать вал на заданный угол (0-180°). Обычный мотор вращается непрерывно без контроля положения.

Какой сигнал используется для управления сервоприводом?

ШИМ-сигнал с частотой 50 Гц (период 20 мс), где длительность импульса определяет угол: 0.5 мс = 0°, 1.5 мс = 90°, 2.0 мс = 180°.

Почему сервоприводы потребляют большой ток?

При движении и удержании позиции сервопривод потребляет ток до 500-1000 мА. При нагрузке или заедании ток может возрастать до 1.5А, что слишком много для вывода 5V Arduino.

В чем разница между write() и writeMicroseconds()?

write(angle) принимает угол 0-180°, библиотека сама преобразует его в импульс. writeMicroseconds(pulse) принимает непосредственно длительность импульса в микросекундах (500-2500 мкс), что позволяет более точно управлять сервой.

Как превратить сервопривод в мотор непрерывного вращения?

Нужно физически модифицировать серву: снять ограничители с шестерен и отключить потенциометр. Тогда 1.0 мс будет означать "полный назад", 1.5 мс — "стоп", 2.0 мс — "полный вперед".

💡 Идеи для проектов с сервоприводами

1. Робот-манипулятор (2 сервопривода)

// Управление роборукой с двумя сервами
Servo baseServo; // Основание
Servo armServo; // Рука

void setup() {
  baseServo.attach(9);
  armServo.attach(10);
}

void pickObject(int baseAngle, int armAngle) {
  // 1. Повернуть основание
  baseServo.write(baseAngle);
  delay(1000);
  
  // 2. Опустить руку
  armServo.write(armAngle);
  delay(1000);
  
  // 3. Поднять руку с объектом
  armServo.write(90);
  delay(1000);
}

2. Система автоматического полива

// Сервопривод открывает/закрывает клапан полива
Servo waterValve;

void waterPlants(int seconds) {
  Serial.println("Watering plants...");
  
  // Открыть клапан
  waterValve.write(0);
  delay(seconds * 1000);
  
  // Закрыть клапан
  waterValve.write(180);
  Serial.println("Watering complete");
}

3. Система слежения за светом (фоторезистор + серва)

// Слежение за самым ярким источником света
Servo trackerServo;
const int ldrPin = A0;

void trackLight() {
  int maxLight = 0;
  int bestAngle = 0;
  
  // Просканировать все углы 0-180
  for (int angle = 0; angle <= 180; angle += 10) {
    trackerServo.write(angle);
    delay(200);
    
    int lightLevel = analogRead(ldrPin);
    if (lightLevel > maxLight) {
      maxLight = lightLevel;
      bestAngle = angle;
    }
  }
  
  // Повернуться к самому яркому свету
  trackerServo.write(bestAngle);
}

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

Занятие 18. Глаза и уши Arduino — аналоговые датчики

18. Глаза и уши Arduino — аналоговые датчики

📅 10.02 ⏱ 2 × 40 минут
💡 Сбор информации! На этом занятии мы научимся считывать данные с аналоговых датчиков окружающей среды

📋 План занятия

  1. 00:00-00:20: Теория. Аналоговые входы Arduino. Функция analogRead(). АЦП 10-бит.
  2. 00:20-00:40: Подключение фоторезистора и датчика влажности почвы. Калибровка.
  3. 00:40-00:45: Короткий перерыв
  4. 00:45-01:25: Практика: считывание значений, калибровка, использование map().
  5. 01:25-01:30: Итоги. Объяснение домашнего задания

🎯 Основной материал

1.1 Аналоговые входы Arduino и функция analogRead()

Ключевые понятия:
Аналоговый сигнал — непрерывно изменяющееся напряжение (0-5V)
Цифровой сигнал — только два состояния: HIGH (5V) или LOW (0V)
АЦП (Аналого-Цифровой Преобразователь) — преобразует аналоговое напряжение в цифровое число

Характеристики АЦП Arduino Uno:

  • Разрешение: 10 бит (2¹⁰ = 1024 значения)
  • Диапазон: 0-1023 (0V = 0, 5V = 1023)
  • Входы: 6 аналоговых пинов (A0-A5)
  • Время преобразования: ~0.1 мс (10 000 измерений в секунду)
  • Точность: ±2 значения (погрешность)

Функция analogRead():

// Считывание аналогового значения с пина A0
int sensorValue = analogRead(A0);

// Значение будет от 0 до 1023
// 0 = 0V, 512 = 2.5V, 1023 = 5V (приблизительно)

// Вывод значения в Serial Monitor
Serial.begin(9600);
Serial.print("Sensor value: ");
Serial.println(sensorValue);
Важно знать!
Функция analogRead() занимает время (~0.1 мс). Если вызывать её слишком часто в цикле без задержек, это может замедлить выполнение остальной программы.

1.2 Принцип работы аналоговых датчиков

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

1. Фоторезистор (датчик освещенности)

  • Принцип: Сопротивление изменяется в зависимости от освещенности
  • Светло: Низкое сопротивление (~1 кОм)
  • Темно: Высокое сопротивление (~100 кОм)
  • Схема подключения: Делитель напряжения с резистором 10 кОм

2. Датчик влажности почвы (аналоговый)

  • Принцип: Сопротивление между контактами зависит от влажности почвы
  • Сухая почва: Высокое сопротивление (сухой воздух не проводит ток)
  • Влажная почва: Низкое сопротивление (вода проводит ток)
  • Выход: Аналоговое напряжение 0-5V

Схема делителя напряжения для фоторезистора:

Фоторезистор + резистор 10 кОм:
• 5V → Фоторезистор → A0 → Резистор 10кОм → GND

Формула напряжения на A0:
Vout = 5V × (R2 / (R1 + R2))
где R1 = фоторезистор, R2 = 10 кОм

1.3 Интерактивный симулятор "Аналоговый датчик"

Симулятор датчика влажности почвы

Перемещайте ползунок, чтобы изменить "влажность":

512
Состояние: Средняя влажность

Интерпретация значений:

0-300: Сухая почва

301-700: Средняя влажность

701-1023: Влажная почва

1.4 Калибровка датчиков и таблица экспериментальных значений

Калибровка — процесс определения пороговых значений для разных состояний датчика:

Условие Значение analogRead() Выбранный порог Действие
Полностью сухая почва 850-1023 > 700 Включить полив
Средняя влажность 400-849 300-700 Ничего не делать
Очень влажная почва 0-399 < 300 Выключить полив
Очень светло (день) 0-200 < 200 Выключить свет
Темно (ночь) 800-1023 > 800 Включить свет

Программа для калибровки датчика:

void setup() {
  Serial.begin(9600);
  Serial.println("=== Sensor Calibration ===");
  Serial.println("Place sensor in different conditions");
  Serial.println("Press any key to record value");
}

void loop() {
  int sensorValue = analogRead(A0);
  
  // Автоматическое определение мин/макс
  static int minValue = 1023;
  static int maxValue = 0;
  
  if (sensorValue < minValue) minValue = sensorValue;
  if (sensorValue > maxValue) maxValue = sensorValue;
  
  Serial.print("Current: ");
  Serial.print(sensorValue);
  Serial.print(" | Min: ");
  Serial.print(minValue);
  Serial.print(" | Max: ");
  Serial.println(maxValue);
  
  delay(500);
}

1.5 Библиотека для датчика DHT11 (температура и влажность воздуха)

DHT11 — цифровой датчик температуры и влажности, но требует специальной библиотеки:

// Подключение библиотеки DHT
#include <DHT.h>

// Определение типа датчика и пина
#define DHTPIN 2 // Пин подключения
#define DHTTYPE DHT11 // Тип датчика

// Создание объекта датчика
DHT dht(DHTPIN, DHTTYPE);

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

void loop() {
  // Задержка между измерениями (минимум 2 секунды для DHT11)
  delay(2000);
  
  // Чтение температуры и влажности
  float humidity = dht.readHumidity();
  float temperature = dht.readTemperature();
  
  // Проверка на ошибки чтения
  if (isnan(humidity) || isnan(temperature)) {
    Serial.println("Error reading DHT sensor!");
    return;
  }
  
  // Вывод значений
  Serial.print("Humidity: ");
  Serial.print(humidity);
  Serial.print(" % | Temperature: ");
  Serial.print(temperature);
  Serial.println(" °C");
}
Особенности DHT11:
• Цифровой интерфейс (один провод для данных)
• Диапазон влажности: 20-80% ±5%
• Диапазон температуры: 0-50°C ±2°C
• Частота опроса: не чаще 1 раза в 2 секунды
• Требует библиотеки DHT.h

2. Практическая часть: Работа с аналоговыми датчиками

Необходимые компоненты:
• Arduino Uno
• Фоторезистор + резистор 10 кОм
• Аналоговый датчик влажности почвы
• Светодиод + резистор 220 Ом
• Макетная плата и провода

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

Подключение фоторезистора (датчика освещенности):

Схема делителя напряжения:
• 5V → Фоторезистор → A0
• A0 → Резистор 10 кОм → GND

Светодиод для индикации:
• Pin 13 → Резистор 220 Ом → Светодиод(+)
• Светодиод(-) → GND

2.2 Пример 1: Считывание значений с фоторезистора

const int ldrPin = A0; // Фоторезистор
const int ledPin = 13; // Светодиод

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

void loop() {
  // Считываем значение с фоторезистора
  int lightLevel = analogRead(ldrPin);
  
  // Выводим значение
  Serial.print("Light level: ");
  Serial.println(lightLevel);
  
  // Включаем светодиод, если темно (значение > 800)
  if (lightLevel > 800) {
    digitalWrite(ledPin, HIGH);
    Serial.println("It's dark! LED ON");
  } else {
    digitalWrite(ledPin, LOW);
  }
  
  delay(1000);
}

2.3 Пример 2: Датчик влажности почвы с калибровкой

const int soilPin = A1; // Датчик почвы
const int ledPin = 13; // Светодиод
const int dryThreshold = 700; // Порог для сухой почвы
const int wetThreshold = 300; // Порог для влажной почвы

void setup() {
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
  Serial.println("Soil Moisture Sensor");
}

void loop() {
  int moisture = analogRead(soilPin);
  
  // Преобразуем в проценты (0% = влажно, 100% = сухо)
  int moisturePercent = map(moisture, wetThreshold, dryThreshold, 100, 0);
  moisturePercent = constrain(moisturePercent, 0, 100);
  
  // Выводим информацию
  Serial.print("Raw: ");
  Serial.print(moisture);
  Serial.print(" | Percent: ");
  Serial.print(moisturePercent);
  Serial.println("%");
  
  // Включаем светодиод, если почва сухая
  if (moisture > dryThreshold) {
    digitalWrite(ledPin, HIGH);
    Serial.println("Soil is dry! Water needed.");
  } else {
    digitalWrite(ledPin, LOW);
  }
  
  delay(2000);
}

2.4 Пример 3: Логическая операция "И" (&&)

Оператор && (И) позволяет проверять несколько условий одновременно:

void loop() {
  int light = analogRead(A0);
  int moisture = analogRead(A1);
  
  // Включаем полив только если:
  // 1. Почва сухая (moisture > 700) И
  // 2. Сейчас день (light < 300)
  if (moisture > 700 && light < 300) {
    Serial.println("Conditions met: Watering plants");
    waterPlants();
  } else {
    Serial.println("Conditions not met");
    if (moisture <= 700) Serial.println(" - Soil is wet enough");
    if (light >= 300) Serial.println(" - It's night time");
  }
  
  delay(5000);
}
Таблица истинности оператора && (И):
Условие A && Условие B = Результат
true && true = true
true && false = false
false && true = false
false && false = false

Оба условия должны быть true, чтобы результат был true.

3. Советы по оптимизации работы с аналоговыми датчиками

3.1 Уменьшение шума измерений

int readAverage(int pin, int samples) {
  long sum = 0;
  for (int i = 0; i < samples; i++) {
    sum += analogRead(pin);
    delay(1); // Маленькая задержка между измерениями
  }
  return sum / samples;
}

void loop() {
  // Вместо analogRead(A0) используем:
  int smoothedValue = readAverage(A0, 10);
  // 10 измерений, усредненных = меньше шума
}

3.2 Экономное использование analogRead()

unsigned long lastReadTime = 0;
const unsigned long READ_INTERVAL = 1000; // 1 секунда

void loop() {
  unsigned long currentTime = millis();
  
  // Читаем датчик только раз в секунду
  if (currentTime - lastReadTime >= READ_INTERVAL) {
    int sensorValue = analogRead(A0);
    Serial.println(sensorValue);
    lastReadTime = currentTime;
  }
  
  // Здесь можно выполнять другую работу
  doOtherTasks();
}

📝 Домашнее задание

ДЗ #7 📅 Срок сдачи: 17.02 🏆 Макс. балл: 5 баллов

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

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

Задание на выбор (выполните ОДИН вариант):

Вариант 1: Система контроля влажности почвы
Светодиод индикации включается, когда почва становится слишком сухой.
Вариант 2: Система автоматического освещения
Светодиод включается, когда в комнате становится темно.

Вариант 1: Система контроля влажности почвы (5 баллов)

  1. Соберите схему со следующими компонентами:
    • Аналоговый датчик влажности почвы (подключен к A0)
    • Светодиод с резистором 220 Ом (подключен к пину 13)
    • Arduino Uno
  2. Напишите программу, которая:
    • Считывает значение с датчика влажности почвы каждые 2 секунды
    • Определяет пороговое значение для "сухой почвы" (например, > 700)
    • Включает светодиод, если analogRead() ниже порога (почва сухая)
    • Выключает светодиод, если почва влажная
    • Выводит в Serial Monitor:
      • Текущее значение датчика
      • Состояние почвы ("Сухая" или "Влажная")
      • Состояние светодиода ("ВКЛ" или "ВЫКЛ")

Пример кода для варианта 1:

const int soilPin = A0;
const int ledPin = 13;
const int dryThreshold = 700; // Настройте под ваш датчик!

void setup() {
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
  Serial.println("Soil Moisture Monitoring System");
  Serial.println("==============================");
}

void loop() {
  // 1. Считываем значение с датчика
  int moistureValue = analogRead(soilPin);
  
  // 2. Проверяем условие
  if (moistureValue > dryThreshold) {
    // Почва сухая - включаем светодиод
    digitalWrite(ledPin, HIGH);
    Serial.print("ALERT! Soil is DRY (");
    Serial.print(moistureValue);
    Serial.println(") - LED ON");
  } else {
    // Почва влажная - выключаем светодиод
    digitalWrite(ledPin, LOW);
    Serial.print("Soil is WET (");
    Serial.print(moistureValue);
    Serial.println(") - LED OFF");
  }
  
  // 3. Ждем 2 секунды перед следующим измерением
  delay(2000);
}

Вариант 2: Система автоматического освещения (5 баллов)

  1. Соберите схему со следующими компонентами:
    • Фоторезистор с резистором 10 кОм (подключен к A0)
    • Светодиод с резистором 220 Ом (подключен к пину 13)
    • Arduino Uno
  2. Напишите программу, которая:
    • Считывает значение с фоторезистора каждую секунду
    • Определяет пороговое значение для "темноты" (например, > 800)
    • Включает светодиод, если analogRead() выше порога (темно)
    • Выключает светодиод, если светло
    • Выводит в Serial Monitor:
      • Текущий уровень освещенности (0-1023)
      • Состояние ("Светло" или "Темно")
      • Состояние светодиода ("ВКЛ" или "ВЫКЛ")
      • Процент освещенности (используя map())

Пример кода для варианта 2:

const int ldrPin = A0;
const int ledPin = 13;
const int darkThreshold = 800; // Настройте под ваш датчик!

void setup() {
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
  Serial.println("Automatic Lighting System");
  Serial.println("==========================");
}

void loop() {
  // 1. Считываем значение с фоторезистора
  int lightValue = analogRead(ldrPin);
  
  // 2. Преобразуем в проценты (0% = темно, 100% = светло)
  int lightPercent = map(lightValue, 0, 1023, 0, 100);
  lightPercent = constrain(lightPercent, 0, 100);
  
  // 3. Проверяем условие
  if (lightValue > darkThreshold) {
    // Темно - включаем светодиод
    digitalWrite(ledPin, HIGH);
    Serial.print("DARK (");
    Serial.print(lightPercent);
    Serial.print("%) - LED ON | Raw: ");
    Serial.println(lightValue);
  } else {
    // Светло - выключаем светодиод
    digitalWrite(ledPin, LOW);
    Serial.print("LIGHT (");
    Serial.print(lightPercent);
    Serial.print("%) - LED OFF | Raw: ");
    Serial.println(lightValue);
  }
  
  // 4. Ждем 1 секунду перед следующим измерением
  delay(1000);
}

Дополнительное задание (дополнительный балл):

Добавьте "гистерезис" в систему:
Чтобы светодиод не мигал при значениях близких к порогу, используйте разные пороги для включения и выключения.
// Гистерезис для датчика освещенности
const int turnOnThreshold = 800; // Включить при > 800
const int turnOffThreshold = 600; // Выключить при < 600
bool ledState = false;

void loop() {
  int lightValue = analogRead(ldrPin);
  
  if (lightValue > turnOnThreshold && !ledState) {
    // Стало темно и свет был выключен - включаем
    ledState = true;
    digitalWrite(ledPin, HIGH);
  } else if (lightValue < turnOffThreshold && ledState) {
    // Стало светло и свет был включен - выключаем
    ledState = false;
    digitalWrite(ledPin, LOW);
  }
}

🗣 Критерии оценки ДЗ:

  • 2 балла: Корректная сборка схемы с датчиком и светодиодом
  • 2 балла: Рабочая программа с правильной логикой условия if()
  • 1 балл: Четкий вывод информации в Serial Monitor и правильная калибровка
  • Дополнительно +1 балл: Реализация гистерезиса или другой улучшенной логики
Важно для калибровки:
1. Проведите несколько измерений в разных условиях
2. Определите минимальное и максимальное значение датчика
3. Выберите порог посередине между состояниями
4. Убедитесь, что система стабильно работает при пороговом значении

🔍 Дополнительно

❓ Контрольные вопросы

Какие значения возвращает функция analogRead()?

Функция analogRead() возвращает целое число от 0 до 1023 (10-битное значение), где 0 соответствует 0V, а 1023 соответствует 5V на аналоговом входе Arduino.

Почему для фоторезистора нужен дополнительный резистор?

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

Что такое калибровка датчика и зачем она нужна?

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

Как работает функция map() и зачем она нужна?

Функция map(value, fromLow, fromHigh, toLow, toHigh) преобразует значение из одного диапазона в другой. Например, преобразует 0-1023 от датчика в 0-100% для удобного отображения.

Почему analogRead() может замедлять программу?

Каждое выполнение analogRead() занимает около 0.1 мс. Если вызывать её в цикле без задержек тысячи раз в секунду, это займет значительную часть времени процессора и замедлит выполнение остального кода.

Занятие 19. Умные датчики и протоколы

Занятие 19. Умные датчики и протоколы

???? 17.02 ⏱️ 2 × 40 минут
💡Интерфейсы связи! На этом занятии мы научимся работать с "умными" датчиками, которые общаются по специальным протоколам

📋 План занятия

  1. 00:00-00:20: Теория. Понятие протокола связи. "Умные" датчики. UART, I2C, 1-Wire.
  2. 00:20-00:40: Работа с HC-SR04: принцип эхолокации, функция pulseIn().
  3. 00:40-00:45: Короткий перерыв
  4. 00:45-01:15: Практика: измерение расстояния с HC-SR04. Датчик температуры DS18B20.
  5. 01:15-01:25: Использование библиотек OneWire и DallasTemperature
  6. 01:25-01:30: Итоги. Объяснение домашнего задания

🎯 Основной материал

1.1 Что такое "умные" датчики и протоколы связи?

Ключевые понятия:
Протокол связи — набор правил, по которым устройства обмениваются данными
"Умный" датчик — датчик со встроенным контроллером, который общается по протоколу
Библиотека — готовый код, упрощающий работу с устройством
UART, I2C, 1-Wire — популярные протоколы связи в микроконтроллерах

Преимущества "умных" датчиков:

  • Простота подключения: Часто требуется всего 2-3 провода
  • Высокая точность: Калибровка выполняется производителем
  • Цифровой интерфейс: Помехоустойчивость, не нужно АЦП
  • Мультиплексирование: Можно подключать несколько датчиков к одному проводу
  • Готовые библиотеки: Не нужно разбираться в протоколе, можно использовать готовые примеры

Популярные протоколы связи

Протокол Скорость Кол-во проводов Примеры датчиков
UART 9600-115200 бод 2 (RX, TX) GPS модули, Bluetooth
I2C 100-400 кГц 2 (SDA, SCL) Акселерометры, дисплеи
1-Wire 16 кбит/с 1 (данные + питание) DS18B20 (температура)
Важно!
Для работы с большинством "умных" датчиков не нужно глубоко разбираться в протоколе! Можно использовать готовые библиотеки и примеры из интернета.

1.2 Ультразвуковой датчик расстояния HC-SR04

HC-SR04 — популярный датчик для измерения расстояния от 2 см до 400 см с точностью ±3 мм.

Принцип работы (эхолокация):

  1. Отправка сигнала: Arduino отправляет короткий импульс на пин TRIG
  2. Излучение ультразвука: Датчик излучает 8 импульсов ультразвука (40 кГц)
  3. Ожидание эха: Звуковая волна отражается от объекта и возвращается к датчику
  4. Прием сигнала: Датчик генерирует импульс на пине ECHO, длительность которого равна времени прохождения звука туда и обратно
  5. Расчет расстояния: Расстояние = (время × скорость звука) / 2

Формула расчета расстояния:

Расстояние (см) = (длительность импульса (мкс) × 0.0343) / 2
или упрощенно:
Расстояние (см) = длительность импульса (мкс) / 58

Скорость звука: 343 м/с = 0.0343 см/мкс
Деление на 2 — потому что звук проходит путь туда и обратно

Функция pulseIn():

Функция pulseIn(pin, value, timeout) измеряет длительность импульса на указанном пине:

// Пример использования pulseIn()
unsigned long duration = pulseIn(echoPin, HIGH, 30000);
// Измеряет длительность HIGH импульса на echoPin
// timeout = 30000 мкс (30 мс) - максимальное время ожидания
// Возвращает длительность в микросекундах

2. Практика: Работа с ультразвуковым датчиком HC-SR04

Необходимые компоненты:
• Arduino Uno
• Ультразвуковой датчик HC-SR04
• Соединительные провода
• Макетная плата

2.1 Схема подключения HC-SR04

Подключение HC-SR04 к Arduino
HC-SR04 VCC → Arduino 5V
HC-SR04 GND → Arduino GND
HC-SR04 TRIG → Arduino Pin 9
HC-SR04 ECHO → Arduino Pin 10

Примечание: Некоторые версии HC-SR04 требуют резисторного делителя 5V→3.3V для пина ECHO

2.2 Пример 1: Измерение расстояния с HC-SR04

Полный код с подробными комментариями:

/* * Программа для измерения расстояния с датчика HC-SR04 * Выводит расстояние в сантиметрах в Serial Monitor */

// Определяем пины для подключения датчика
const int trigPin = 9; // Пин TRIG (управляющий)
const int echoPin = 10; // Пин ECHO (измерение длительности)

void setup() {
  // Настраиваем Serial Monitor для вывода данных
  Serial.begin(9600);
  
  // Настраиваем пины датчика
  pinMode(trigPin, OUTPUT); // TRIG - выход (отправляем сигнал)
  pinMode(echoPin, INPUT); // ECHO - вход (принимаем сигнал)
  
  Serial.println("HC-SR04 Distance Measurement");
  Serial.println("==============================");
}

void loop() {
  // 1. Сначала устанавливаем TRIG в LOW на 2 микросекунды
  // Это обеспечивает "чистый" стартовый импульс
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  
  // 2. Отправляем 10-микросекундный импульс HIGH на TRIG
  // Этот импульс "запускает" ультразвуковой датчик
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  
  // 3. Измеряем длительность импульса на пине ECHO
  // pulseIn() ждет, пока пин станет HIGH, затем измеряет время,
  // пока он не станет LOW, и возвращает длительность в микросекундах
  long duration = pulseIn(echoPin, HIGH, 30000);
  // timeout = 30000 мкс (30 мс) - максимальное время ожидания эха
  // Это соответствует максимальному расстоянию ~5 метров
  
  // 4. Проверяем, получили ли мы корректное измерение
  if (duration == 0) {
    // pulseIn() вернул 0, если не дождалась импульса (таймаут)
    Serial.println("No echo received - object too far or no object");
    delay(500);
    return; // Прерываем выполнение этой итерации loop()
  }
  
  // 5. Рассчитываем расстояние
  // Формула: расстояние = (время * скорость_звука) / 2
  // Скорость звука: 343 м/с = 0.0343 см/мкс
  // Деление на 2, потому что звук идет туда и обратно
  float distance = duration * 0.0343 / 2;
  
  // 6. Альтернативный расчет (более простая формула)
  // float distance = duration / 58.0; // Та же формула, упрощенная
  
  // 7. Выводим результаты в Serial Monitor
  Serial.print("Duration: ");
  Serial.print(duration);
  Serial.print(" microseconds | Distance: ");
  Serial.print(distance);
  Serial.println(" cm");
  
  // 8. Ждем 500 мс перед следующим измерением
  // Это нужно, чтобы эхо от предыдущего измерения полностью затухло
  delay(500);
}
Как проверить работу программы:
1. Подключите датчик по схеме выше
2. Загрузите программу в Arduino
3. Откройте Serial Monitor (9600 бод)
4. Поднесите руку к датчику и наблюдайте изменение расстояния
5. Попробуйте измерить расстояние до разных объектов

3. Датчик температуры DS18B20 (протокол 1-Wire)

DS18B20 — цифровой датчик температуры с интерфейсом 1-Wire. Точность ±0.5°C в диапазоне -10°C до +85°C.

Особенности протокола 1-Wire:

  • Один провод: Данные и питание по одному проводу (паразитное питание)
  • Множество устройств: К одному проводу можно подключить много датчиков
  • Уникальные адреса: Каждый датчик имеет уникальный 64-битный серийный номер
  • Библиотеки: OneWire и DallasTemperature упрощают работу с датчиком

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

Подключение DS18B20 к Arduino
DS18B20 VDD (красный) → Arduino 5V
DS18B20 GND (черный) → Arduino GND
DS18B20 DQ (желтый/белый) → Arduino Pin 2
DQ → Резистор 4.7 кОм → 5V (подтяжка к питанию)

Важно: Резистор 4.7 кОм обязателен для работы протокола 1-Wire!

4. Практика: Работа с датчиком температуры DS18B20

4.1 Установка библиотек

Для работы с DS18B20 нужны две библиотеки:

  1. OneWire — для работы с протоколом 1-Wire
  2. DallasTemperature — для удобной работы с датчиками Dallas (включая DS18B20)
Как установить библиотеки в Arduino IDE:
1. Скетч → Подключить библиотеку → Управлять библиотеками
2. В поиске введите "OneWire", установите библиотеку от Paul Stoffregen
3. В поиске введите "DallasTemperature", установите библиотеку от Miles Burton
4. Перезапустите Arduino IDE

4.2 Пример 2: Измерение температуры с DS18B20

/* * Программа для измерения температуры с датчика DS18B20 * Использует библиотеки OneWire и DallasTemperature * Выводит температуру в градусах Цельсия в Serial Monitor */

// Подключаем необходимые библиотеки
#include <OneWire.h>
#include <DallasTemperature.h>

// Определяем пин для подключения датчика
#define ONE_WIRE_BUS 2

// Создаем объект для работы с шиной 1-Wire
OneWire oneWire(ONE_WIRE_BUS);

// Создаем объект для работы с датчиками Dallas
// и передаем ему ссылку на объект oneWire
DallasTemperature sensors(&oneWire);

void setup() {
  // Настраиваем Serial Monitor
  Serial.begin(9600);
  Serial.println("DS18B20 Temperature Sensor");
  Serial.println("==========================");
  
  // Инициализируем работу с датчиками
  sensors.begin();
  
  // Определяем сколько датчиков подключено к шине
  int deviceCount = sensors.getDeviceCount();
  Serial.print("Found ");
  Serial.print(deviceCount);
  Serial.println(" temperature sensor(s)");
}

void loop() {
  Serial.println("------------------------");
  
  // 1. Запрашиваем температуру у всех датчиков на шине
  // Эта команда отправляется всем датчикам одновременно
  sensors.requestTemperatures();
  
  // 2. Читаем температуру с первого датчика (индекс 0)
  // Если датчиков несколько, можно прочитать их все
  float temperature = sensors.getTempCByIndex(0);
  
  // 3. Проверяем корректность измерения
  // getTempCByIndex() возвращает -127 при ошибке
  if (temperature == -127.0) {
    Serial.println("Error reading temperature!");
    Serial.println("Check connection and pull-up resistor");
  } else {
    // 4. Выводим температуру
    Serial.print("Temperature: ");
    Serial.print(temperature);
    Serial.println(" °C");
    
    // 5. Дополнительно: конвертируем в градусы Фаренгейта
    float fahrenheit = temperature * 9.0 / 5.0 + 32.0;
    Serial.print("Temperature: ");
    Serial.print(fahrenheit);
    Serial.println(" °F");
  }
  
  // 6. Ждем 2 секунды перед следующим измерением
  // DS18B20 требует время на преобразование температуры
  delay(2000);
}
Преимущество использования библиотек:
• Не нужно разбираться в протоколе 1-Wire
• Все сложные операции скрыты в библиотеке
• Можно легко работать с несколькими датчиками
• Есть готовые примеры в Arduino IDE
• Автоматическое определение датчиков на шине

5. Интерактивный симулятор "Парктроник"

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

Перемещайте ползунок, чтобы изменить "расстояние до объекта":

100 см
Статус: Объект далеко

Пороги срабатывания парктроника:

150+ см: Объект далеко (зеленый)

50-149 см: Объект близко (желтый)

15-49 см: Осторожно! (оранжевый)

0-14 см: Опасность! Включен зуммер (красный)

📝 Домашнее задание

ДЗ #7 📅 Срок сдачи: 24.02 🏆 Макс. балл: 5 баллов

Система парктроник с ультразвуковым датчиком

Цель задания: Научиться создавать полезные устройства с использованием "умных" датчиков, работать с функцией pulseIn() и создавать звуковую индикацию.

Задание: Система парковочного радара (парктроник)

  1. Соберите схему со следующими компонентами:
    • Ультразвуковой датчик HC-SR04
    • Пассивный зуммер (buzzer)
    • Arduino Uno
    • Макетная плата и провода
  2. Напишите программу, которая:
    • Измеряет расстояние до объекта с помощью HC-SR04
    • Выводит расстояние в сантиметрах в Serial Monitor
    • При расстоянии больше 15 см: зуммер выключен
    • При расстоянии 15 см или меньше: включается прерывистый звуковой сигнал
    • Чем ближе объект, тем чаще сигнал (выше частота)

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

Подключение для ДЗ
HC-SR04 VCC → Arduino 5V
HC-SR04 GND → Arduino GND
HC-SR04 TRIG → Arduino Pin 9
HC-SR04 ECHO → Arduino Pin 10

Зуммер (+) → Arduino Pin 8
Зуммер (-) → Arduino GND

Примечание: Если зуммер активный (со встроенным генератором),
он будет издавать звук постоянной частоты при подаче питания.
Для этого задания нужен ПАССИВНЫЙ зуммер!

Пример кода для домашнего задания:

/* * Домашнее задание: Парктроник * HC-SR04 + зуммер * Выводит расстояние в Serial Monitor * Включает прерывистый звук при приближении < 15 см */

const int trigPin = 9; // Пин TRIG датчика
const int echoPin = 10; // Пин ECHO датчика
const int buzzerPin = 8; // Пин зуммера
const int warningDistance = 15; // Порог срабатывания (см)

void setup() {
  Serial.begin(9600);
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);
  pinMode(buzzerPin, OUTPUT);
  
  Serial.println("Parktronic System");
  Serial.println("Warning distance: " + String(warningDistance) + " cm");
}

void loop() {
  // Измеряем расстояние
  long distance = measureDistance();
  
  // Выводим расстояние в Serial Monitor
  Serial.print("Distance: ");
  if (distance > 0) {
    Serial.print(distance);
    Serial.println(" cm");
  } else {
    Serial.println("Out of range");
  }
  
  // Проверяем расстояние и управляем зуммером
  if (distance > 0 && distance <= warningDistance) {
    // Объект ближе 15 см - включаем прерывистый сигнал
   &span class="code-function">playWarningTone(distance);
    Serial.println("WARNING! Object too close!");
  } else {
    // Объект далеко - выключаем зуммер
    noTone(buzzerPin);
  }
  
  delay(100); // Короткая задержка между измерениями
}

// Функция для измерения расстояния
long measureDistance() {
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
  
  long duration = pulseIn(echoPin, HIGH, 30000);
  
  if (duration == 0) {
    return -1; // Ошибка измерения
  }
  
  // Рассчитываем расстояние в см
  long distance = duration / 58;
  return distance;
}

// Функция для воспроизведения предупреждающего сигнала
// Чем меньше расстояние, тем чаще сигнал
void playWarningTone(long distance) {
  // Частота сигнала зависит от расстояния
  // Чем ближе объект, тем выше частота
  int frequency = map(distance, 5, warningDistance, 2000, 500);
  frequency = constrain(frequency, 500, 2000);
  
  // Длительность сигнала также зависит от расстояния
  int toneDuration = map(distance, 5, warningDistance, 50, 200);
  
  // Воспроизводим тон
  tone(buzzerPin, frequency, toneDuration);
}

Дополнительное задание (дополнительный балл):

Добавьте светодиодную индикацию:
• Зеленый светодиод: расстояние > 50 см
• Желтый светодиод: расстояние 15-50 см
• Красный светодиод: расстояние < 15 см + зуммер

Используйте 3 светодиода разных цветов для визуальной индикации.

Подсказка для улучшения программы:

// Добавьте в начало программы:
const int greenLED = 3;
const int yellowLED = 4;
const int redLED = 5;

// В setup() добавьте:
pinMode(greenLED, OUTPUT);
pinMode(yellowLED, OUTPUT);
pinMode(redLED, OUTPUT);

// В loop() после измерения расстояния:
if (distance > 50) {
  // Зеленый - объект далеко
  digitalWrite(greenLED, HIGH);
  digitalWrite(yellowLED, LOW);
  digitalWrite(redLED, LOW);
} else if (distance > 15) {
  // Желтый - осторожно
  digitalWrite(greenLED, LOW);
  digitalWrite(yellowLED, HIGH);
  digitalWrite(redLED, LOW);
} else if (distance > 0) {
  // Красный + зуммер - опасность
  digitalWrite(greenLED, LOW);
  digitalWrite(yellowLED, LOW);
  digitalWrite(redLED, HIGH);
}

🜇; Критерии оценки ДЗ:

  • 1 балл: Корректная сборка схемы с HC-SR04 и зуммером
  • 1 балл: Рабочее измерение расстояния и вывод в Serial Monitor
  • 2 балла: Корректная работа зуммера (прерывистый сигнал при distance ≤ 15 см)
  • 1 балл: Четкий вывод информации и комментирование кода
  • Дополнительно +1 балл: Реализация светодиодной индикации (3 светодиода)
Важные замечания:
1. Для работы с зуммером используйте функцию tone(pin, frequency, duration)
2. Пассивный зуммер может издавать звук только одной частоты в момент времени
3. Функция tone() не блокирует выполнение программы (в отличие от delay)
4. HC-SR04 может неправильно измерять расстояние до мягких или пористых объектов
5. Проверьте работу системы на разных расстояниях (5, 10, 15, 20, 50 см)

🔍 Дополнительно

❓ Контрольные вопросы

Что такое протокол связи и зачем он нужен?

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

Как работает ультразвуковой датчик HC-SR04?

HC-SR04 работает по принципу эхолокации: отправляет ультразвуковой импульс, ждет его отражения от объекта и измеряет время между отправкой и приемом эха. Расстояние вычисляется по формуле: расстояние = (время × скорость звука) / 2.

Что делает функция pulseIn() и как она используется с HC-SR04?

Функция pulseIn(pin, value, timeout) измеряет длительность импульса на указанном пине. С HC-SR04 она измеряет длительность HIGH-импульса на пине ECHO, который соответствует времени прохождения звука до объекта и обратно.

Чем "умные" датчики лучше аналоговых?

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

Зачем нужен резистор 4.7 кОм при подключении DS18B20?

Резистор 4.7 кОм является подтягивающим резистором (pull-up resistor). Он необходим для корректной работы протокола 1-Wire, так как подтягивает линию данных к высокому уровню, когда ни одно устройство не активно.

Можно ли подключить несколько датчиков DS18B20 к одному проводу?

Да, можно! К одной шине 1-Wire можно подключить до 127 датчиков DS18B20. Каждый датчик имеет уникальный 64-битный серийный номер, что позволяет обращаться к каждому отдельно. Библиотека DallasTemperature автоматически определяет все подключенные датчики.

🗣 Полезные ресурсы

⚠️ Частые проблемы и их решения

HC-SR04 показывает неправильные значения или постоянно 0

Возможные причины и решения:

  1. Неправильное подключение: Проверьте, что TRIG и ECHO не перепутаны
  2. Отсутствие питания: Проверьте подключение VCC и GND
  3. Объект слишком близко: Минимальное расстояние HC-SR04 — 2 см
  4. Объект слишком далеко: Максимальное расстояние — 400 см
  5. Мягкий или пористый объект: Ультразвук плохо отражается от тканей, ковров
  6. Несколько датчиков мешают друг другу: Работайте с одним датчиком или добавьте задержки
DS18B20 не определяется или показывает -127°C

Проверьте следующее:

  1. Подтягивающий резистор: Обязателен резистор 4.7 кОм между DQ и 5V
  2. Полярность: У DS18B20 обычно: красный - 5V, черный - GND, желтый/белый - данные
  3. Библиотеки: Установлены ли OneWire и DallasTemperature?
  4. Провода: Длинные провода могут создавать помехи, используйте провода короче 10 м
  5. Питание: При паразитном питании (2 провода) могут быть проблемы, лучше использовать 3 провода
Зуммер не издает звук или пищит постоянно

Диагностика:

  1. Активный vs пассивный: Убедитесь, что у вас пассивный зуммер
  2. Полярность: У зуммера есть + и -, подключите правильно
  3. Функция tone(): Используйте tone(pin, frequency) для включения и noTone(pin) для выключения
  4. Частота: Человеческое ухо слышит 20-20000 Гц, пробуйте 1000-4000 Гц
  5. Тест: Попробуйте простой тест: tone(8, 1000); delay(1000); noTone(8);
Занятие 20. Двигатели и направление — H-мост

16. Двигатели и направление — H-мост

📅 27.01 ⏱ 2 × 40 минут
🚗 Ключевая тема для проекта! На этом занятии мы научимся управлять направлением вращения моторов, что критически важно для робота Line Follower

📋 План занятия

  1. 00:00-00:20: Теория. Почему MOSFET не может менять направление. Принцип H-моста.
  2. 00:20-00:40: Знакомство с драйвером L298N/L9110S. Таблица истинности управления.
  3. 00:40-00:45: Короткий перерыв
  4. 00:45-01:25: Практика: управление TT-мотором через драйвер. Плавный разгон и торможение.
  5. 01:25-01:30: Итоги. Объяснение домашнего задания

🎯 Основной материал

1.1 Почему MOSFET не может менять направление вращения?

Ограничение одиночного MOSFET:
MOSFET может только включать/выключать ток, но не может менять его направление.
Мотору для реверса нужно поменять полярность напряжения — для этого нужна специальная схема.

Аналогия с мостом:

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

  • MOSFET — это просто паром, который может только включать/выключать движение
  • H-мост — это разводной мост с двумя путями: на северный берег и на южный

Решение: H-мост (Н-мост)

Структура H-моста (4 ключа):

[+] ---- [Q1] ---- A+ ---- [МОТОР] ---- B- ---- [Q4] ---- [-]
             |                                    |
[+] ---- [Q2] ---- A- ---- [МОТОР] ---- B+ ---- [Q3] ---- [-]

Q1-Q4 — транзисторные ключи (обычно MOSFET)

1.2 Интерактивная схема H-моста

Нажмите на состояния ниже, чтобы увидеть, как работает H-мост:

▶ ВПЕРЕД

Q1 и Q4 открыты

Ток: [+] → Q1 → Мотор(+) → Мотор(-) → Q4 → [-]

● Q1 ВКЛ
○ Q2 ВЫКЛ
○ Q3 ВЫКЛ
● Q4 ВКЛ

◀ НАЗАД

Q2 и Q3 открыты

Ток: [+] → Q2 → Мотор(-) → Мотор(+) → Q3 → [-]

○ Q1 ВЫКЛ
● Q2 ВКЛ
● Q3 ВКЛ
○ Q4 ВЫКЛ

⚠ ТОРМОЖЕНИЕ

Q1 и Q2 открыты (или Q3 и Q4)

Короткое замыкание обмоток мотора

● Q1 ВКЛ
● Q2 ВКЛ
○ Q3 ВЫКЛ
○ Q4 ВЫКЛ

∞ СВОБОДНЫЙ ХОД

Все ключи выключены

Мотор вращается по инерции

○ Q1 ВЫКЛ
○ Q2 ВЫКЛ
○ Q3 ВЫКЛ
○ Q4 ВЫКЛ
Выберите состояние выше, чтобы увидеть описание
На схеме показано, как работают 4 транзисторных ключа для управления направлением вращения мотора.

1.3 Драйверы двигателей L298N и L9110S

Вместо того чтобы собирать H-мост из отдельных транзисторов, мы используем готовые драйверы:

Драйвер Макс. ток Напряжение Особенности
L298N 2А на канал (4А пиковый) 5-35В 2 канала, радиатор, стабилизатор 5В
L9110S 0.8А (1.5А пиковый) 2.5-12В Маленький, дешевый, 2 канала
TB6612FNG 1.2А (3.2А пиковый) 2.5-13.5В Эффективный, мало тепла

Распиновка L298N (самый распространенный):

Питание

+12V (VM) Питание моторов (5-35В)
+5V (VCC) Питание логики (можно от Arduino)
GND Земля (должна быть общей!)

Канал A (Мотор 1)

ENA ШИМ для скорости (пин ~ на Arduino)
IN1 Направление 1 (цифровой пин)
IN2 Направление 2 (цифровой пин)
OUT1, OUT2 Выход на мотор

Канал B (Мотор 2)

ENB ШИМ для скорости
IN3, IN4 Направление
OUT3, OUT4 Выход на мотор
Важно: Для небольших моторов (TT-моторы, до 500 мА) можно использовать L298N без радиатора. Для более мощных моторов обязательно нужен радиатор или активное охлаждение!

1.4 Таблица истинности управления L298N

Управление направлением осуществляется комбинацией сигналов IN1 и IN2:

IN1 IN2 ENA Режим Действие
1 (HIGH) 0 (LOW) PWM (0-255) ВПЕРЕД Вращение по часовой стрелке
0 (LOW) 1 (HIGH) PWM (0-255) НАЗАД Вращение против часовой стрелки
1 (HIGH) 1 (HIGH) X ТОРМОЖЕНИЕ Быстрая остановка (короткое замыкание)
0 (LOW) 0 (LOW) X СВОБОДНЫЙ ХОД Вращение по инерции
X X 0 (LOW) ОТКЛЮЧЕНО Мотор выключен (независимо от IN1/IN2)
Опасная комбинация: Никогда не подавайте одновременно IN1=1 и IN2=1 при ENA=1 (PWM > 0)! Это создаст короткое замыкание внутри драйвера и может его повредить.

Универсальная функция управления мотором:

// Универсальная функция управления мотором через L298N
void setMotor(int enaPin, int in1Pin, int in2Pin, int speed, bool direction) {
  // speed: 0-255 (ШИМ значение)
  // direction: true - вперед, false - назад

  if (speed == 0) {
    // Остановка (свободный ход)
    digitalWrite(in1Pin, LOW);
    digitalWrite(in2Pin, LOW);
    analogWrite(enaPin, 0);
  } else if (direction) {
    // Вращение вперед
    digitalWrite(in1Pin, HIGH);
    digitalWrite(in2Pin, LOW);
    analogWrite(enaPin, speed);
  } else {
    // Вращение назад
    digitalWrite(in1Pin, LOW);
    digitalWrite(in2Pin, HIGH);
    analogWrite(enaPin, speed);
  }
}

// Пример использования:
void setup() {
  pinMode(9, OUTPUT); // ENA
  pinMode(8, OUTPUT); // IN1
  pinMode(7, OUTPUT); // IN2
}

void loop() {
  // Вперед на полной скорости
  setMotor(9, 8, 7, 255, true);
  delay(2000);
  
  // Назад на половине скорости
  setMotor(9, 8, 7, 128, false);
  delay(2000);
}

2. Практическая часть: Управление TT-мотором

Цель практики: Собрать схему управления TT-мотором через драйвер L298N/L9110S и написать программу плавного управления скоростью и направлением.

2.1 Необходимые компоненты

Компонент Количество Примечание
Arduino Uno/Nano 1 -
Драйвер L298N или L9110S 1 Для одного мотора достаточно одного канала
TT-мотор с редуктором 1 3-6В, с колесом
Батарейный отсек 4×AA 1 6В для моторов (ОТДЕЛЬНОЕ ПИТАНИЕ!)
Перемычки папа-папа 10-15 Для подключения
Макетная плата 1 Для удобства подключения

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

Подключение для одного мотора:

1. Arduino 5V → VCC на L298N (питание логики)
2. Arduino GND → GND на L298N (ОБЯЗАТЕЛЬНО!)
3. Батарейка 6V+ → +12V (VM) на L298N
4. Батарейка 6V- → GND на L298N
5. Arduino D9 (~) → ENA на L298N (ШИМ для скорости)
6. Arduino D8 → IN1 на L298N
7. Arduino D7 → IN2 на L298N
8. OUT1, OUT2 на L298N → Контакты мотора
ВНИМАНИЕ!
1. НЕ подключайте питание моторов (VM) к Arduino 5V!
2. ОБЯЗАТЕЛЬНА общая земля между Arduino, драйвером и батарейкой
3. TT-моторы могут потреблять до 500 мА при старте — Arduino не обеспечит такой ток!

2.3 Базовые задачи управления

// === КОНФИГУРАЦИЯ ПИНОВ ===
const int ENA_PIN = 9; // ШИМ-пин для скорости (должен быть ~)
const int IN1_PIN = 8; // Направление 1
const int IN2_PIN = 7; // Направление 2

void setup() {
  pinMode(ENA_PIN, OUTPUT);
  pinMode(IN1_PIN, OUTPUT);
  pinMode(IN2_PIN, OUTPUT);
  Serial.begin(9600);
  Serial.println("Motor Control Ready");
}

void loop() {
  Serial.println("Forward at full speed");
  motorForward(255);
  delay(2000);
  
  Serial.println("Backward at half speed");
  motorBackward(128);
  delay(2000);
  
  Serial.println("Stop (coast)");
  motorStop();
  delay(1000);
  
  Serial.println("Brake (fast stop)");
  motorBrake();
  delay(1000);
}

void motorForward(int speed) {
  digitalWrite(IN1_PIN, HIGH);
  digitalWrite(IN2_PIN, LOW);
  analogWrite(ENA_PIN, speed);
}

void motorBackward(int speed) {
  digitalWrite(IN1_PIN, LOW);
  digitalWrite(IN2_PIN, HIGH);
  analogWrite(ENA_PIN, speed);
}

void motorStop() {
  // Свободный ход (коастинг)
  digitalWrite(IN1_PIN, LOW);
  digitalWrite(IN2_PIN, LOW);
  analogWrite(ENA_PIN, 0);
}

void motorBrake() {
  // Торможение (короткое замыкание обмоток)
  digitalWrite(IN1_PIN, HIGH);
  digitalWrite(IN2_PIN, HIGH);
  analogWrite(ENA_PIN, 0);
}

2.4 Плавный разгон и торможение

void smoothAcceleration(bool forward, int accelerationTime) {
  // Устанавливаем направление
  if (forward) {
    digitalWrite(IN1_PIN, HIGH);
    digitalWrite(IN2_PIN, LOW);
  } else {
    digitalWrite(IN1_PIN, LOW);
    digitalWrite(IN2_PIN, HIGH);
  }
  
  // Плавный разгон от 0 до 255
  Serial.print("Accelerating ");
  Serial.println(forward ? "forward" : "backward");
  
  for (int speed = 0; speed <= 255; speed++) {
    analogWrite(ENA_PIN, speed);
    delay(accelerationTime / 255);
  }
  
  // Держим максимальную скорость 1 секунду
  delay(1000);
  
  // Плавное торможение от 255 до 0
  Serial.println("Decelerating");
  for (int speed = 255; speed >= 0; speed--) {
    analogWrite(ENA_PIN, speed);
    delay(accelerationTime / 255);
  }
  
  // Полная остановка
  motorStop();
  delay(500);
}

2.5 Задания для практики

Выполните все три задания:
1. Соберите схему с драйвером и одним мотором
2. Загрузите код плавного разгона/торможения
3. Модифицируйте программу по указаниям ниже

Задание 1: "Змейка скорости" 1 балл

Задача: Создайте программу, где мотор плавно разгоняется от 0 до 255, затем плавно тормозит до 0, и сразу (без остановки) начинает движение в обратном направлении с таким же профилем скорости.

void loop() {
  // === ВАШ КОД ЗДЕСЬ ===
  // 1. Плавный разгон вперед от 0 до 255 (3 секунды)
  // 2. Плавное торможение до 0 (3 секунды)
  // 3. БЕЗ ПАУЗЫ: плавный разгон назад от 0 до 255
  // 4. Плавное торможение назад до 0
  // 5. Пауза 1 секунда и повтор цикла
}

Задание 2: "Ступенчатая скорость" 1 балл

Задача: Реализуйте программу, где мотор работает 2 секунды на скорости 64, затем 2 секунды на скорости 128, затем 2 секунды на скорости 192, затем 2 секунды на скорости 255 — всё в одном направлении.

void loop() {
  // === ВАШ КОД ЗДЕСЬ ===
  // Используйте массив скоростей: int speeds[] = {64, 128, 192, 255};
  // Цикл for по массиву, для каждой скорости:
  // 1. Установить направление (вперед)
  // 2. Установить скорость из массива
  // 3. Ждать 2000 мс
  // 4. Перейти к следующей скорости
}

Задание 3: "Эксперимент с торможением" 1 балл

Задача: Сравните два вида остановки: свободный ход (коастинг) и торможение (брейкинг).

void loop() {
  // Тест 1: Свободный ход
  Serial.println("Test 1: Coasting stop");
  motorForward(255);
  delay(1000);
  motorStop(); // Свободный ход
  // Замерьте время полной остановки мотора
  delay(3000);
  
  // Тест 2: Торможение
  Serial.println("Test 2: Braking stop");
  motorForward(255);
  delay(1000);
  motorBrake(); // Торможение
  // Замерьте время полной остановки мотора
  delay(3000);
  
  // === ЗАПИШИТЕ В ТЕТРАДЬ ===
  // 1. Какая остановка быстрее?
  // 2. Слышен ли характерный звук при торможении?
  // 3. Какой способ лучше для точного позиционирования?
}

3. Важные технические замечания

3.1 Диоды обратной связи (Flyback Diodes)

Почему моторы "стреляют" в драйвер:
При резком отключении мотора в его обмотках возникает ЭДС самоиндукции (высокое напряжение обратной полярности).
Хорошая новость: В драйверах L298N и L9110S защитные диоды уже встроены на плате!

3.2 Нагрев драйвера

L298N сильно греется, особенно при больших токах или при использовании торможения:

  • Без радиатора: До 0.5А непрерывно
  • С радиатором: До 2А непрерывно
  • Решение: Установите радиатор, используйте активное охлаждение (вентилятор), избегайте длительного торможения

3.3 Переход на более эффективные драйверы

Для проектов роботов рекомендуются более современные драйверы:

Драйвер Преимущества Для каких проектов
TB6612FNG Низкий нагрев, высокая эффективность Line Follower, небольшие роботы
DRV8833 Маленький размер, низкое напряжение Мини-роботы, дроны
VNH5019 Мощный (до 30А), защита от перегрева Тяжелые роботы, автомобили

📝 Домашнее задание

ДЗ #5 📅 Срок сдачи: 03.02 🏆 Макс. балл: 5 баллов

Управление скоростью мотора через потенциометр

Цель задания: Научиться считывать аналоговые значения с потенциометра и использовать их для плавного управления скоростью и направлением вращения мотора.

Часть 1: Сборка схемы (2 балла)

  1. Соберите схему со следующими компонентами:
    • Драйвер L298N/L9110S с подключенным TT-мотором
    • Потенциометр 10 кОм, подключенный к аналоговому пину A0
    • Отдельный источник питания 6В для моторов (батарейный отсек)
  2. Схема подключения потенциометра:
    Потенциометр (10 кОм):
    • Левый вывод → GND Arduino
    • Средний вывод → A0 Arduino (сигнал)
    • Правый вывод → 5V Arduino

Часть 2: Программирование (3 балла)

Задача: Напишите программу, которая:

  1. Считывает значение с потенциометра (0-1023)
  2. Преобразует это значение в скорость мотора (-255 до +255) с помощью функции map()
  3. Управляет мотором:
    • Когда потенциометр в среднем положении → мотор остановлен
    • При повороте вправо → мотор вращается вперед со скоростью пропорциональной углу поворота
    • При повороте влево → мотор вращается назад со скоростью пропорциональной углу поворота
  4. Выводит в Serial Monitor:
    • Сырое значение с потенциометра (0-1023)
    • Преобразованная скорость (-255..+255)
    • Направление вращения (Вперед/Назад/Стоп)

Ключевая функция: map()

// Функция map() преобразует число из одного диапазона в другой
// Синтаксис: map(значение, отМинимум, отМаксимум, кМинимум, кМаксимум)

// Пример 1: Преобразование 0-1023 в 0-255
int potValue = analogRead(A0); // 0-1023
int speed = map(potValue, 0, 1023, 0, 255);
// speed теперь будет от 0 до 255

// Пример 2: Преобразование 0-1023 в -255 до +255
int potValue2 = analogRead(A0);
int speed2 = map(potValue2, 0, 1023, -255, 255);
// speed2 будет от -255 до +255
// Отрицательные значения - вращение назад
// Положительные значения - вращение вперед
Как работает map():
1. Берёт значение из исходного диапазона (0-1023)
2. Вычисляет его положение в этом диапазоне (например, 512 - это середина)
3. Переносит это положение в новый диапазон (-255..+255)
4. Возвращает соответствующее значение в новом диапазоне

Формула:
результат = (значение - отМинимум) × (кМаксимум - кМинимум) / (отМаксимум - отМинимум) + кМинимум

Каркас программы для ДЗ:

// === КОНФИГУРАЦИЯ ПИНОВ ===
const int POT_PIN = A0; // Потенциометр
const int ENA_PIN = 9; // ШИМ для скорости
const int IN1_PIN = 8; // Направление 1
const int IN2_PIN = 7; // Направление 2

void setup() {
  pinMode(ENA_PIN, OUTPUT);
  pinMode(IN1_PIN, OUTPUT);
  pinMode(IN2_PIN, OUTPUT);
  Serial.begin(9600);
  Serial.println("Potentiometer Motor Control");
}

void loop() {
  // 1. Считать значение с потенциометра (0-1023)
  int potValue = analogRead(POT_PIN);
  
  // 2. Преобразовать в скорость от -255 до +255 с помощью map()
  int speed = map(potValue, 0, 1023, -255, 255);
  
  // 3. Управление мотором в зависимости от скорости
  if (speed > 0) {
    // Вращение вперед
    digitalWrite(IN1_PIN, HIGH);
    digitalWrite(IN2_PIN, LOW);
    analogWrite(ENA_PIN, speed);
    Serial.print("Forward, speed: ");
  } else if (speed < 0) {
    // Вращение назад (скорость отрицательная)
    digitalWrite(IN1_PIN, LOW);
    digitalWrite(IN2_PIN, HIGH);
    analogWrite(ENA_PIN, -speed); // Берем модуль скорости
    Serial.print("Backward, speed: ");
  } else {
    // Остановка
    digitalWrite(IN1_PIN, LOW);
    digitalWrite(IN2_PIN, LOW);
    analogWrite(ENA_PIN, 0);
    Serial.print("Stopped, speed: ");
  }
  
  // 4. Вывод информации в Serial Monitor
  Serial.print(speed);
  Serial.print(" | Potentiometer: ");
  Serial.println(potValue);
  
  // 5. Небольшая задержка для стабильности
  delay(100);
}

Усложнение задания (дополнительные баллы):

Выполните ОДИН из вариантов для получения дополнительного балла:
Вариант А: "Мертвая зона" +1 балл

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

// Пример создания мертвой зоны
int potValue = analogRead(POT_PIN);
int speed = 0;

if (potValue < 450) {
  // Левый диапазон: назад
  speed = map(potValue, 0, 450, -255, 0);
} else if (potValue > 573) {
  // Правый диапазон: вперед
  speed = map(potValue, 573, 1023, 0, 255);
}
// Между 450 и 573 - мертвая зона, speed = 0
Вариант Б: "Плавный старт" +1 балл

Добавьте плавный старт мотора при резком повороте потенциометра. Используйте плавное изменение скорости вместо мгновенного.

// Пример плавного изменения скорости
int targetSpeed = map(potValue, 0, 1023, -255, 255);
static int currentSpeed = 0;

// Плавное изменение текущей скорости к целевой
if (currentSpeed < targetSpeed) {
  currentSpeed += 5; // Шаг разгона
  currentSpeed = min(currentSpeed, targetSpeed);
} else if (currentSpeed > targetSpeed) {
  currentSpeed -= 5; // Шаг торможения
  currentSpeed = max(currentSpeed, targetSpeed);
}
Вариант В: "Двойной потенциометр" +1 балл

Используйте два потенциометра: один для управления скоростью (0-255), второй для выбора направления (вперед/назад) через кнопку или тумблер.

// Пример с двумя потенциометрами
int speedPot = analogRead(A0); // Скорость (0-1023)
int dirPot = analogRead(A1); // Направление

// Преобразование скорости
int speed = map(speedPot, 0, 1023, 0, 255);

// Определение направления
bool forward = (dirPot > 512); // >50% - вперед

🗣 Критерии оценки ДЗ:

  • 2 балла: Корректная сборка схемы с потенциометром и драйвером мотора
  • 2 балла: Рабочая программа с использованием функции map() для преобразования значений
  • 1 балл: Четкий вывод информации в Serial Monitor и плавное управление мотором
  • Дополнительно +1 балл: Выполнение одного из вариантов усложнения

Что должно быть в отчете:

  1. Фото собранной схемы
  2. Полный код программы с комментариями
  3. Скриншот Serial Monitor с показаниями потенциометра и скорости
  4. Описание работы: как значения 0-1023 преобразуются в -255..+255
Советы по выполнению:
• Используйте функцию constrain() для ограничения скорости в диапазоне -255..255
• Добавьте небольшую задержку delay(10-50) для стабильности считывания
• Для отрицательных скоростей используйте функцию abs() для получения модуля
• Проверьте, что при среднем положении потенциометра мотор действительно остановлен
Распространенные ошибки:
1. Не подключена общая земля (GND) между Arduino, драйвером и потенциометром
2. Питание моторов подключено к 5V Arduino (должно быть отдельное питание!)
3. Забыли использовать analogWrite() для ШИМ-пина скорости
4. Неправильные диапазоны в функции map()

🔍 Дополнительно

❓ Контрольные вопросы

Почему для изменения направления мотора нужен H-мост, а не просто MOSFET?

Одиночный MOSFET может только включать/выключать ток в одном направлении. H-мост состоит из 4 ключей, которые могут менять полярность напряжения на моторе, создавая ток в обоих направлениях.

Что произойдет, если одновременно открыть верхний и нижний ключи на одной стороне H-моста?

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

В чем разница между свободным ходом (коастинг) и торможением (брейкинг)?

При свободном ходе все ключи выключены, мотор вращается по инерции. При торможении обмотки мотора замыкаются через ключи, создавая электрическое сопротивление вращению — мотор останавливается быстрее.

Зачем нужен отдельный источник питания для моторов?

Моторы потребляют большой ток (сотни мА - единицы А). Arduino не может обеспечить такой ток через свои пины. Также пульсации тока от моторов могут создавать помехи в работе микроконтроллера.

Почему L298N сильно греется, а TB6612FNG — нет?

L298N использует биполярную технологию с большим падением напряжения (≈2В). TB6612FNG использует MOSFET с очень низким сопротивлением в открытом состоянии (≈0.2Ω), поэтому выделяет меньше тепла.

💡 Готовые функции для проекта Line Follower

Библиотека управления двумя моторами

// === КОНФИГУРАЦИЯ ДЛЯ ДВУХ МОТОРОВ ===
const int ENA = 9; // Левый мотор: скорость
const int IN1 = 8; // Левый мотор: направление 1
const int IN2 = 7; // Левый мотор: направление 2
const int ENB = 10; // Правый мотор: скорость
const int IN3 = 5; // Правый мотор: направление 1
const int IN4 = 6; // Правый мотор: направление 2

void setupMotors() {
  pinMode(ENA, OUTPUT);
  pinMode(IN1, OUTPUT);
  pinMode(IN2, OUTPUT);
  pinMode(ENB, OUTPUT);
  pinMode(IN3, OUTPUT);
  pinMode(IN4, OUTPUT);
}

void setLeftMotor(int speed, bool forward) {
  if (forward) {
    digitalWrite(IN1, HIGH);
    digitalWrite(IN2, LOW);
  } else {
    digitalWrite(IN1, LOW);
    digitalWrite(IN2, HIGH);
  }
  analogWrite(ENA, abs(speed));
}

void setRightMotor(int speed, bool forward) {
  if (forward) {
    digitalWrite(IN3, HIGH);
    digitalWrite(IN4, LOW);
  } else {
    digitalWrite(IN3, LOW);
    digitalWrite(IN4, HIGH);
  }
  analogWrite(ENB, abs(speed));
}

void drive(int leftSpeed, int rightSpeed) {
  // Универсальная функция движения для робота
  setLeftMotor(leftSpeed, leftSpeed >= 0);
  setRightMotor(rightSpeed, rightSpeed >= 0);
}

void robotStop() {
  drive(0, 0);
}

Примеры маневров для робота

// Движение вперед
void moveForward(int speed) {
  drive(speed, speed);
}

// Поворот на месте
void rotate(int speed, bool clockwise) {
  if (clockwise) {
    drive(speed, -speed); // Правый назад, левый вперед
  } else {
    drive(-speed, speed); // Левый назад, правый вперед
  }
}

// Плавный поворот (один мотор медленнее)
void smoothTurn(int baseSpeed, int turnAmount) {
  // turnAmount: -100..+100, где отрицательное - влево
  int leftSpeed = constrain(baseSpeed - turnAmount, 0, 255);
  int rightSpeed = constrain(baseSpeed + turnAmount, 0, 255);
  drive(leftSpeed, rightSpeed);
}

Совет для подготовки к проекту: Начните создавать свою библиотеку функций управления моторами уже сейчас. Это значительно упростит разработку робота Line Follower в будущем.

Made on
Tilda