Проект 9: Движение по линии
Робот Фобо следует по чёрной линии! Три датчика (из Проекта 8) постоянно проверяют, где находится линия, и моторы корректируют направление. Это классический режим автономной робототехники — робот «боится» потерять линию.
Как это работает:
Три датчика читают линию (левый, центр, правый)
Если линия по центру (0-1-0) → едем прямо
Если линия ушла влево (1-х-х) → поворачиваем налево
Если линия ушла вправо (х-х-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 метр, добавьте плавный поворот, потом замкнутый круг. Изолента позволяет легко переделывать трассу!
Загрузка и тест
Замените значения калибровки в коде на свои из Проекта 8
Загрузите код (процесс загрузки см. Урок 1)
Поставьте робота на линию (центральный датчик над чёрной линией)
Включите питание — старт через 3 секунды
Робот следует по линии! В 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 случаев)
Текущий код использует упрощённую логику (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: Мастер-режим — все режимы вместе!
Успехов в робототехнике! 🤖