Проект 9: Движение по линии

Робот Фобо следует по чёрной линии! Три датчика (из Проекта 8) постоянно проверяют, где находится линия, и моторы корректируют направление. Это классический режим автономной робототехники — робот «боится» потерять линию.

Робот Фобо следует по линии

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

  1. Три датчика читают линию (левый, центр, правый)

  2. Если линия по центру (0-1-0) → едем прямо

  3. Если линия ушла влево (1-х-х) → поворачиваем налево

  4. Если линия ушла вправо (х-х-1) → поворачиваем направо

Важно: Используйте значения калибровки датчиков из Проекта 8 (белое/чёрное для каждого датчика).

Рабочий код

Скопируйте и загрузите этот код в Arduino IDE:

/*
 * Проект 9: Движение по линии
 * Робот Фобо следует по чёрной линии
 */

#include <AlashMotorControlLite.h>

// ============ ПИНЫ ДАТЧИКОВ ============
const uint8_t LEFT_SENSOR = A0;
const uint8_t CENTER_SENSOR = A1;
const uint8_t RIGHT_SENSOR = A2;

// ============ КАЛИБРОВКА ============
// ВАЖНО: Замените эти значения на свои из Проекта 8!

// Вариант 1: Обычные датчики (белое=большое, чёрное=маленькое)
const int LEFT_WHITE = 950, LEFT_BLACK = 100;
const int CENTER_WHITE = 940, CENTER_BLACK = 95;
const int RIGHT_WHITE = 960, RIGHT_BLACK = 110;

// Вариант 2: Инвертированные датчики (белое=маленькое, чёрное=большое)
// Если ваши датчики инвертированные, раскомментируйте:
// const int LEFT_WHITE = 28, LEFT_BLACK = 38;
// const int CENTER_WHITE = 29, CENTER_BLACK = 41;
// const int RIGHT_WHITE = 26, RIGHT_BLACK = 36;

// Вычисляем пороги (середина между белым и чёрным)
const int LEFT_THRESHOLD = (LEFT_WHITE + LEFT_BLACK) / 2;
const int CENTER_THRESHOLD = (CENTER_WHITE + CENTER_BLACK) / 2;
const int RIGHT_THRESHOLD = (RIGHT_WHITE + RIGHT_BLACK) / 2;

// Определяем тип датчиков
const bool INVERTED = (LEFT_BLACK > LEFT_WHITE);

// ============ НАСТРОЙКИ СКОРОСТИ ============
const int SPEED_FORWARD = 70;   // Скорость движения вперёд (минимум 45)
const int SPEED_TURN = 80;      // Скорость поворота - внешнее колесо (минимум 65)
const int SPEED_REVERSE = 65;   // Скорость реверса - внутреннее колесо (минимум 65)
const int SPEED_SEARCH = 50;    // Скорость поиска линии (минимум 45)

// ============ МОТОРЫ ============
AlashMotorControlLite motorLeft(DIR_DIR_PWM, 8, 12, 6);  // IN3, IN4, ENB (левый мотор)
AlashMotorControlLite motorRight(DIR_DIR_PWM, 4, 2, 5);  // IN1, IN2, ENA (правый мотор)

// ============ ФУНКЦИЯ ОПРЕДЕЛЕНИЯ ЛИНИИ ============
bool isOnLine(int value, int threshold) {
  if (INVERTED) {
    return value > threshold;  // Инвертированные: больше = чёрное
  } else {
    return value < threshold;  // Обычные: меньше = чёрное
  }
}

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

  Serial.println("=== Робот Фобо: Движение по линии ===");
  Serial.print("Тип датчиков: ");
  Serial.println(INVERTED ? "ИНВЕРТИРОВАННЫЕ" : "ОБЫЧНЫЕ");
  Serial.println("Поставьте робота на линию (центр над чёрным)");
  Serial.println("Старт через 3 секунды...");
  delay(3000);
}

void loop() {
  // Читаем датчики
  int leftValue = analogRead(LEFT_SENSOR);
  int centerValue = analogRead(CENTER_SENSOR);
  int rightValue = analogRead(RIGHT_SENSOR);

  // Определяем, на линии ли каждый датчик
  bool L = isOnLine(leftValue, LEFT_THRESHOLD);
  bool C = isOnLine(centerValue, CENTER_THRESHOLD);
  bool R = isOnLine(rightValue, RIGHT_THRESHOLD);

  // Вывод в Serial Monitor (каждые 200 мс)
  static unsigned long lastPrint = 0;
  if (millis() - lastPrint > 200) {
    Serial.print(L ? "■" : "□");
    Serial.print("-");
    Serial.print(C ? "■" : "□");
    Serial.print("-");
    Serial.print(R ? "■" : "□");
    Serial.print("  (");
    Serial.print(leftValue);
    Serial.print("/");
    Serial.print(centerValue);
    Serial.print("/");
    Serial.print(rightValue);
    Serial.print(")  ");

    // Показываем действие
    if (!L && C && !R) Serial.println("→ ПРЯМО");
    else if (L) Serial.println("↖ ВЛЕВО");
    else if (R) Serial.println("↗ ВПРАВО");
    else Serial.println("? ПОИСК");

    lastPrint = millis();
  }

  // ========== ЛОГИКА СЛЕДОВАНИЯ ПО ЛИНИИ ==========

  // Случай 1: Центр на линии (0-1-0) → ПРЯМО
  if (!L && C && !R) {
    motorLeft.setSpeed(SPEED_FORWARD);
    motorRight.setSpeed(SPEED_FORWARD);
  }

  // Случай 2: Линия ушла влево (1-х-х) → ПОВОРОТ НАЛЕВО
  else if (L) {
    motorLeft.setSpeed(-SPEED_REVERSE);   // Левое колесо назад
    motorRight.setSpeed(SPEED_TURN);      // Правое колесо вперёд
  }

  // Случай 3: Линия ушла вправо (х-х-1) → ПОВОРОТ НАПРАВО
  else if (R) {
    motorLeft.setSpeed(SPEED_TURN);       // Левое колесо вперёд
    motorRight.setSpeed(-SPEED_REVERSE);  // Правое колесо назад
  }

  // Случай 4: Линия потеряна (0-0-0) → МЕДЛЕННЫЙ ПОИСК
  else {
    motorLeft.setSpeed(SPEED_SEARCH);
    motorRight.setSpeed(SPEED_SEARCH);
  }
}

Как работает код:

  • Калибровка — используются значения белого/чёрного из Проекта 8

  • isOnLine() — функция определяет, на линии ли датчик (учитывает инверсию)

  • 4 случая — центр, лево, право, потеря линии

  • Резкий поворот — одно колесо назад, другое вперёд (поворот на месте)

  • Serial Monitor — показывает датчики (■□), значения и действия

Почему реверс одного колеса?

Обычный поворот: оба колеса вперёд, одно быстрее → плавная дуга. Реверс: одно колесо назад, другое вперёд → резкий разворот на месте.

Это помогает на крутых поворотах — робот не «вылетает» с линии.

Подготовка трассы

Требования к трассе:

  • Чёрная линия на белом фоне (или наоборот)

  • Ширина линии: 2-3 см (стандартная изолента)

  • Повороты плавные (радиус не менее 10 см)

  • Ровная поверхность, хорошее освещение

Как создать трассу:

  • Используйте чёрную изоленту (ширина 2-3 см) на светлой поверхности

  • Светлая поверхность: белая бумага формата A2/A1, ватман, светлый картон, гладкий светлый стол или пол

  • Изолента легко наклеивается и снимается — ученик может сам проектировать трассу!

Совет

Начните с простого: сначала прямая линия 1 метр, добавьте плавный поворот, потом замкнутый круг. Изолента позволяет легко переделывать трассу!

Загрузка и тест

  1. Замените значения калибровки в коде на свои из Проекта 8

  2. Загрузите код (процесс загрузки см. Урок 1)

  3. Поставьте робота на линию (центральный датчик над чёрной линией)

  4. Включите питание — старт через 3 секунды

  5. Робот следует по линии! В Serial Monitor видно состояние датчиков (■-■-□)

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

Эксперимент 1: Настройка скорости

Попробуйте разные комбинации скоростей:

Вариант A: Медленный и точный

const int SPEED_FORWARD = 55;   // Медленно
const int SPEED_TURN = 70;      // Минимум 65 для поворотов
const int SPEED_REVERSE = 65;   // Минимум 65
const int SPEED_SEARCH = 45;    // Минимум 45

Вариант B: Быстрый и агрессивный

const int SPEED_FORWARD = 90;   // Быстро
const int SPEED_TURN = 100;
const int SPEED_REVERSE = 85;
const int SPEED_SEARCH = 60;

Задание: Какая скорость позволяет проходить самые крутые повороты? При какой скорости робот самый быстрый?

Эксперимент 2: Плавные повороты

Замените логику на плавные повороты (без реверса):

const int SPEED_SLOW = 50;  // Медленная скорость (минимум 45)

// Случай 2: Линия ушла влево → ПЛАВНЫЙ ПОВОРОТ
else if (L) {
  motorLeft.setSpeed(SPEED_SLOW);   // Левое медленнее
  motorRight.setSpeed(SPEED_TURN);  // Правое быстрее
}

// Случай 3: Линия ушла вправо → ПЛАВНЫЙ ПОВОРОТ
else if (R) {
  motorLeft.setSpeed(SPEED_TURN);   // Левое быстрее
  motorRight.setSpeed(SPEED_SLOW);  // Правое медленнее
}

Задание: Сравните резкие и плавные повороты. Какой алгоритм проходит острые углы? Какой едет плавнее?

Эксперимент 3: Детальная логика (8 случаев)

Робот Phobo с 8-случайной логикой движения по линии

Текущий код использует упрощённую логику (4 случая). Добавим детализацию:

const int SPEED_SLOW = 50;  // Медленная скорость (минимум 45)

// Центр на линии (0-1-0) → ПРЯМО
if (!L && C && !R) {
  motorLeft.setSpeed(SPEED_FORWARD);
  motorRight.setSpeed(SPEED_FORWARD);
}

// Лёгкий сдвиг влево (1-1-0) → Лёгкий поворот
else if (L && C && !R) {
  motorLeft.setSpeed(SPEED_SLOW);
  motorRight.setSpeed(SPEED_TURN);
}

// Лёгкий сдвиг вправо (0-1-1) → Лёгкий поворот
else if (!L && C && R) {
  motorLeft.setSpeed(SPEED_TURN);
  motorRight.setSpeed(SPEED_SLOW);
}

// Резкий сдвиг влево (1-0-0) → Резкий поворот с реверсом
else if (L && !C && !R) {
  motorLeft.setSpeed(-SPEED_REVERSE);
  motorRight.setSpeed(SPEED_TURN);
}

// Резкий сдвиг вправо (0-0-1) → Резкий поворот с реверсом
else if (!L && !C && R) {
  motorLeft.setSpeed(SPEED_TURN);
  motorRight.setSpeed(-SPEED_REVERSE);
}

// Все датчики на линии (1-1-1) → Перекрёсток или широкая линия
else if (L && C && R) {
  motorLeft.setSpeed(SPEED_FORWARD);
  motorRight.setSpeed(SPEED_FORWARD);
}

// Линия потеряна (0-0-0) → Поиск
else {
  motorLeft.setSpeed(SPEED_SEARCH);
  motorRight.setSpeed(SPEED_SEARCH);
}

Задание: Сравните простой (4 случая) и детальный (8 случаев) алгоритмы. Какой точнее? Какой проще понять?

Эксперимент 4: Счётчик кругов

Добавьте подсчёт пройденных кругов (полезно для тестирования):

int lapCount = 0;
bool wasAllSensors = false;

void loop() {
  // ... чтение датчиков ...

  // Обнаружение перекрёстка (все датчики на линии = 1-1-1)
  if (L && C && R) {
    if (!wasAllSensors) {
      lapCount++;
      Serial.print("🏁 Круг пройден! Всего кругов: ");
      Serial.println(lapCount);
      wasAllSensors = true;
    }
    motorLeft.setSpeed(SPEED_FORWARD);
    motorRight.setSpeed(SPEED_FORWARD);
  } else {
    wasAllSensors = false;

    // ... остальная логика ...
  }
}

Задание: Измерьте среднее время прохождения круга при разных скоростях.

Решение проблем

Проблема 1: Робот крутится на месте

Причины:

  • Слишком сильный реверс → Уменьшите SPEED_REVERSE (с 60 до 40)

  • Линия слишком узкая → Используйте линию 2-3 см шириной

  • Датчики слишком чувствительные → Проверьте калибровку

Проблема 2: Робот теряет линию на поворотах

Причины:

  • Недостаточно резкий поворот → Увеличьте SPEED_REVERSE (до 80)

  • Слишком быстрое движение → Уменьшите SPEED_FORWARD

  • Повороты слишком крутые → Начните с плавных дуг

Проблема 3: Робот едет криво на прямой

Решение: Откалибруйте моторы (см. Проект 2) или добавьте коррекцию:

// Если робот сворачивает вправо:
if (!L && C && !R) {
  motorLeft.setSpeed(SPEED_FORWARD);
  motorRight.setSpeed(SPEED_FORWARD - 5);  // Коррекция
}

Проблема 4: Датчики не видят линию

Проверьте:

  • Калибровка правильная? (Проект 8)

  • Датчики на высоте 3-5 мм от поверхности?

  • Провода подключены к A0, A1, A2?

  • Serial Monitor показывает разные значения на белом/чёрном?

Проблема 5: Робот не останавливается на конце линии

Решение: Добавьте обнаружение конца линии:

// Если все датчики потеряли линию более 2 секунд → стоп
static unsigned long lostTime = 0;

if (!L && !C && !R) {
  if (lostTime == 0) lostTime = millis();
  if (millis() - lostTime > 2000) {
    motorLeft.stop();
    motorRight.stop();
    Serial.println("⚠️ Конец линии!");
    while(1);  // Остановка программы
  }
} else {
  lostTime = 0;
}

Заключение

Отлично! 🎉 Робот Фобо теперь следует по линии автономно!

Что вы узнали:

✅ Алгоритм следования по линии (4 случая: центр, лево, право, потеря)

✅ Резкие повороты с реверсом колеса (для крутых углов)

✅ Плавные повороты без реверса (для дуг)

✅ Отладку через Serial Monitor (визуализация датчиков ■□)

✅ Калибровку и адаптацию под разные трассы

Что дальше:

  • Проект 10: ИК-пульт — знакомство с инфракрасным управлением

  • Проект 11: Управление роботом с ИК-пульта — ручное управление

  • Проект 14: Мастер-режим — все режимы вместе!

Успехов в робототехнике! 🤖